Skip to content

Commit 5ca4e34

Browse files
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.
1 parent de5a6c7 commit 5ca4e34

File tree

6 files changed

+86
-1
lines changed

6 files changed

+86
-1
lines changed

Doc/library/functions.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2032,6 +2032,10 @@ are always available. They are listed here in alphabetical order.
20322032
:func:`super`, see `guide to using super()
20332033
<https://rhettinger.wordpress.com/2011/05/26/super-considered-super/>`_.
20342034

2035+
.. versionchanged:: 3.14
2036+
:class:`super` objects are now :mod:`pickleable <pickle>` and
2037+
:mod:`copyable <copy>`.
2038+
20352039

20362040
.. _func-tuple:
20372041
.. class:: tuple()

Doc/whatsnew/3.14.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ Other language changes
190190
They raise an error if the argument is a string.
191191
(Contributed by Serhiy Storchaka in :gh:`84978`.)
192192

193+
* :class:`super` objects are now :mod:`pickleable <pickle>` and
194+
:mod:`copyable <copy>`.
195+
(Contributed by Serhiy Storchaka in :gh:`125767`.)
196+
193197

194198
New modules
195199
===========

Lib/copy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def _copy_immutable(x):
106106
bytes, frozenset, type, range, slice, property,
107107
types.BuiltinFunctionType, types.EllipsisType,
108108
types.NotImplementedType, types.FunctionType, types.CodeType,
109-
weakref.ref):
109+
weakref.ref, super):
110110
d[t] = _copy_immutable
111111

112112
d[list] = list.copy

Lib/copyreg.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ def pickle_union(obj):
3636

3737
pickle(type(int | str), pickle_union)
3838

39+
def pickle_super(obj):
40+
return super, (obj.__thisclass__, obj.__self__)
41+
42+
pickle(super, pickle_super)
43+
3944
# Support for pickling new-style objects
4045

4146
def _reconstructor(cls, base, state):

Lib/test/test_super.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Unit tests for zero-argument super() & related machinery."""
22

3+
import copy
4+
import pickle
35
import textwrap
46
import threading
57
import unittest
@@ -539,6 +541,74 @@ def work():
539541
for thread in threads:
540542
thread.join()
541543

544+
def test_special_methods(self):
545+
for e in E(), E:
546+
s = super(C, e)
547+
self.assertEqual(s.__reduce__, e.__reduce__)
548+
self.assertEqual(s.__reduce_ex__, e.__reduce_ex__)
549+
self.assertEqual(s.__getstate__, e.__getstate__)
550+
self.assertFalse(hasattr(s, '__getnewargs__'))
551+
self.assertFalse(hasattr(s, '__getnewargs_ex__'))
552+
self.assertFalse(hasattr(s, '__setstate__'))
553+
self.assertFalse(hasattr(s, '__copy__'))
554+
self.assertFalse(hasattr(s, '__deepcopy__'))
555+
556+
def test_pickling(self):
557+
e = E()
558+
e.x = 1
559+
s = super(C, e)
560+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
561+
with self.subTest(proto=proto):
562+
u = pickle.loads(pickle.dumps(s, proto))
563+
self.assertEqual(u.f(), s.f())
564+
self.assertIs(type(u), type(s))
565+
self.assertIs(type(u.__self__), E)
566+
self.assertEqual(u.__self__.x, 1)
567+
self.assertIs(u.__thisclass__, C)
568+
self.assertIs(u.__self_class__, E)
569+
570+
s = super(C, E)
571+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
572+
with self.subTest(proto=proto):
573+
u = pickle.loads(pickle.dumps(s, proto))
574+
self.assertEqual(u.cm(), s.cm())
575+
self.assertEqual(u.f, s.f)
576+
self.assertIs(type(u), type(s))
577+
self.assertIs(u.__self__, E)
578+
self.assertIs(u.__thisclass__, C)
579+
self.assertIs(u.__self_class__, E)
580+
581+
def test_shallow_copying(self):
582+
s = super(C, E())
583+
self.assertIs(copy.copy(s), s)
584+
s = super(C, E)
585+
self.assertIs(copy.copy(s), s)
586+
587+
def test_deep_copying(self):
588+
e = E()
589+
e.x = [1]
590+
s = super(C, e)
591+
u = copy.deepcopy(s)
592+
self.assertEqual(u.f(), s.f())
593+
self.assertIs(type(u), type(s))
594+
self.assertIsNot(u, s)
595+
self.assertIs(type(u.__self__), E)
596+
self.assertIsNot(u.__self__, e)
597+
self.assertIsNot(u.__self__.x, e.x)
598+
self.assertEqual(u.__self__.x, [1])
599+
self.assertIs(u.__thisclass__, C)
600+
self.assertIs(u.__self_class__, E)
601+
602+
s = super(C, E)
603+
u = copy.deepcopy(s)
604+
self.assertEqual(u.cm(), s.cm())
605+
self.assertEqual(u.f, s.f)
606+
self.assertIsNot(u, s)
607+
self.assertIs(type(u), type(s))
608+
self.assertIs(u.__self__, E)
609+
self.assertIs(u.__thisclass__, C)
610+
self.assertIs(u.__self_class__, E)
611+
542612

543613
if __name__ == "__main__":
544614
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:class:`super` objects are now :mod:`pickleable <pickle>` and
2+
:mod:`copyable <copy>`.

0 commit comments

Comments
 (0)