]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.11] gh-104035: Do not ignore user-defined `__{get,set}state__` in slotted frozen...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 1 May 2023 16:02:41 +0000 (09:02 -0700)
committerGitHub <noreply@github.com>
Mon, 1 May 2023 16:02:41 +0000 (10:02 -0600)
gh-104035: Do not ignore user-defined `__{get,set}state__` in slotted frozen dataclasses (GH-104041)
(cherry picked from commit 99aab610622fc4b4c4fe56b77c0760cf77066a53)

Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
Lib/dataclasses.py
Lib/test/test_dataclasses.py
Misc/NEWS.d/next/Library/2023-05-01-16-43-28.gh-issue-104035.MrJBw8.rst [new file with mode: 0644]

index a7e0547705ca2823526457b50ca3327ce677546f..148daf45330c03a3703e88be40263a71cad9c845 100644 (file)
@@ -1191,8 +1191,10 @@ def _add_slots(cls, is_frozen, weakref_slot):
 
     if is_frozen:
         # Need this for pickling frozen classes with slots.
-        cls.__getstate__ = _dataclass_getstate
-        cls.__setstate__ = _dataclass_setstate
+        if '__getstate__' not in cls_dict:
+            cls.__getstate__ = _dataclass_getstate
+        if '__setstate__' not in cls_dict:
+            cls.__setstate__ = _dataclass_setstate
 
     return cls
 
index 1aaa78db4b0af880c4259b5de81d0a95cdc4a85b..4714d0bca8611431b92a57aefcca452e4e77ce68 100644 (file)
@@ -3068,6 +3068,74 @@ class TestSlots(unittest.TestCase):
                 self.assertIsNot(obj, p)
                 self.assertEqual(obj, p)
 
+    @dataclass(frozen=True, slots=True)
+    class FrozenSlotsGetStateClass:
+        foo: str
+        bar: int
+
+        getstate_called: bool = field(default=False, compare=False)
+
+        def __getstate__(self):
+            object.__setattr__(self, 'getstate_called', True)
+            return [self.foo, self.bar]
+
+    @dataclass(frozen=True, slots=True)
+    class FrozenSlotsSetStateClass:
+        foo: str
+        bar: int
+
+        setstate_called: bool = field(default=False, compare=False)
+
+        def __setstate__(self, state):
+            object.__setattr__(self, 'setstate_called', True)
+            object.__setattr__(self, 'foo', state[0])
+            object.__setattr__(self, 'bar', state[1])
+
+    @dataclass(frozen=True, slots=True)
+    class FrozenSlotsAllStateClass:
+        foo: str
+        bar: int
+
+        getstate_called: bool = field(default=False, compare=False)
+        setstate_called: bool = field(default=False, compare=False)
+
+        def __getstate__(self):
+            object.__setattr__(self, 'getstate_called', True)
+            return [self.foo, self.bar]
+
+        def __setstate__(self, state):
+            object.__setattr__(self, 'setstate_called', True)
+            object.__setattr__(self, 'foo', state[0])
+            object.__setattr__(self, 'bar', state[1])
+
+    def test_frozen_slots_pickle_custom_state(self):
+        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+            with self.subTest(proto=proto):
+                obj = self.FrozenSlotsGetStateClass('a', 1)
+                dumped = pickle.dumps(obj, protocol=proto)
+
+                self.assertTrue(obj.getstate_called)
+                self.assertEqual(obj, pickle.loads(dumped))
+
+        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+            with self.subTest(proto=proto):
+                obj = self.FrozenSlotsSetStateClass('a', 1)
+                obj2 = pickle.loads(pickle.dumps(obj, protocol=proto))
+
+                self.assertTrue(obj2.setstate_called)
+                self.assertEqual(obj, obj2)
+
+        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+            with self.subTest(proto=proto):
+                obj = self.FrozenSlotsAllStateClass('a', 1)
+                dumped = pickle.dumps(obj, protocol=proto)
+
+                self.assertTrue(obj.getstate_called)
+
+                obj2 = pickle.loads(dumped)
+                self.assertTrue(obj2.setstate_called)
+                self.assertEqual(obj, obj2)
+
     def test_slots_with_default_no_init(self):
         # Originally reported in bpo-44649.
         @dataclass(slots=True)
diff --git a/Misc/NEWS.d/next/Library/2023-05-01-16-43-28.gh-issue-104035.MrJBw8.rst b/Misc/NEWS.d/next/Library/2023-05-01-16-43-28.gh-issue-104035.MrJBw8.rst
new file mode 100644 (file)
index 0000000..8c8e3d6
--- /dev/null
@@ -0,0 +1,2 @@
+Do not ignore user-defined ``__getstate__`` and ``__setstate__`` methods for
+slotted frozen dataclasses.