Skip to content

Commit 823fbf4

Browse files
authored
If using a frozen class with slots, add __getstate__ and __setstate__ to set the instance values. (GH-25786)
1 parent f82fd77 commit 823fbf4

File tree

2 files changed

+34
-2
lines changed

2 files changed

+34
-2
lines changed

Lib/dataclasses.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,14 +1087,28 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
10871087
tuple(f.name for f in std_init_fields))
10881088

10891089
if slots:
1090-
cls = _add_slots(cls)
1090+
cls = _add_slots(cls, frozen)
10911091

10921092
abc.update_abstractmethods(cls)
10931093

10941094
return cls
10951095

10961096

1097-
def _add_slots(cls):
1097+
# _dataclass_getstate and _dataclass_setstate are needed for pickling frozen
1098+
# classes with slots. These could be slighly more performant if we generated
1099+
# the code instead of iterating over fields. But that can be a project for
1100+
# another day, if performance becomes an issue.
1101+
def _dataclass_getstate(self):
1102+
return [getattr(self, f.name) for f in fields(self)]
1103+
1104+
1105+
def _dataclass_setstate(self, state):
1106+
for field, value in zip(fields(self), state):
1107+
# use setattr because dataclass may be frozen
1108+
object.__setattr__(self, field.name, value)
1109+
1110+
1111+
def _add_slots(cls, is_frozen):
10981112
# Need to create a new class, since we can't set __slots__
10991113
# after a class has been created.
11001114

@@ -1120,6 +1134,11 @@ def _add_slots(cls):
11201134
if qualname is not None:
11211135
cls.__qualname__ = qualname
11221136

1137+
if is_frozen:
1138+
# Need this for pickling frozen classes with slots.
1139+
cls.__getstate__ = _dataclass_getstate
1140+
cls.__setstate__ = _dataclass_setstate
1141+
11231142
return cls
11241143

11251144

Lib/test/test_dataclasses.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2833,6 +2833,19 @@ class A:
28332833
self.assertFalse(hasattr(A, "__slots__"))
28342834
self.assertTrue(hasattr(B, "__slots__"))
28352835

2836+
# Can't be local to test_frozen_pickle.
2837+
@dataclass(frozen=True, slots=True)
2838+
class FrozenSlotsClass:
2839+
foo: str
2840+
bar: int
2841+
2842+
def test_frozen_pickle(self):
2843+
# bpo-43999
2844+
2845+
assert self.FrozenSlotsClass.__slots__ == ("foo", "bar")
2846+
p = pickle.dumps(self.FrozenSlotsClass("a", 1))
2847+
assert pickle.loads(p) == self.FrozenSlotsClass("a", 1)
2848+
28362849

28372850
class TestDescriptors(unittest.TestCase):
28382851
def test_set_name(self):

0 commit comments

Comments
 (0)