]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-125767: Fix pickling and copying of super objects (GH-125781)
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 21 Oct 2024 18:30:45 +0000 (21:30 +0300)
committerGitHub <noreply@github.com>
Mon, 21 Oct 2024 18:30:45 +0000 (21:30 +0300)
Previously, copying a super object returned a copy of the instance
invoking super(). Pickling a super object could pickle the instance
invoking super() or fail, depending on its type and protocol.

Now deep copying returns a new super object and pickling pickles the super
object. Shallow copying returns the same super object.

Doc/library/functions.rst
Doc/whatsnew/3.14.rst
Lib/copy.py
Lib/copyreg.py
Lib/test/test_super.py
Misc/NEWS.d/next/Library/2024-10-21-13-52-37.gh-issue-125767.0kK4lX.rst [new file with mode: 0644]

index 0638df04c6ff407e15af668b4ead958b3eaa0d92..290c63827ff76653cbee660c5dd9e7fa02ca0617 100644 (file)
@@ -2032,6 +2032,10 @@ are always available.  They are listed here in alphabetical order.
    :func:`super`, see `guide to using super()
    <https://rhettinger.wordpress.com/2011/05/26/super-considered-super/>`_.
 
+   .. versionchanged:: 3.14
+     :class:`super` objects are now :mod:`pickleable <pickle>` and
+      :mod:`copyable <copy>`.
+
 
 .. _func-tuple:
 .. class:: tuple()
index ad841538ccc547ba5bb0e13e82d237282da8e34e..d52faa614db94e5a14ac348982183399d8a139e9 100644 (file)
@@ -190,6 +190,10 @@ Other language changes
   They raise an error if the argument is a string.
   (Contributed by Serhiy Storchaka in :gh:`84978`.)
 
+* :class:`super` objects are now :mod:`pickleable <pickle>` and
+  :mod:`copyable <copy>`.
+  (Contributed by Serhiy Storchaka in :gh:`125767`.)
+
 
 New modules
 ===========
index a79976d3a658f05efad54f1d3570113739063fc6..f27e109973cfb728bdd741cc736156328a62cfc0 100644 (file)
@@ -106,7 +106,7 @@ for t in (types.NoneType, int, float, bool, complex, str, tuple,
           bytes, frozenset, type, range, slice, property,
           types.BuiltinFunctionType, types.EllipsisType,
           types.NotImplementedType, types.FunctionType, types.CodeType,
-          weakref.ref):
+          weakref.ref, super):
     d[t] = _copy_immutable
 
 d[list] = list.copy
index 578392409b403c0a07a81c2dfdaa1e35971785b1..17c5dde67c887cd125492cde1504c69c5f3238e4 100644 (file)
@@ -36,6 +36,11 @@ def pickle_union(obj):
 
 pickle(type(int | str), pickle_union)
 
+def pickle_super(obj):
+    return super, (obj.__thisclass__, obj.__self__)
+
+pickle(super, pickle_super)
+
 # Support for pickling new-style objects
 
 def _reconstructor(cls, base, state):
index 1222ec6a3c410972093840c2e4f895c5e58b5c57..149016635522c3f585e1c7474e95fddcb1a16199 100644 (file)
@@ -1,5 +1,7 @@
 """Unit tests for zero-argument super() & related machinery."""
 
+import copy
+import pickle
 import textwrap
 import threading
 import unittest
@@ -539,6 +541,74 @@ class TestSuper(unittest.TestCase):
         for thread in threads:
             thread.join()
 
+    def test_special_methods(self):
+        for e in E(), E:
+            s = super(C, e)
+            self.assertEqual(s.__reduce__, e.__reduce__)
+            self.assertEqual(s.__reduce_ex__, e.__reduce_ex__)
+            self.assertEqual(s.__getstate__, e.__getstate__)
+            self.assertFalse(hasattr(s, '__getnewargs__'))
+            self.assertFalse(hasattr(s, '__getnewargs_ex__'))
+            self.assertFalse(hasattr(s, '__setstate__'))
+            self.assertFalse(hasattr(s, '__copy__'))
+            self.assertFalse(hasattr(s, '__deepcopy__'))
+
+    def test_pickling(self):
+        e = E()
+        e.x = 1
+        s = super(C, e)
+        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+            with self.subTest(proto=proto):
+                u = pickle.loads(pickle.dumps(s, proto))
+                self.assertEqual(u.f(), s.f())
+                self.assertIs(type(u), type(s))
+                self.assertIs(type(u.__self__), E)
+                self.assertEqual(u.__self__.x, 1)
+                self.assertIs(u.__thisclass__, C)
+                self.assertIs(u.__self_class__, E)
+
+        s = super(C, E)
+        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+            with self.subTest(proto=proto):
+                u = pickle.loads(pickle.dumps(s, proto))
+                self.assertEqual(u.cm(), s.cm())
+                self.assertEqual(u.f, s.f)
+                self.assertIs(type(u), type(s))
+                self.assertIs(u.__self__, E)
+                self.assertIs(u.__thisclass__, C)
+                self.assertIs(u.__self_class__, E)
+
+    def test_shallow_copying(self):
+        s = super(C, E())
+        self.assertIs(copy.copy(s), s)
+        s = super(C, E)
+        self.assertIs(copy.copy(s), s)
+
+    def test_deep_copying(self):
+        e = E()
+        e.x = [1]
+        s = super(C, e)
+        u = copy.deepcopy(s)
+        self.assertEqual(u.f(), s.f())
+        self.assertIs(type(u), type(s))
+        self.assertIsNot(u, s)
+        self.assertIs(type(u.__self__), E)
+        self.assertIsNot(u.__self__, e)
+        self.assertIsNot(u.__self__.x, e.x)
+        self.assertEqual(u.__self__.x, [1])
+        self.assertIs(u.__thisclass__, C)
+        self.assertIs(u.__self_class__, E)
+
+        s = super(C, E)
+        u = copy.deepcopy(s)
+        self.assertEqual(u.cm(), s.cm())
+        self.assertEqual(u.f, s.f)
+        self.assertIsNot(u, s)
+        self.assertIs(type(u), type(s))
+        self.assertIs(u.__self__, E)
+        self.assertIs(u.__thisclass__, C)
+        self.assertIs(u.__self_class__, E)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2024-10-21-13-52-37.gh-issue-125767.0kK4lX.rst b/Misc/NEWS.d/next/Library/2024-10-21-13-52-37.gh-issue-125767.0kK4lX.rst
new file mode 100644 (file)
index 0000000..bfda740
--- /dev/null
@@ -0,0 +1,2 @@
+:class:`super` objects are now :mod:`pickleable <pickle>` and
+:mod:`copyable <copy>`.