Skip to content

Commit b42a86a

Browse files
gh-41779: Allow defining the __dict__ and __weakref__ __slots__ for any class
1 parent b3b63e8 commit b42a86a

File tree

5 files changed

+89
-20
lines changed

5 files changed

+89
-20
lines changed

Doc/reference/datamodel.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2630,8 +2630,8 @@ Notes on using *__slots__*:
26302630
descriptor directly from the base class). This renders the meaning of the
26312631
program undefined. In the future, a check may be added to prevent this.
26322632

2633-
* :exc:`TypeError` will be raised if nonempty *__slots__* are defined for a
2634-
class derived from a
2633+
* :exc:`TypeError` will be raised if *__slots__* other than *__dict__* and
2634+
*__weakref__* are defined for a class derived from a
26352635
:c:member:`"variable-length" built-in type <PyTypeObject.tp_itemsize>` such as
26362636
:class:`int`, :class:`bytes`, and :class:`tuple`.
26372637

@@ -2656,6 +2656,10 @@ Notes on using *__slots__*:
26562656
of the iterator's values. However, the *__slots__* attribute will be an empty
26572657
iterator.
26582658

2659+
.. versionchanged:: next
2660+
Allowed defining the *__dict__* and *__weakref__* *__slots__* for any class.
2661+
2662+
26592663
.. _class-customization:
26602664

26612665
Customizing class creation

Doc/whatsnew/3.15.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,10 @@ Other language changes
394394
syntax warnings by module name.
395395
(Contributed by Serhiy Storchaka in :gh:`135801`.)
396396

397+
* Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <slots>`
398+
for any class.
399+
(Contributed by Serhiy Storchaka in :gh:`41779`.)
400+
397401

398402
New modules
399403
===========

Lib/test/test_descr.py

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,18 +1329,17 @@ class D(object):
13291329
self.assertNotHasAttr(a, "__weakref__")
13301330
a.foo = 42
13311331
self.assertEqual(a.__dict__, {"foo": 42})
1332+
with self.assertRaises(TypeError):
1333+
weakref.ref(a)
13321334

13331335
class W(object):
13341336
__slots__ = ["__weakref__"]
13351337
a = W()
13361338
self.assertHasAttr(a, "__weakref__")
13371339
self.assertNotHasAttr(a, "__dict__")
1338-
try:
1340+
with self.assertRaises(AttributeError):
13391341
a.foo = 42
1340-
except AttributeError:
1341-
pass
1342-
else:
1343-
self.fail("shouldn't be allowed to set a.foo")
1342+
self.assertIs(weakref.ref(a)(), a)
13441343

13451344
class C1(W, D):
13461345
__slots__ = []
@@ -1349,6 +1348,7 @@ class C1(W, D):
13491348
self.assertHasAttr(a, "__weakref__")
13501349
a.foo = 42
13511350
self.assertEqual(a.__dict__, {"foo": 42})
1351+
self.assertIs(weakref.ref(a)(), a)
13521352

13531353
class C2(D, W):
13541354
__slots__ = []
@@ -1357,6 +1357,62 @@ class C2(D, W):
13571357
self.assertHasAttr(a, "__weakref__")
13581358
a.foo = 42
13591359
self.assertEqual(a.__dict__, {"foo": 42})
1360+
self.assertIs(weakref.ref(a)(), a)
1361+
1362+
@unittest.skipIf(_testcapi is None, 'need the _testcapi module')
1363+
def test_slots_special_before_items(self):
1364+
class D(_testcapi.HeapCCollection):
1365+
__slots__ = ["__dict__"]
1366+
a = D(1, 2, 3)
1367+
self.assertHasAttr(a, "__dict__")
1368+
self.assertNotHasAttr(a, "__weakref__")
1369+
a.foo = 42
1370+
self.assertEqual(a.__dict__, {"foo": 42})
1371+
with self.assertRaises(TypeError):
1372+
weakref.ref(a)
1373+
del a.__dict__
1374+
self.assertNotHasAttr(a, "foo")
1375+
self.assertEqual(a.__dict__, {})
1376+
self.assertEqual(list(a), [1, 2, 3])
1377+
1378+
class W(_testcapi.HeapCCollection):
1379+
__slots__ = ["__weakref__"]
1380+
a = W(1, 2, 3)
1381+
self.assertHasAttr(a, "__weakref__")
1382+
self.assertNotHasAttr(a, "__dict__")
1383+
with self.assertRaises(AttributeError):
1384+
a.foo = 42
1385+
self.assertIs(weakref.ref(a)(), a)
1386+
1387+
@support.subTests(('base', 'arg'), [
1388+
(tuple, (1, 2, 3)),
1389+
(int, 9876543210**2),
1390+
(bytes, b'ab'),
1391+
])
1392+
def test_slots_special_after_items(self, base, arg):
1393+
class D(base):
1394+
__slots__ = ["__dict__"]
1395+
a = D(arg)
1396+
self.assertHasAttr(a, "__dict__")
1397+
self.assertNotHasAttr(a, "__weakref__")
1398+
a.foo = 42
1399+
self.assertEqual(a.__dict__, {"foo": 42})
1400+
with self.assertRaises(TypeError):
1401+
weakref.ref(a)
1402+
del a.__dict__
1403+
self.assertNotHasAttr(a, "foo")
1404+
self.assertEqual(a.__dict__, {})
1405+
self.assertEqual(a, base(arg))
1406+
1407+
class W(base):
1408+
__slots__ = ["__weakref__"]
1409+
a = W(arg)
1410+
self.assertHasAttr(a, "__weakref__")
1411+
self.assertNotHasAttr(a, "__dict__")
1412+
with self.assertRaises(AttributeError):
1413+
a.foo = 42
1414+
self.assertIs(weakref.ref(a)(), a)
1415+
self.assertEqual(a, base(arg))
13601416

13611417
def test_slots_special2(self):
13621418
# Testing __qualname__ and __classcell__ in __slots__
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <slots>`
2+
for any class.

Objects/typeobject.c

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4343,14 +4343,6 @@ type_new_slots_bases(type_new_ctx *ctx)
43434343
static int
43444344
type_new_slots_impl(type_new_ctx *ctx, PyObject *dict)
43454345
{
4346-
/* Are slots allowed? */
4347-
if (ctx->nslot > 0 && ctx->base->tp_itemsize != 0) {
4348-
PyErr_Format(PyExc_TypeError,
4349-
"nonempty __slots__ not supported for subtype of '%s'",
4350-
ctx->base->tp_name);
4351-
return -1;
4352-
}
4353-
43544346
if (type_new_visit_slots(ctx) < 0) {
43554347
return -1;
43564348
}
@@ -4377,14 +4369,13 @@ type_new_slots(type_new_ctx *ctx, PyObject *dict)
43774369
ctx->add_dict = 0;
43784370
ctx->add_weak = 0;
43794371
ctx->may_add_dict = (ctx->base->tp_dictoffset == 0);
4380-
ctx->may_add_weak = (ctx->base->tp_weaklistoffset == 0
4381-
&& ctx->base->tp_itemsize == 0);
4372+
ctx->may_add_weak = (ctx->base->tp_weaklistoffset == 0);
43824373

43834374
if (ctx->slots == NULL) {
43844375
if (ctx->may_add_dict) {
43854376
ctx->add_dict++;
43864377
}
4387-
if (ctx->may_add_weak) {
4378+
if (ctx->may_add_weak && ctx->base->tp_itemsize == 0) {
43884379
ctx->add_weak++;
43894380
}
43904381
}
@@ -4650,6 +4641,12 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type, PyObject *dict
46504641
if (et->ht_slots != NULL) {
46514642
PyMemberDef *mp = _PyHeapType_GET_MEMBERS(et);
46524643
Py_ssize_t nslot = PyTuple_GET_SIZE(et->ht_slots);
4644+
if (ctx->base->tp_itemsize != 0) {
4645+
PyErr_Format(PyExc_TypeError,
4646+
"nonempty __slots__ not supported for subtype of '%s'",
4647+
ctx->base->tp_name);
4648+
return -1;
4649+
}
46534650
for (Py_ssize_t i = 0; i < nslot; i++, mp++) {
46544651
mp->name = PyUnicode_AsUTF8(
46554652
PyTuple_GET_ITEM(et->ht_slots, i));
@@ -4889,8 +4886,14 @@ type_new_init(type_new_ctx *ctx)
48894886
set_tp_dict(type, dict);
48904887

48914888
PyHeapTypeObject *et = (PyHeapTypeObject*)type;
4892-
et->ht_slots = ctx->slots;
4893-
ctx->slots = NULL;
4889+
if (ctx->slots && PyTuple_GET_SIZE(ctx->slots)) {
4890+
et->ht_slots = ctx->slots;
4891+
ctx->slots = NULL;
4892+
}
4893+
else {
4894+
et->ht_slots = NULL;
4895+
Py_CLEAR(ctx->slots);
4896+
}
48944897

48954898
return type;
48964899

0 commit comments

Comments
 (0)