Skip to content

Commit 632682c

Browse files
[3.13] gh-118033: Fix __weakref__ not set for generic dataclasses (GH-118099) (#118821)
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 09896fc commit 632682c

File tree

3 files changed

+118
-3
lines changed

3 files changed

+118
-3
lines changed

Lib/dataclasses.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -1199,10 +1199,17 @@ def _dataclass_setstate(self, state):
11991199

12001200
def _get_slots(cls):
12011201
match cls.__dict__.get('__slots__'):
1202-
# A class which does not define __slots__ at all is equivalent
1203-
# to a class defining __slots__ = ('__dict__', '__weakref__')
1202+
# `__dictoffset__` and `__weakrefoffset__` can tell us whether
1203+
# the base type has dict/weakref slots, in a way that works correctly
1204+
# for both Python classes and C extension types. Extension types
1205+
# don't use `__slots__` for slot creation
12041206
case None:
1205-
yield from ('__dict__', '__weakref__')
1207+
slots = []
1208+
if getattr(cls, '__weakrefoffset__', -1) != 0:
1209+
slots.append('__weakref__')
1210+
if getattr(cls, '__dictrefoffset__', -1) != 0:
1211+
slots.append('__dict__')
1212+
yield from slots
12061213
case str(slot):
12071214
yield slot
12081215
# Slots may be any iterable, but we cannot handle an iterator

Lib/test/test_dataclasses/__init__.py

+106
Original file line numberDiff line numberDiff line change
@@ -3515,8 +3515,114 @@ class A:
35153515
class B(A):
35163516
pass
35173517

3518+
self.assertEqual(B.__slots__, ())
35183519
B()
35193520

3521+
def test_dataclass_derived_generic(self):
3522+
T = typing.TypeVar('T')
3523+
3524+
@dataclass(slots=True, weakref_slot=True)
3525+
class A(typing.Generic[T]):
3526+
pass
3527+
self.assertEqual(A.__slots__, ('__weakref__',))
3528+
self.assertTrue(A.__weakref__)
3529+
A()
3530+
3531+
@dataclass(slots=True, weakref_slot=True)
3532+
class B[T2]:
3533+
pass
3534+
self.assertEqual(B.__slots__, ('__weakref__',))
3535+
self.assertTrue(B.__weakref__)
3536+
B()
3537+
3538+
def test_dataclass_derived_generic_from_base(self):
3539+
T = typing.TypeVar('T')
3540+
3541+
class RawBase: ...
3542+
3543+
@dataclass(slots=True, weakref_slot=True)
3544+
class C1(typing.Generic[T], RawBase):
3545+
pass
3546+
self.assertEqual(C1.__slots__, ())
3547+
self.assertTrue(C1.__weakref__)
3548+
C1()
3549+
@dataclass(slots=True, weakref_slot=True)
3550+
class C2(RawBase, typing.Generic[T]):
3551+
pass
3552+
self.assertEqual(C2.__slots__, ())
3553+
self.assertTrue(C2.__weakref__)
3554+
C2()
3555+
3556+
@dataclass(slots=True, weakref_slot=True)
3557+
class D[T2](RawBase):
3558+
pass
3559+
self.assertEqual(D.__slots__, ())
3560+
self.assertTrue(D.__weakref__)
3561+
D()
3562+
3563+
def test_dataclass_derived_generic_from_slotted_base(self):
3564+
T = typing.TypeVar('T')
3565+
3566+
class WithSlots:
3567+
__slots__ = ('a', 'b')
3568+
3569+
@dataclass(slots=True, weakref_slot=True)
3570+
class E1(WithSlots, Generic[T]):
3571+
pass
3572+
self.assertEqual(E1.__slots__, ('__weakref__',))
3573+
self.assertTrue(E1.__weakref__)
3574+
E1()
3575+
@dataclass(slots=True, weakref_slot=True)
3576+
class E2(Generic[T], WithSlots):
3577+
pass
3578+
self.assertEqual(E2.__slots__, ('__weakref__',))
3579+
self.assertTrue(E2.__weakref__)
3580+
E2()
3581+
3582+
@dataclass(slots=True, weakref_slot=True)
3583+
class F[T2](WithSlots):
3584+
pass
3585+
self.assertEqual(F.__slots__, ('__weakref__',))
3586+
self.assertTrue(F.__weakref__)
3587+
F()
3588+
3589+
def test_dataclass_derived_generic_from_slotted_base(self):
3590+
T = typing.TypeVar('T')
3591+
3592+
class WithWeakrefSlot:
3593+
__slots__ = ('__weakref__',)
3594+
3595+
@dataclass(slots=True, weakref_slot=True)
3596+
class G1(WithWeakrefSlot, Generic[T]):
3597+
pass
3598+
self.assertEqual(G1.__slots__, ())
3599+
self.assertTrue(G1.__weakref__)
3600+
G1()
3601+
@dataclass(slots=True, weakref_slot=True)
3602+
class G2(Generic[T], WithWeakrefSlot):
3603+
pass
3604+
self.assertEqual(G2.__slots__, ())
3605+
self.assertTrue(G2.__weakref__)
3606+
G2()
3607+
3608+
@dataclass(slots=True, weakref_slot=True)
3609+
class H[T2](WithWeakrefSlot):
3610+
pass
3611+
self.assertEqual(H.__slots__, ())
3612+
self.assertTrue(H.__weakref__)
3613+
H()
3614+
3615+
def test_dataclass_slot_dict(self):
3616+
class WithDictSlot:
3617+
__slots__ = ('__dict__',)
3618+
3619+
@dataclass(slots=True)
3620+
class A(WithDictSlot): ...
3621+
3622+
self.assertEqual(A.__slots__, ())
3623+
self.assertEqual(A().__dict__, {})
3624+
A()
3625+
35203626

35213627
class TestDescriptors(unittest.TestCase):
35223628
def test_set_name(self):
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)