Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e953956

Browse filesBrowse files
[3.12] gh-118033: Fix __weakref__ not set for generic dataclasses (GH-118099) (#118822)
gh-118033: Fix `__weakref__` not set for generic dataclasses (GH-118099) (cherry picked from commit fa9b9cb) Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
1 parent 530c3bb commit e953956
Copy full SHA for e953956

File tree

3 files changed

+118
-3
lines changed
Filter options

3 files changed

+118
-3
lines changed

‎Lib/dataclasses.py

Copy file name to clipboardExpand all lines: Lib/dataclasses.py
+10-3Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,10 +1168,17 @@ def _dataclass_setstate(self, state):
11681168

11691169
def _get_slots(cls):
11701170
match cls.__dict__.get('__slots__'):
1171-
# A class which does not define __slots__ at all is equivalent
1172-
# to a class defining __slots__ = ('__dict__', '__weakref__')
1171+
# `__dictoffset__` and `__weakrefoffset__` can tell us whether
1172+
# the base type has dict/weakref slots, in a way that works correctly
1173+
# for both Python classes and C extension types. Extension types
1174+
# don't use `__slots__` for slot creation
11731175
case None:
1174-
yield from ('__dict__', '__weakref__')
1176+
slots = []
1177+
if getattr(cls, '__weakrefoffset__', -1) != 0:
1178+
slots.append('__weakref__')
1179+
if getattr(cls, '__dictrefoffset__', -1) != 0:
1180+
slots.append('__dict__')
1181+
yield from slots
11751182
case str(slot):
11761183
yield slot
11771184
# Slots may be any iterable, but we cannot handle an iterator

‎Lib/test/test_dataclasses/__init__.py

Copy file name to clipboardExpand all lines: Lib/test/test_dataclasses/__init__.py
+106Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3411,8 +3411,114 @@ class A:
34113411
class B(A):
34123412
pass
34133413

3414+
self.assertEqual(B.__slots__, ())
34143415
B()
34153416

3417+
def test_dataclass_derived_generic(self):
3418+
T = typing.TypeVar('T')
3419+
3420+
@dataclass(slots=True, weakref_slot=True)
3421+
class A(typing.Generic[T]):
3422+
pass
3423+
self.assertEqual(A.__slots__, ('__weakref__',))
3424+
self.assertTrue(A.__weakref__)
3425+
A()
3426+
3427+
@dataclass(slots=True, weakref_slot=True)
3428+
class B[T2]:
3429+
pass
3430+
self.assertEqual(B.__slots__, ('__weakref__',))
3431+
self.assertTrue(B.__weakref__)
3432+
B()
3433+
3434+
def test_dataclass_derived_generic_from_base(self):
3435+
T = typing.TypeVar('T')
3436+
3437+
class RawBase: ...
3438+
3439+
@dataclass(slots=True, weakref_slot=True)
3440+
class C1(typing.Generic[T], RawBase):
3441+
pass
3442+
self.assertEqual(C1.__slots__, ())
3443+
self.assertTrue(C1.__weakref__)
3444+
C1()
3445+
@dataclass(slots=True, weakref_slot=True)
3446+
class C2(RawBase, typing.Generic[T]):
3447+
pass
3448+
self.assertEqual(C2.__slots__, ())
3449+
self.assertTrue(C2.__weakref__)
3450+
C2()
3451+
3452+
@dataclass(slots=True, weakref_slot=True)
3453+
class D[T2](RawBase):
3454+
pass
3455+
self.assertEqual(D.__slots__, ())
3456+
self.assertTrue(D.__weakref__)
3457+
D()
3458+
3459+
def test_dataclass_derived_generic_from_slotted_base(self):
3460+
T = typing.TypeVar('T')
3461+
3462+
class WithSlots:
3463+
__slots__ = ('a', 'b')
3464+
3465+
@dataclass(slots=True, weakref_slot=True)
3466+
class E1(WithSlots, Generic[T]):
3467+
pass
3468+
self.assertEqual(E1.__slots__, ('__weakref__',))
3469+
self.assertTrue(E1.__weakref__)
3470+
E1()
3471+
@dataclass(slots=True, weakref_slot=True)
3472+
class E2(Generic[T], WithSlots):
3473+
pass
3474+
self.assertEqual(E2.__slots__, ('__weakref__',))
3475+
self.assertTrue(E2.__weakref__)
3476+
E2()
3477+
3478+
@dataclass(slots=True, weakref_slot=True)
3479+
class F[T2](WithSlots):
3480+
pass
3481+
self.assertEqual(F.__slots__, ('__weakref__',))
3482+
self.assertTrue(F.__weakref__)
3483+
F()
3484+
3485+
def test_dataclass_derived_generic_from_slotted_base(self):
3486+
T = typing.TypeVar('T')
3487+
3488+
class WithWeakrefSlot:
3489+
__slots__ = ('__weakref__',)
3490+
3491+
@dataclass(slots=True, weakref_slot=True)
3492+
class G1(WithWeakrefSlot, Generic[T]):
3493+
pass
3494+
self.assertEqual(G1.__slots__, ())
3495+
self.assertTrue(G1.__weakref__)
3496+
G1()
3497+
@dataclass(slots=True, weakref_slot=True)
3498+
class G2(Generic[T], WithWeakrefSlot):
3499+
pass
3500+
self.assertEqual(G2.__slots__, ())
3501+
self.assertTrue(G2.__weakref__)
3502+
G2()
3503+
3504+
@dataclass(slots=True, weakref_slot=True)
3505+
class H[T2](WithWeakrefSlot):
3506+
pass
3507+
self.assertEqual(H.__slots__, ())
3508+
self.assertTrue(H.__weakref__)
3509+
H()
3510+
3511+
def test_dataclass_slot_dict(self):
3512+
class WithDictSlot:
3513+
__slots__ = ('__dict__',)
3514+
3515+
@dataclass(slots=True)
3516+
class A(WithDictSlot): ...
3517+
3518+
self.assertEqual(A.__slots__, ())
3519+
self.assertEqual(A().__dict__, {})
3520+
A()
3521+
34163522

34173523
class TestDescriptors(unittest.TestCase):
34183524
def test_set_name(self):
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :func:`dataclasses.dataclass` not creating a ``__weakref__`` slot when
2+
subclassing :class:`typing.Generic`.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.