Skip to content

Commit 8f5790d

Browse files
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.
1 parent 3149d64 commit 8f5790d

File tree

5 files changed

+47
-11
lines changed

5 files changed

+47
-11
lines changed

Doc/reference/datamodel.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2632,8 +2632,10 @@ Notes on using *__slots__*:
26322632

26332633
* :exc:`TypeError` will be raised if *__slots__* other than *__dict__* and
26342634
*__weakref__* are defined for a class derived from a
2635-
:c:member:`"variable-length" built-in type <PyTypeObject.tp_itemsize>` such as
2636-
:class:`int`, :class:`bytes`, and :class:`tuple`.
2635+
:c:member:`"variable-length" built-in type <PyTypeObject.tp_itemsize>`,
2636+
unless the C type is defined with :c:macro:`Py_TPFLAGS_ITEMS_AT_END`.
2637+
For example, they cannot be defined on subclasses of
2638+
:class:`int`, :class:`bytes`, or :class:`tuple`.
26372639

26382640
* Any non-string :term:`iterable` may be assigned to *__slots__*.
26392641

@@ -2658,6 +2660,9 @@ Notes on using *__slots__*:
26582660

26592661
.. versionchanged:: next
26602662
Allowed defining the *__dict__* and *__weakref__* *__slots__* for any class.
2663+
Allowed defining any *__slots__* for a class derived from :class:`type` or
2664+
other :c:member:`"variable-length" built-in type <PyTypeObject.tp_itemsize>`
2665+
with the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag.
26612666

26622667

26632668
.. _class-customization:

Doc/whatsnew/3.15.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,12 @@ Other language changes
399399
(Contributed by Serhiy Storchaka in :gh:`41779`.)
400400

401401

402+
* Allowed defining any *__slots__* for a class derived from :class:`type` or
403+
other :c:member:`"variable-length" built-in type <PyTypeObject.tp_itemsize>`
404+
with the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag.
405+
(Contributed by Serhiy Storchaka in :gh:`103740`.)
406+
407+
402408
New modules
403409
===========
404410

Lib/test/test_descr.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,32 @@ class X(object):
13201320
with self.assertRaisesRegex(AttributeError, "'X' object has no attribute 'a'"):
13211321
X().a
13221322

1323+
def test_slots_before_items(self):
1324+
class C(type):
1325+
__slots__ = ['a']
1326+
x = C('A', (int,), {})
1327+
self.assertNotHasAttr(x, "a")
1328+
x.a = 1
1329+
x.b = 2
1330+
self.assertEqual(x.a, 1)
1331+
self.assertEqual(x.b, 2)
1332+
self.assertNotIn('a', x.__dict__)
1333+
self.assertIn('b', x.__dict__)
1334+
del x.a
1335+
self.assertNotHasAttr(x, "a")
1336+
1337+
@unittest.skipIf(_testcapi is None, 'need the _testcapi module')
1338+
def test_slots_before_items2(self):
1339+
class D(_testcapi.HeapCCollection):
1340+
__slots__ = ['a']
1341+
x = D(1, 2, 3)
1342+
self.assertNotHasAttr(x, "a")
1343+
x.a = 42
1344+
self.assertEqual(x.a, 42)
1345+
del x.a
1346+
self.assertNotHasAttr(x, "a")
1347+
self.assertEqual(list(x), [1, 2, 3])
1348+
13231349
def test_slots_special(self):
13241350
# Testing __dict__ and __weakref__ in __slots__...
13251351
class D(object):
@@ -1384,14 +1410,6 @@ class W(_testcapi.HeapCCollection):
13841410
a.foo = 42
13851411
self.assertIs(weakref.ref(a)(), a)
13861412

1387-
with self.assertRaises(TypeError):
1388-
class X(_testcapi.HeapCCollection):
1389-
__slots__ = ['x']
1390-
1391-
with self.assertRaises(TypeError):
1392-
class X(_testcapi.HeapCCollection):
1393-
__slots__ = ['__dict__', 'x']
1394-
13951413
@support.subTests(('base', 'arg'), [
13961414
(tuple, (1, 2, 3)),
13971415
(int, 9876543210**2),
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <slots>`
2+
for any class. Allowed defining any *__slots__* for a class derived from
3+
:class:`type` or other :c:member:`"variable-length" built-in type
4+
<PyTypeObject.tp_itemsize>` with the :c:macro:`Py_TPFLAGS_ITEMS_AT_END`
5+
flag.

Objects/typeobject.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4641,7 +4641,9 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type, PyObject *dict
46414641
if (et->ht_slots != NULL) {
46424642
PyMemberDef *mp = _PyHeapType_GET_MEMBERS(et);
46434643
Py_ssize_t nslot = PyTuple_GET_SIZE(et->ht_slots);
4644-
if (ctx->base->tp_itemsize != 0) {
4644+
int after_items = (ctx->base->tp_itemsize != 0 &&
4645+
!(ctx->base->tp_flags & Py_TPFLAGS_ITEMS_AT_END));
4646+
if (after_items) {
46454647
PyErr_Format(PyExc_TypeError,
46464648
"arbitrary __slots__ not supported for subtype of '%s'",
46474649
ctx->base->tp_name);

0 commit comments

Comments
 (0)