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 632682c

Browse filesBrowse 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
Copy full SHA for 632682c

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
@@ -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

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
@@ -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):
+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.