Skip to content

Commit 89cee94

Browse files
pythongh-89850: Add default C implementations of persistent_id() and persistent_load() (pythonGH-113579)
Previously the C implementation of pickle.Pickler and pickle.Unpickler classes did not have such methods and they could only be used if they were overloaded in subclasses or set as instance attributes. Fixed calling super().persistent_id() and super().persistent_load() in subclasses of the C implementation of pickle.Pickler and pickle.Unpickler classes. It no longer causes an infinite recursion.
1 parent b3d2427 commit 89cee94

File tree

5 files changed

+190
-214
lines changed

5 files changed

+190
-214
lines changed

Doc/library/pickle.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,10 @@ The :mod:`pickle` module exports three classes, :class:`Pickler`,
345345

346346
See :ref:`pickle-persistent` for details and examples of uses.
347347

348+
.. versionchanged:: 3.13
349+
Add the default implementation of this method in the C implementation
350+
of :class:`!Pickler`.
351+
348352
.. attribute:: dispatch_table
349353

350354
A pickler object's dispatch table is a registry of *reduction
@@ -446,6 +450,10 @@ The :mod:`pickle` module exports three classes, :class:`Pickler`,
446450

447451
See :ref:`pickle-persistent` for details and examples of uses.
448452

453+
.. versionchanged:: 3.13
454+
Add the default implementation of this method in the C implementation
455+
of :class:`!Unpickler`.
456+
449457
.. method:: find_class(module, name)
450458

451459
Import *module* if necessary and return the object called *name* from it,

Lib/test/test_pickle.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ class PyIdPersPicklerTests(AbstractIdentityPersistentPicklerTests,
122122

123123
pickler = pickle._Pickler
124124
unpickler = pickle._Unpickler
125+
persistent_load_error = pickle.UnpicklingError
125126

126127
@support.cpython_only
127128
def test_pickler_reference_cycle(self):
@@ -176,7 +177,6 @@ class DispatchTable:
176177
support.gc_collect()
177178
self.assertIsNone(table_ref())
178179

179-
180180
@support.cpython_only
181181
def test_unpickler_reference_cycle(self):
182182
def check(Unpickler):
@@ -206,6 +206,28 @@ def persistent_load(pid):
206206
return pid
207207
check(PersUnpickler)
208208

209+
def test_pickler_super(self):
210+
class PersPickler(self.pickler):
211+
def persistent_id(subself, obj):
212+
self.assertIsNone(super().persistent_id(obj))
213+
return obj
214+
215+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
216+
f = io.BytesIO()
217+
pickler = PersPickler(f, proto)
218+
pickler.dump('abc')
219+
self.assertEqual(self.loads(f.getvalue()), 'abc')
220+
221+
def test_unpickler_super(self):
222+
class PersUnpickler(self.unpickler):
223+
def persistent_load(subself, pid):
224+
with self.assertRaises(self.persistent_load_error):
225+
super().persistent_load(pid)
226+
return pid
227+
228+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
229+
unpickler = PersUnpickler(io.BytesIO(self.dumps('abc', proto)))
230+
self.assertEqual(unpickler.load(), 'abc')
209231

210232
class PyPicklerUnpicklerObjectTests(AbstractPicklerUnpicklerObjectTests, unittest.TestCase):
211233

@@ -256,6 +278,7 @@ class CPersPicklerTests(PyPersPicklerTests):
256278
class CIdPersPicklerTests(PyIdPersPicklerTests):
257279
pickler = _pickle.Pickler
258280
unpickler = _pickle.Unpickler
281+
persistent_load_error = _pickle.UnpicklingError
259282

260283
class CDumpPickle_LoadPickle(PyPicklerTests):
261284
pickler = _pickle.Pickler
@@ -326,7 +349,7 @@ class SizeofTests(unittest.TestCase):
326349
check_sizeof = support.check_sizeof
327350

328351
def test_pickler(self):
329-
basesize = support.calcobjsize('7P2n3i2n3i2P')
352+
basesize = support.calcobjsize('6P2n3i2n3i2P')
330353
p = _pickle.Pickler(io.BytesIO())
331354
self.assertEqual(object.__sizeof__(p), basesize)
332355
MT_size = struct.calcsize('3nP0n')
@@ -343,7 +366,7 @@ def test_pickler(self):
343366
0) # Write buffer is cleared after every dump().
344367

345368
def test_unpickler(self):
346-
basesize = support.calcobjsize('2P2n2P 2P2n2i5P 2P3n8P2n2i')
369+
basesize = support.calcobjsize('2P2nP 2P2n2i5P 2P3n8P2n2i')
347370
unpickler = _pickle.Unpickler
348371
P = struct.calcsize('P') # Size of memo table entry.
349372
n = struct.calcsize('n') # Size of mark table entry.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Add default implementations of :meth:`pickle.Pickler.persistent_id` and
2+
:meth:`pickle.Unpickler.persistent_load` methods in the C implementation.
3+
Calling ``super().persistent_id()`` and ``super().persistent_load()`` in
4+
subclasses of the C implementation of :class:`pickle.Pickler` and
5+
:class:`pickle.Unpickler` classes no longer causes infinite recursion.

0 commit comments

Comments
 (0)