From 5849af7a80166e9e82040e082f22772bd7cf3061 Mon Sep 17 00:00:00 2001 From: Bluenix Date: Wed, 8 Jun 2022 02:53:08 +0200 Subject: GH-93521: For dataclasses, filter out `__weakref__` slot if present in bases (GH-93535) --- Lib/dataclasses.py | 13 ++++-- Lib/test/test_dataclasses.py | 48 ++++++++++++++++++++++ .../2022-06-06-13-19-43.gh-issue-93521._vE8m9.rst | 4 ++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-06-06-13-19-43.gh-issue-93521._vE8m9.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 4645ebf..18ab690 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1156,11 +1156,16 @@ def _add_slots(cls, is_frozen, weakref_slot): itertools.chain.from_iterable(map(_get_slots, cls.__mro__[1:-1])) ) # The slots for our class. Remove slots from our base classes. Add - # '__weakref__' if weakref_slot was given. + # '__weakref__' if weakref_slot was given, unless it is already present. cls_dict["__slots__"] = tuple( - itertools.chain( - itertools.filterfalse(inherited_slots.__contains__, field_names), - ("__weakref__",) if weakref_slot else ()) + itertools.filterfalse( + inherited_slots.__contains__, + itertools.chain( + # gh-93521: '__weakref__' also needs to be filtered out if + # already present in inherited_slots + field_names, ('__weakref__',) if weakref_slot else () + ) + ), ) for field_name in field_names: diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index cf29cd0..98dfab4 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -3109,6 +3109,54 @@ class TestSlots(unittest.TestCase): "weakref_slot is True but slots is False"): B = make_dataclass('B', [('a', int),], weakref_slot=True) + def test_weakref_slot_subclass_weakref_slot(self): + @dataclass(slots=True, weakref_slot=True) + class Base: + field: int + + # A *can* also specify weakref_slot=True if it wants to (gh-93521) + @dataclass(slots=True, weakref_slot=True) + class A(Base): + ... + + # __weakref__ is in the base class, not A. But an instance of A + # is still weakref-able. + self.assertIn("__weakref__", Base.__slots__) + self.assertNotIn("__weakref__", A.__slots__) + a = A(1) + weakref.ref(a) + + def test_weakref_slot_subclass_no_weakref_slot(self): + @dataclass(slots=True, weakref_slot=True) + class Base: + field: int + + @dataclass(slots=True) + class A(Base): + ... + + # __weakref__ is in the base class, not A. Even though A doesn't + # specify weakref_slot, it should still be weakref-able. + self.assertIn("__weakref__", Base.__slots__) + self.assertNotIn("__weakref__", A.__slots__) + a = A(1) + weakref.ref(a) + + def test_weakref_slot_normal_base_weakref_slot(self): + class Base: + __slots__ = ('__weakref__',) + + @dataclass(slots=True, weakref_slot=True) + class A(Base): + field: int + + # __weakref__ is in the base class, not A. But an instance of + # A is still weakref-able. + self.assertIn("__weakref__", Base.__slots__) + self.assertNotIn("__weakref__", A.__slots__) + a = A(1) + weakref.ref(a) + class TestDescriptors(unittest.TestCase): def test_set_name(self): diff --git a/Misc/NEWS.d/next/Library/2022-06-06-13-19-43.gh-issue-93521._vE8m9.rst b/Misc/NEWS.d/next/Library/2022-06-06-13-19-43.gh-issue-93521._vE8m9.rst new file mode 100644 index 0000000..3a3ff47 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-06-06-13-19-43.gh-issue-93521._vE8m9.rst @@ -0,0 +1,4 @@ +Fixed a case where dataclasses would try to add ``__weakref__`` into the +``__slots__`` for a dataclass that specified ``weakref_slot=True`` when it was +already defined in one of its bases. This resulted in a ``TypeError`` upon the +new class being created. -- cgit v0.12