From 8f5790d0dcaa1a3456e781d911123ffc853d12ae Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 16 Nov 2025 19:41:16 +0200 Subject: [PATCH] gh-103740: Support any slots in the type subclasses Allow defining any __slots__ for a class derived from the type class or other "variable-length" built-in type with the Py_TPFLAGS_ITEMS_AT_END flag. --- Doc/reference/datamodel.rst | 9 +++-- Doc/whatsnew/3.15.rst | 6 ++++ Lib/test/test_descr.py | 34 ++++++++++++++----- ...-11-16-21-14-48.gh-issue-103740.rXIj5h.rst | 5 +++ Objects/typeobject.c | 4 ++- 5 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-11-16-21-14-48.gh-issue-103740.rXIj5h.rst diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index ebadbc215a0eed..17300b05c583c1 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2632,8 +2632,10 @@ Notes on using *__slots__*: * :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 ` such as - :class:`int`, :class:`bytes`, and :class:`tuple`. + :c:member:`"variable-length" built-in type `, + unless the C type is defined with :c:macro:`Py_TPFLAGS_ITEMS_AT_END`. + For example, they cannot be defined on subclasses of + :class:`int`, :class:`bytes`, or :class:`tuple`. * Any non-string :term:`iterable` may be assigned to *__slots__*. @@ -2658,6 +2660,9 @@ Notes on using *__slots__*: .. versionchanged:: next Allowed defining the *__dict__* and *__weakref__* *__slots__* for any class. + Allowed defining any *__slots__* for a class derived from :class:`type` or + other :c:member:`"variable-length" built-in type ` + with the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag. .. _class-customization: diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index d0af9212d55567..d473dafdf44557 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -399,6 +399,12 @@ Other language changes (Contributed by Serhiy Storchaka in :gh:`41779`.) +* Allowed defining any *__slots__* for a class derived from :class:`type` or + other :c:member:`"variable-length" built-in type ` + with the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag. + (Contributed by Serhiy Storchaka in :gh:`103740`.) + + New modules =========== diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 82a48ad4d1aced..4a67107c5dd77e 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1320,6 +1320,32 @@ class X(object): with self.assertRaisesRegex(AttributeError, "'X' object has no attribute 'a'"): X().a + def test_slots_before_items(self): + class C(type): + __slots__ = ['a'] + x = C('A', (int,), {}) + self.assertNotHasAttr(x, "a") + x.a = 1 + x.b = 2 + self.assertEqual(x.a, 1) + self.assertEqual(x.b, 2) + self.assertNotIn('a', x.__dict__) + self.assertIn('b', x.__dict__) + del x.a + self.assertNotHasAttr(x, "a") + + @unittest.skipIf(_testcapi is None, 'need the _testcapi module') + def test_slots_before_items2(self): + class D(_testcapi.HeapCCollection): + __slots__ = ['a'] + x = D(1, 2, 3) + self.assertNotHasAttr(x, "a") + x.a = 42 + self.assertEqual(x.a, 42) + del x.a + self.assertNotHasAttr(x, "a") + self.assertEqual(list(x), [1, 2, 3]) + def test_slots_special(self): # Testing __dict__ and __weakref__ in __slots__... class D(object): @@ -1384,14 +1410,6 @@ class W(_testcapi.HeapCCollection): 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), diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-11-16-21-14-48.gh-issue-103740.rXIj5h.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-16-21-14-48.gh-issue-103740.rXIj5h.rst new file mode 100644 index 00000000000000..cf6d33a17aebc4 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-16-21-14-48.gh-issue-103740.rXIj5h.rst @@ -0,0 +1,5 @@ +Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ ` +for any class. Allowed defining any *__slots__* for a class derived from +:class:`type` or other :c:member:`"variable-length" built-in type +` with the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` +flag. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 4c6ff51493f799..1ef6fc2ee0c561 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4641,7 +4641,9 @@ 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) { + int after_items = (ctx->base->tp_itemsize != 0 && + !(ctx->base->tp_flags & Py_TPFLAGS_ITEMS_AT_END)); + if (after_items) { PyErr_Format(PyExc_TypeError, "arbitrary __slots__ not supported for subtype of '%s'", ctx->base->tp_name);