Skip to main content

Lib/pickle.py (part 5)

Source:

cpython 3.14 @ ab2d84fe1023/Lib/pickle.py

This annotation covers the __reduce__ protocol and customization hooks. See lib_pickle4_detail for save_list, save_dict, save_bytes, and protocol 5.

Map

LinesSymbolRole
1-80Pickler.save_reduceSerialize an object using __reduce__ or __reduce_ex__
81-160Unpickler.load_reduceExecute REDUCE opcode: call callable(*args)
161-240Pickler.persistent_idHook for persistent external references
241-320dispatch_tablePer-pickler copyreg-style type→reduce mapping
321-500copyreg.pickle / copyreg.dispatch_tableGlobal type registration

Reading

Pickler.save_reduce

# CPython: Lib/pickle.py:420 Pickler.save_reduce
def save_reduce(self, func, args, state=None, listitems=None,
dictitems=None, state_setter=None, obj=None):
save = self.save
write = self.write
if self.proto >= 2:
code = _extension_registry.get((func.__module__, func.__qualname__))
if code:
...
write(EXT1 + pack('<B', code))
return
save(func)
save(args)
write(REDUCE)
if obj is not None:
self.memoize(obj)
if state is not None:
save(state)
write(BUILD)
if listitems is not None:
self._batch_appends(listitems)
if dictitems is not None:
self._batch_setitems(dictitems)

save_reduce serializes func and args, then emits REDUCE. On load, REDUCE calls func(*args). state is applied via __setstate__ (through BUILD). listitems/dictitems are used for list and dict subclasses.

Unpickler.load_reduce

# CPython: Lib/pickle.py:1240 Unpickler.load_reduce
def load_reduce(self):
stack = self.stack
args = stack.pop()
func = stack[-1]
stack[-1] = func(*args)
dispatch[REDUCE[0]] = load_reduce

REDUCE pops the args tuple and calls the callable at TOS. The result replaces the callable on the stack. For simple classes, func is the class itself and args is the constructor arguments.

Pickler.persistent_id

# CPython: Lib/pickle.py:280 Pickler.save
def save(self, obj, save_persistent_id=True):
...
if save_persistent_id:
pid = self.persistent_id(obj)
if pid is not None:
self.save_pers(pid)
return
...

persistent_id(obj) returns a non-None value to serialize the object as an external reference rather than by value. The Unpickler.persistent_load hook reconstructs it. Used for database rows, file handles, etc.

dispatch_table

# CPython: Lib/pickle.py:220 Pickler.save (dispatch_table lookup)
t = type(obj)
reduce = getattr(self, 'dispatch_table', None)
if reduce is not None:
reduce = reduce.get(t)
if reduce is None:
reduce = copyreg.dispatch_table.get(t)
if reduce is not None:
rv = reduce(obj)
else:
reduce = getattr(obj, '__reduce_ex__', None)
if reduce is not None:
rv = reduce(self.proto)

dispatch_table is checked before __reduce_ex__. A per-pickler dispatch_table overrides the global copyreg.dispatch_table. This allows customizing pickling for third-party types without modifying the types themselves.

gopy notes

save_reduce is module/pickle.SaveReduce in module/pickle/module.go. load_reduce pops args and calls objects.CallObject. persistent_id calls back into the Python hook via objects.CallOneArg. dispatch_table is a map[objects.Type]objects.Object.