Skip to content

Commit

Permalink
GH-93521: For dataclasses, filter out __weakref__ slot if present i…
Browse files Browse the repository at this point in the history
…n bases (GH-93535)

(cherry picked from commit 5849af7)

Co-authored-by: Bluenix <bluenixdev@gmail.com>
  • Loading branch information
miss-islington and Bluenix2 authored Jun 8, 2022
1 parent f26d1b5 commit 121ab58
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 4 deletions.
13 changes: 9 additions & 4 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
48 changes: 48 additions & 0 deletions Lib/test/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3109,6 +3109,54 @@ def test_weakref_slot_make_dataclass(self):
"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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit 121ab58

Please sign in to comment.