mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-41779: Allow defining the __dict__ and __weakref__ __slots__ for any class (GH-141755)
This commit is contained in:
parent
598d4c64de
commit
4bcab461c2
5 changed files with 104 additions and 20 deletions
|
|
@ -2630,8 +2630,8 @@ Notes on using *__slots__*:
|
|||
descriptor directly from the base class). This renders the meaning of the
|
||||
program undefined. In the future, a check may be added to prevent this.
|
||||
|
||||
* :exc:`TypeError` will be raised if nonempty *__slots__* are defined for a
|
||||
class derived from a
|
||||
* :exc:`TypeError` will be raised if *__slots__* other than *__dict__* and
|
||||
*__weakref__* are defined for a class derived from a
|
||||
:c:member:`"variable-length" built-in type <PyTypeObject.tp_itemsize>` such as
|
||||
:class:`int`, :class:`bytes`, and :class:`tuple`.
|
||||
|
||||
|
|
@ -2656,6 +2656,10 @@ Notes on using *__slots__*:
|
|||
of the iterator's values. However, the *__slots__* attribute will be an empty
|
||||
iterator.
|
||||
|
||||
.. versionchanged:: next
|
||||
Allowed defining the *__dict__* and *__weakref__* *__slots__* for any class.
|
||||
|
||||
|
||||
.. _class-customization:
|
||||
|
||||
Customizing class creation
|
||||
|
|
|
|||
|
|
@ -394,6 +394,10 @@ Other language changes
|
|||
syntax warnings by module name.
|
||||
(Contributed by Serhiy Storchaka in :gh:`135801`.)
|
||||
|
||||
* Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <slots>`
|
||||
for any class.
|
||||
(Contributed by Serhiy Storchaka in :gh:`41779`.)
|
||||
|
||||
|
||||
New modules
|
||||
===========
|
||||
|
|
|
|||
|
|
@ -1329,18 +1329,17 @@ class D(object):
|
|||
self.assertNotHasAttr(a, "__weakref__")
|
||||
a.foo = 42
|
||||
self.assertEqual(a.__dict__, {"foo": 42})
|
||||
with self.assertRaises(TypeError):
|
||||
weakref.ref(a)
|
||||
|
||||
class W(object):
|
||||
__slots__ = ["__weakref__"]
|
||||
a = W()
|
||||
self.assertHasAttr(a, "__weakref__")
|
||||
self.assertNotHasAttr(a, "__dict__")
|
||||
try:
|
||||
with self.assertRaises(AttributeError):
|
||||
a.foo = 42
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.fail("shouldn't be allowed to set a.foo")
|
||||
self.assertIs(weakref.ref(a)(), a)
|
||||
|
||||
class C1(W, D):
|
||||
__slots__ = []
|
||||
|
|
@ -1349,6 +1348,7 @@ class C1(W, D):
|
|||
self.assertHasAttr(a, "__weakref__")
|
||||
a.foo = 42
|
||||
self.assertEqual(a.__dict__, {"foo": 42})
|
||||
self.assertIs(weakref.ref(a)(), a)
|
||||
|
||||
class C2(D, W):
|
||||
__slots__ = []
|
||||
|
|
@ -1357,6 +1357,77 @@ class C2(D, W):
|
|||
self.assertHasAttr(a, "__weakref__")
|
||||
a.foo = 42
|
||||
self.assertEqual(a.__dict__, {"foo": 42})
|
||||
self.assertIs(weakref.ref(a)(), a)
|
||||
|
||||
@unittest.skipIf(_testcapi is None, 'need the _testcapi module')
|
||||
def test_slots_special_before_items(self):
|
||||
class D(_testcapi.HeapCCollection):
|
||||
__slots__ = ["__dict__"]
|
||||
a = D(1, 2, 3)
|
||||
self.assertHasAttr(a, "__dict__")
|
||||
self.assertNotHasAttr(a, "__weakref__")
|
||||
a.foo = 42
|
||||
self.assertEqual(a.__dict__, {"foo": 42})
|
||||
with self.assertRaises(TypeError):
|
||||
weakref.ref(a)
|
||||
del a.__dict__
|
||||
self.assertNotHasAttr(a, "foo")
|
||||
self.assertEqual(a.__dict__, {})
|
||||
self.assertEqual(list(a), [1, 2, 3])
|
||||
|
||||
class W(_testcapi.HeapCCollection):
|
||||
__slots__ = ["__weakref__"]
|
||||
a = W(1, 2, 3)
|
||||
self.assertHasAttr(a, "__weakref__")
|
||||
self.assertNotHasAttr(a, "__dict__")
|
||||
with self.assertRaises(AttributeError):
|
||||
a.foo = 42
|
||||
self.assertIs(weakref.ref(a)(), a)
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
class X(_testcapi.HeapCCollection):
|
||||
__slots__ = ['x']
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
class X(_testcapi.HeapCCollection):
|
||||
__slots__ = ['__dict__', 'x']
|
||||
|
||||
@support.subTests(('base', 'arg'), [
|
||||
(tuple, (1, 2, 3)),
|
||||
(int, 9876543210**2),
|
||||
(bytes, b'ab'),
|
||||
])
|
||||
def test_slots_special_after_items(self, base, arg):
|
||||
class D(base):
|
||||
__slots__ = ["__dict__"]
|
||||
a = D(arg)
|
||||
self.assertHasAttr(a, "__dict__")
|
||||
self.assertNotHasAttr(a, "__weakref__")
|
||||
a.foo = 42
|
||||
self.assertEqual(a.__dict__, {"foo": 42})
|
||||
with self.assertRaises(TypeError):
|
||||
weakref.ref(a)
|
||||
del a.__dict__
|
||||
self.assertNotHasAttr(a, "foo")
|
||||
self.assertEqual(a.__dict__, {})
|
||||
self.assertEqual(a, base(arg))
|
||||
|
||||
class W(base):
|
||||
__slots__ = ["__weakref__"]
|
||||
a = W(arg)
|
||||
self.assertHasAttr(a, "__weakref__")
|
||||
self.assertNotHasAttr(a, "__dict__")
|
||||
with self.assertRaises(AttributeError):
|
||||
a.foo = 42
|
||||
self.assertIs(weakref.ref(a)(), a)
|
||||
self.assertEqual(a, base(arg))
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
class X(base):
|
||||
__slots__ = ['x']
|
||||
with self.assertRaises(TypeError):
|
||||
class X(base):
|
||||
__slots__ = ['__dict__', 'x']
|
||||
|
||||
def test_slots_special2(self):
|
||||
# Testing __qualname__ and __classcell__ in __slots__
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <slots>`
|
||||
for any class.
|
||||
|
|
@ -4343,14 +4343,6 @@ type_new_slots_bases(type_new_ctx *ctx)
|
|||
static int
|
||||
type_new_slots_impl(type_new_ctx *ctx, PyObject *dict)
|
||||
{
|
||||
/* Are slots allowed? */
|
||||
if (ctx->nslot > 0 && ctx->base->tp_itemsize != 0) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"nonempty __slots__ not supported for subtype of '%s'",
|
||||
ctx->base->tp_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (type_new_visit_slots(ctx) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -4377,14 +4369,13 @@ type_new_slots(type_new_ctx *ctx, PyObject *dict)
|
|||
ctx->add_dict = 0;
|
||||
ctx->add_weak = 0;
|
||||
ctx->may_add_dict = (ctx->base->tp_dictoffset == 0);
|
||||
ctx->may_add_weak = (ctx->base->tp_weaklistoffset == 0
|
||||
&& ctx->base->tp_itemsize == 0);
|
||||
ctx->may_add_weak = (ctx->base->tp_weaklistoffset == 0);
|
||||
|
||||
if (ctx->slots == NULL) {
|
||||
if (ctx->may_add_dict) {
|
||||
ctx->add_dict++;
|
||||
}
|
||||
if (ctx->may_add_weak) {
|
||||
if (ctx->may_add_weak && ctx->base->tp_itemsize == 0) {
|
||||
ctx->add_weak++;
|
||||
}
|
||||
}
|
||||
|
|
@ -4650,6 +4641,12 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type, PyObject *dict
|
|||
if (et->ht_slots != NULL) {
|
||||
PyMemberDef *mp = _PyHeapType_GET_MEMBERS(et);
|
||||
Py_ssize_t nslot = PyTuple_GET_SIZE(et->ht_slots);
|
||||
if (ctx->base->tp_itemsize != 0) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"arbitrary __slots__ not supported for subtype of '%s'",
|
||||
ctx->base->tp_name);
|
||||
return -1;
|
||||
}
|
||||
for (Py_ssize_t i = 0; i < nslot; i++, mp++) {
|
||||
mp->name = PyUnicode_AsUTF8(
|
||||
PyTuple_GET_ITEM(et->ht_slots, i));
|
||||
|
|
@ -4889,8 +4886,14 @@ type_new_init(type_new_ctx *ctx)
|
|||
set_tp_dict(type, dict);
|
||||
|
||||
PyHeapTypeObject *et = (PyHeapTypeObject*)type;
|
||||
if (ctx->slots && PyTuple_GET_SIZE(ctx->slots)) {
|
||||
et->ht_slots = ctx->slots;
|
||||
ctx->slots = NULL;
|
||||
}
|
||||
else {
|
||||
et->ht_slots = NULL;
|
||||
Py_CLEAR(ctx->slots);
|
||||
}
|
||||
|
||||
return type;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue