Skip to content

Commit

Permalink
pythongh-125767: Fix pickling and copying of super objects
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
serhiy-storchaka committed Oct 21, 2024
1 parent ded105a commit 56b7e25
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
The :class:`super` objects are now :mod:`pickleable <pickle>` and
:mod:`copyable <copy>`.


.. _func-tuple:
.. class:: tuple()
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ Other language changes
They raise an error if the argument is a string.
(Contributed by Serhiy Storchaka in :gh:`84978`.)

* The :class:`super` objects are now :mod:`pickleable <pickle>` and
:mod:`copyable <copy>`.
(Contributed by Serhiy Storchaka in :gh:`125767`.)


New modules
===========
Expand Down
2 changes: 1 addition & 1 deletion Lib/copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def _copy_immutable(x):
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
Expand Down
5 changes: 5 additions & 0 deletions Lib/copyreg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
70 changes: 70 additions & 0 deletions Lib/test/test_super.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Unit tests for zero-argument super() & related machinery."""

import copy
import pickle
import textwrap
import threading
import unittest
Expand Down Expand Up @@ -539,6 +541,74 @@ def work():
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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The :class:`super` objects are now :mod:`pickleable <pickle>` and
:mod:`copyable <copy>`.

0 comments on commit 56b7e25

Please sign in to comment.