From: Eric V. Smith Date: Sat, 1 May 2021 17:27:30 +0000 (-0400) Subject: If using a frozen class with slots, add __getstate__ and __setstate__ to set the... X-Git-Tag: v3.10.0b1~53 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=823fbf4e0eb66cbef0eacb7e8dbfb5dc8ea83b40;p=thirdparty%2FPython%2Fcpython.git If using a frozen class with slots, add __getstate__ and __setstate__ to set the instance values. (GH-25786) --- diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 5e5716316f09..363d0b66d208 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1087,14 +1087,28 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen, tuple(f.name for f in std_init_fields)) if slots: - cls = _add_slots(cls) + cls = _add_slots(cls, frozen) abc.update_abstractmethods(cls) return cls -def _add_slots(cls): +# _dataclass_getstate and _dataclass_setstate are needed for pickling frozen +# classes with slots. These could be slighly more performant if we generated +# the code instead of iterating over fields. But that can be a project for +# another day, if performance becomes an issue. +def _dataclass_getstate(self): + return [getattr(self, f.name) for f in fields(self)] + + +def _dataclass_setstate(self, state): + for field, value in zip(fields(self), state): + # use setattr because dataclass may be frozen + object.__setattr__(self, field.name, value) + + +def _add_slots(cls, is_frozen): # Need to create a new class, since we can't set __slots__ # after a class has been created. @@ -1120,6 +1134,11 @@ def _add_slots(cls): if qualname is not None: cls.__qualname__ = qualname + if is_frozen: + # Need this for pickling frozen classes with slots. + cls.__getstate__ = _dataclass_getstate + cls.__setstate__ = _dataclass_setstate + return cls diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index 2fa0ae0126bf..16ee4c7705d8 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -2833,6 +2833,19 @@ class TestSlots(unittest.TestCase): self.assertFalse(hasattr(A, "__slots__")) self.assertTrue(hasattr(B, "__slots__")) + # Can't be local to test_frozen_pickle. + @dataclass(frozen=True, slots=True) + class FrozenSlotsClass: + foo: str + bar: int + + def test_frozen_pickle(self): + # bpo-43999 + + assert self.FrozenSlotsClass.__slots__ == ("foo", "bar") + p = pickle.dumps(self.FrozenSlotsClass("a", 1)) + assert pickle.loads(p) == self.FrozenSlotsClass("a", 1) + class TestDescriptors(unittest.TestCase): def test_set_name(self):