Skip to content

Commit 5849af7

Browse files
authored
GH-93521: For dataclasses, filter out __weakref__ slot if present in bases (GH-93535)
1 parent ffc58a9 commit 5849af7

File tree

3 files changed

+61
-4
lines changed

3 files changed

+61
-4
lines changed

Lib/dataclasses.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -1156,11 +1156,16 @@ def _add_slots(cls, is_frozen, weakref_slot):
11561156
itertools.chain.from_iterable(map(_get_slots, cls.__mro__[1:-1]))
11571157
)
11581158
# The slots for our class. Remove slots from our base classes. Add
1159-
# '__weakref__' if weakref_slot was given.
1159+
# '__weakref__' if weakref_slot was given, unless it is already present.
11601160
cls_dict["__slots__"] = tuple(
1161-
itertools.chain(
1162-
itertools.filterfalse(inherited_slots.__contains__, field_names),
1163-
("__weakref__",) if weakref_slot else ())
1161+
itertools.filterfalse(
1162+
inherited_slots.__contains__,
1163+
itertools.chain(
1164+
# gh-93521: '__weakref__' also needs to be filtered out if
1165+
# already present in inherited_slots
1166+
field_names, ('__weakref__',) if weakref_slot else ()
1167+
)
1168+
),
11641169
)
11651170

11661171
for field_name in field_names:

Lib/test/test_dataclasses.py

+48
Original file line numberDiff line numberDiff line change
@@ -3109,6 +3109,54 @@ def test_weakref_slot_make_dataclass(self):
31093109
"weakref_slot is True but slots is False"):
31103110
B = make_dataclass('B', [('a', int),], weakref_slot=True)
31113111

3112+
def test_weakref_slot_subclass_weakref_slot(self):
3113+
@dataclass(slots=True, weakref_slot=True)
3114+
class Base:
3115+
field: int
3116+
3117+
# A *can* also specify weakref_slot=True if it wants to (gh-93521)
3118+
@dataclass(slots=True, weakref_slot=True)
3119+
class A(Base):
3120+
...
3121+
3122+
# __weakref__ is in the base class, not A. But an instance of A
3123+
# is still weakref-able.
3124+
self.assertIn("__weakref__", Base.__slots__)
3125+
self.assertNotIn("__weakref__", A.__slots__)
3126+
a = A(1)
3127+
weakref.ref(a)
3128+
3129+
def test_weakref_slot_subclass_no_weakref_slot(self):
3130+
@dataclass(slots=True, weakref_slot=True)
3131+
class Base:
3132+
field: int
3133+
3134+
@dataclass(slots=True)
3135+
class A(Base):
3136+
...
3137+
3138+
# __weakref__ is in the base class, not A. Even though A doesn't
3139+
# specify weakref_slot, it should still be weakref-able.
3140+
self.assertIn("__weakref__", Base.__slots__)
3141+
self.assertNotIn("__weakref__", A.__slots__)
3142+
a = A(1)
3143+
weakref.ref(a)
3144+
3145+
def test_weakref_slot_normal_base_weakref_slot(self):
3146+
class Base:
3147+
__slots__ = ('__weakref__',)
3148+
3149+
@dataclass(slots=True, weakref_slot=True)
3150+
class A(Base):
3151+
field: int
3152+
3153+
# __weakref__ is in the base class, not A. But an instance of
3154+
# A is still weakref-able.
3155+
self.assertIn("__weakref__", Base.__slots__)
3156+
self.assertNotIn("__weakref__", A.__slots__)
3157+
a = A(1)
3158+
weakref.ref(a)
3159+
31123160

31133161
class TestDescriptors(unittest.TestCase):
31143162
def test_set_name(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fixed a case where dataclasses would try to add ``__weakref__`` into the
2+
``__slots__`` for a dataclass that specified ``weakref_slot=True`` when it was
3+
already defined in one of its bases. This resulted in a ``TypeError`` upon the
4+
new class being created.

0 commit comments

Comments
 (0)