https://github.com/python/cpython/commit/4bcab461c2185517284ae9764cca26fa4519a6e9
commit: 4bcab461c2185517284ae9764cca26fa4519a6e9
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-11-19T17:11:37Z
summary:
gh-41779: Allow defining the __dict__ and __weakref__ __slots__ for any class
(GH-141755)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2025-11-16-21-14-48.gh-issue-41779.rXIj5h.rst
M Doc/reference/datamodel.rst
M Doc/whatsnew/3.15.rst
M Lib/test/test_descr.py
M Objects/typeobject.c
diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst
index 882b05e87319fa..ebadbc215a0eed 100644
--- a/Doc/reference/datamodel.rst
+++ b/Doc/reference/datamodel.rst
@@ -2630,8 +2630,8 @@ 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
+* :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`.
@@ -2656,6 +2656,10 @@ 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.
+
+
.. _class-customization:
Customizing class creation
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index 5a98297d3f8847..d0af9212d55567 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -394,6 +394,10 @@ Other language changes
syntax warnings by module name.
(Contributed by Serhiy Storchaka in :gh:`135801`.)
+* Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <slots>`
+ for any class.
+ (Contributed by Serhiy Storchaka in :gh:`41779`.)
+
New modules
===========
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 14f94285d3f3c2..82a48ad4d1aced 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -1329,18 +1329,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__ = []
@@ -1349,6 +1348,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__ = []
@@ -1357,6 +1357,77 @@ 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)
+
+ 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),
+ (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))
+
+ with self.assertRaises(TypeError):
+ class X(base):
+ __slots__ = ['x']
+ with self.assertRaises(TypeError):
+ class X(base):
+ __slots__ = ['__dict__', 'x']
def test_slots_special2(self):
# Testing __qualname__ and __classcell__ in __slots__
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2025-11-16-21-14-48.gh-issue-41779.rXIj5h.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-16-21-14-48.gh-issue-41779.rXIj5h.rst
new file mode 100644
index 00000000000000..8ba3ca8df86ec8
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-16-21-14-48.gh-issue-41779.rXIj5h.rst
@@ -0,0 +1,2 @@
+Allowed defining the *__dict__* and *__weakref__* :ref:`__slots__ <slots>`
+for any class.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index c99c6b3f6377b6..4c6ff51493f799 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -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;
}
@@ -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++;
}
}
@@ -4650,6 +4641,12 @@ 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) {
+ PyErr_Format(PyExc_TypeError,
+ "arbitrary __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));
@@ -4889,8 +4886,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;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]