Skip to content

Commit ed1ae03

Browse files
gh-125631: Enable setting persistent_id and persistent_load of pickler and unpickler
pickle.Pickler and pickle.Unpickler instances have now managed dicts. Arbitrary instance attributes, including persistent_id and persistent_load, can now be set.
1 parent 2e950e3 commit ed1ae03

File tree

4 files changed

+41
-8
lines changed

4 files changed

+41
-8
lines changed

Lib/pickle.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -548,10 +548,11 @@ def save(self, obj, save_persistent_id=True):
548548
self.framer.commit_frame()
549549

550550
# Check for persistent id (defined by a subclass)
551-
pid = self.persistent_id(obj)
552-
if pid is not None and save_persistent_id:
553-
self.save_pers(pid)
554-
return
551+
if save_persistent_id:
552+
pid = self.persistent_id(obj)
553+
if pid is not None:
554+
self.save_pers(pid)
555+
return
555556

556557
# Check the memo
557558
x = self.memo.get(id(obj))

Lib/test/test_pickle.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,30 @@ def persistent_load(subself, pid):
244244
unpickler = PersUnpickler(io.BytesIO(self.dumps('abc', proto)))
245245
self.assertEqual(unpickler.load(), 'abc')
246246

247+
def test_pickler_instance_attribute(self):
248+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
249+
f = io.BytesIO()
250+
pickler = self.pickler(f, proto)
251+
called = []
252+
def persistent_id(obj):
253+
called.append(obj)
254+
return obj
255+
pickler.persistent_id = persistent_id
256+
pickler.dump('abc')
257+
self.assertEqual(called, ['abc'])
258+
self.assertEqual(self.loads(f.getvalue()), 'abc')
259+
260+
def test_unpickler_instance_attribute(self):
261+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
262+
unpickler = self.unpickler(io.BytesIO(self.dumps('abc', proto)))
263+
called = []
264+
def persistent_load(pid):
265+
called.append(pid)
266+
return pid
267+
unpickler.persistent_load = persistent_load
268+
self.assertEqual(unpickler.load(), 'abc')
269+
self.assertEqual(called, ['abc'])
270+
247271
class PyPicklerUnpicklerObjectTests(AbstractPicklerUnpicklerObjectTests, unittest.TestCase):
248272

249273
pickler_class = pickle._Pickler
@@ -368,17 +392,20 @@ class SizeofTests(unittest.TestCase):
368392

369393
def test_pickler(self):
370394
basesize = support.calcobjsize('6P2n3i2n3i2P')
395+
P = struct.calcsize('P')
371396
p = _pickle.Pickler(io.BytesIO())
372397
self.assertEqual(object.__sizeof__(p), basesize)
373398
MT_size = struct.calcsize('3nP0n')
374399
ME_size = struct.calcsize('Pn0P')
375400
check = self.check_sizeof
376401
check(p, basesize +
402+
2 * P + # Managed dict
377403
MT_size + 8 * ME_size + # Minimal memo table size.
378404
sys.getsizeof(b'x'*4096)) # Minimal write buffer size.
379405
for i in range(6):
380406
p.dump(chr(i))
381407
check(p, basesize +
408+
2 * P + # Managed dict
382409
MT_size + 32 * ME_size + # Size of memo table required to
383410
# save references to 6 objects.
384411
0) # Write buffer is cleared after every dump().
@@ -395,6 +422,7 @@ def test_unpickler(self):
395422
encoding=encoding, errors=errors)
396423
self.assertEqual(object.__sizeof__(u), basesize)
397424
check(u, basesize +
425+
2 * P + # Managed dict
398426
32 * P + # Minimal memo table size.
399427
len(encoding) + 1 + len(errors) + 1)
400428

@@ -404,7 +432,7 @@ def check_unpickler(data, memo_size, marks_size):
404432
u = unpickler(io.BytesIO(dump),
405433
encoding='ASCII', errors='strict')
406434
u.load()
407-
check(u, stdsize + memo_size * P + marks_size * n)
435+
check(u, stdsize + 2 * P + memo_size * P + marks_size * n)
408436

409437
check_unpickler(0, 32, 0)
410438
# 20 is minimal non-empty mark stack size.
@@ -427,7 +455,7 @@ def recurse(deep):
427455
u = unpickler(io.BytesIO(pickle.dumps('a', 0)),
428456
encoding='ASCII', errors='strict')
429457
u.load()
430-
check(u, stdsize + 32 * P + 2 + 1)
458+
check(u, stdsize + 2 * P + 32 * P + 2 + 1)
431459

432460

433461
ALT_IMPORT_MAPPING = {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Restore ability to set :attr:`~pickle.Pickler.persistent_id` and
2+
:attr:`~pickle.Unpickler.persistent_load` attributes of instances of the
3+
:class:`!Pickler` and :class:`!Unpickler` classes in the :mod:`pickle`
4+
module.

Modules/_pickle.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5120,7 +5120,7 @@ static PyType_Spec pickler_type_spec = {
51205120
.name = "_pickle.Pickler",
51215121
.basicsize = sizeof(PicklerObject),
51225122
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC |
5123-
Py_TPFLAGS_IMMUTABLETYPE),
5123+
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_MANAGED_DICT),
51245124
.slots = pickler_type_slots,
51255125
};
51265126

@@ -7585,7 +7585,7 @@ static PyType_Spec unpickler_type_spec = {
75857585
.name = "_pickle.Unpickler",
75867586
.basicsize = sizeof(UnpicklerObject),
75877587
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC |
7588-
Py_TPFLAGS_IMMUTABLETYPE),
7588+
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_MANAGED_DICT),
75897589
.slots = unpickler_type_slots,
75907590
};
75917591

0 commit comments

Comments
 (0)