Skip to content
Open
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
9 changes: 7 additions & 2 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <PyTypeObject.tp_itemsize>` such as
:class:`int`, :class:`bytes`, and :class:`tuple`.
:c:member:`"variable-length" built-in type <PyTypeObject.tp_itemsize>`,
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__*.

Expand All @@ -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 <PyTypeObject.tp_itemsize>`
with the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag.


.. _class-customization:
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <PyTypeObject.tp_itemsize>`
with the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag.
(Contributed by Serhiy Storchaka in :gh:`103740`.)


New modules
===========

Expand Down
34 changes: 26 additions & 8 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <slots>`
for any class. Allowed defining any *__slots__* for a class derived from
:class:`type` or other :c:member:`"variable-length" built-in type
<PyTypeObject.tp_itemsize>` with the :c:macro:`Py_TPFLAGS_ITEMS_AT_END`
flag.
4 changes: 3 additions & 1 deletion Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading