Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 12 additions & 3 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2630,9 +2630,10 @@ 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
:c:member:`"variable-length" built-in type <PyTypeObject.tp_itemsize>` such as
* :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>`
without the :c:macro:`Py_TPFLAGS_ITEMS_AT_END` flag such as
:class:`int`, :class:`bytes`, and :class:`tuple`.
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated

* Any non-string :term:`iterable` may be assigned to *__slots__*.
Expand All @@ -2656,6 +2657,14 @@ 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.

Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
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:

Customizing class creation
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,13 @@
syntax warnings by module name.
(Contributed by Serhiy Storchaka in :gh:`135801`.)

* Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <_slots>`

Check warning on line 397 in Doc/whatsnew/3.15.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

undefined label: '_slots' [ref.ref]
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
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.
(Contributed by Serhiy Storchaka in :gh:`103740`.)


New modules
===========
Expand Down
92 changes: 87 additions & 5 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 All @@ -1329,18 +1355,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 +1374,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 +1383,62 @@ 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)

@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))

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,5 @@
Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <_slots>`
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
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.
31 changes: 18 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,14 @@ 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);
int after_items = (ctx->base->tp_itemsize != 0 &&
!(ctx->base->tp_flags & Py_TPFLAGS_ITEMS_AT_END));
if (after_items) {
PyErr_Format(PyExc_TypeError,
"nonempty __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 +4888,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