Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
===========
Expand Down
81 changes: 76 additions & 5 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ = []
Expand All @@ -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__ = []
Expand All @@ -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__
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <slots>`
for any class.
29 changes: 16 additions & 13 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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++;
}
}
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -4889,8 +4886,14 @@ type_new_init(type_new_ctx *ctx)
set_tp_dict(type, dict);

PyHeapTypeObject *et = (PyHeapTypeObject*)type;
et->ht_slots = ctx->slots;
ctx->slots = NULL;
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;

Expand Down
Loading