diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2024-10-21 18:30:45 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-10-21 18:30:45 (GMT) |
commit | 5ca4e34bc1aab8321911aac6d5b2b9e75ff764d8 (patch) | |
tree | d4f4dc4e80de809d695247c749a3ac50340ef78e | |
parent | de5a6c7c7d00ac37d66cba9849202b374e9cdfb7 (diff) | |
download | cpython-5ca4e34bc1aab8321911aac6d5b2b9e75ff764d8.zip cpython-5ca4e34bc1aab8321911aac6d5b2b9e75ff764d8.tar.gz cpython-5ca4e34bc1aab8321911aac6d5b2b9e75ff764d8.tar.bz2 |
gh-125767: Fix pickling and copying of super objects (GH-125781)
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.
-rw-r--r-- | Doc/library/functions.rst | 4 | ||||
-rw-r--r-- | Doc/whatsnew/3.14.rst | 4 | ||||
-rw-r--r-- | Lib/copy.py | 2 | ||||
-rw-r--r-- | Lib/copyreg.py | 5 | ||||
-rw-r--r-- | Lib/test/test_super.py | 70 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2024-10-21-13-52-37.gh-issue-125767.0kK4lX.rst | 2 |
6 files changed, 86 insertions, 1 deletions
diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index 0638df0..290c638 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -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() diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index ad84153..d52faa6 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -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 =========== diff --git a/Lib/copy.py b/Lib/copy.py index a79976d..f27e109 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -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 diff --git a/Lib/copyreg.py b/Lib/copyreg.py index 5783924..17c5dde 100644 --- a/Lib/copyreg.py +++ b/Lib/copyreg.py @@ -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): diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index 1222ec6..1490166 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -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 index 0000000..bfda740 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-21-13-52-37.gh-issue-125767.0kK4lX.rst @@ -0,0 +1,2 @@ +:class:`super` objects are now :mod:`pickleable <pickle>` and +:mod:`copyable <copy>`. |