Skip to main content

Lib/copyreg.py

cpython 3.14 @ ab2d84fe1023/Lib/copyreg.py

copyreg provides the dispatch table that pickle consults when it serializes an object. It is a pure-Python module with no C accelerator. Three separate concerns live here: the dispatch_table dict (used by Pickler instances to look up per-type reduction functions), the _reconstructor helper (called by __reduce_ex__ on new-style classes to build a minimal reconstruction tuple), and the extension registry (a two-way mapping between (module, name) pairs and small integers for the EXT1/EXT2/EXT4 opcodes).

copy imports dispatch_table from this module and uses it with the same protocol as pickle.

Map

LinesSymbolRolegopy
1-50dispatch_table, pickledispatch_table is a plain dict mapping type to a unary reduce callable; pickle(ob_type, reduce_func, constructor=None) registers an entry after validating the constructor is callable.(stdlib pending)
50-100_reconstructor, __newobj__, __newobj_ex___reconstructor(cls, base, state) creates an instance via base.__new__(cls) and optionally calls base.__init__(state); __newobj__(cls, *args) calls cls.__new__(cls, *args) and is used by the NEWOBJ opcode; __newobj_ex__ adds keyword arguments for NEWOBJ_EX.(stdlib pending)
100-130add_extension, remove_extension, _extension_registry, _inverted_registry, _extension_cacheadd_extension((module, name), code) records a bi-directional mapping so the pickler can emit a compact EXT1/EXT2/EXT4 opcode instead of the full GLOBAL opcode string.(stdlib pending)

Reading

dispatch_table and pickle() (lines 1 to 50)

cpython 3.14 @ ab2d84fe1023/Lib/copyreg.py#L1-50

dispatch_table = {}

def pickle(ob_type, reduce_func, constructor=None):
if not callable(reduce_func):
raise TypeError("reduce_func must be callable")
if constructor is not None:
if not callable(constructor):
raise TypeError("constructor must be callable")
constructor_for(ob_type, constructor)
dispatch_table[ob_type] = reduce_func

dispatch_table is a module-level dict. pickle.Pickler checks self.dispatch_table first (an instance-level override), then falls back to copyreg.dispatch_table. The pickle() function here is the registration helper, not the stdlib module. The optional constructor argument is a legacy CPython 2 concept (for __safe_for_unpickling__) and is ignored in practice, but the callable check is still enforced.

_reconstructor usage in pickle (lines 50 to 100)

cpython 3.14 @ ab2d84fe1023/Lib/copyreg.py#L50-100

def _reconstructor(cls, base, state):
if base is object:
obj = object.__new__(cls)
else:
obj = base.__new__(cls, state)
if base.__init__ != object.__init__:
base.__init__(obj, state)
return obj

def __newobj__(cls, *args):
return cls.__new__(cls, *args)

def __newobj_ex__(cls, args, kwargs):
return cls.__new__(cls, *args, **kwargs)

_reconstructor is the callable that object.__reduce_ex__ places as the first element of the reduce tuple for new-style classes. When base is object, object.__new__(cls) is sufficient. When base is a C type (e.g., int, list) the state value must be passed to __new__ and possibly to __init__ as well so the C-level fields are populated.

__newobj__ is placed in the reduce tuple when the pickler emits the NEWOBJ opcode (protocol 2+). The unpickler calls copyreg.__newobj__(cls, *args) which is equivalent to cls.__new__(cls, *args). This avoids the overhead of a GLOBAL opcode for the constructor name on every object. __newobj_ex__ (protocol 4+) extends this to support keyword arguments via NEWOBJ_EX.

add_extension and the extension registry (lines 100 to 130)

cpython 3.14 @ ab2d84fe1023/Lib/copyreg.py#L100-130

_extension_registry = {} # (module, name) -> code
_inverted_registry = {} # code -> (module, name)
_extension_cache = {} # code -> object (populated by unpickler)

def add_extension(module, name, code):
key = (module, name)
if not 1 <= code <= 0x7fffffff:
raise ValueError("code out of range")
if key in _extension_registry:
if _extension_registry[key] == code:
return # already registered
raise ValueError("key %s is already registered with code %s" %
(key, _extension_registry[key]))
if code in _inverted_registry:
raise ValueError("code %s is already used for key %s" %
(code, _inverted_registry[code]))
_extension_registry[key] = code
_inverted_registry[code] = key

def remove_extension(module, name, code):
key = (module, name)
if _extension_registry.get(key) != code:
raise ValueError("key %s is not registered with code %s" %
(key, code))
del _extension_registry[key]
del _inverted_registry[code]
_extension_cache.pop(code, None)

When the pickler encounters a global name that has been registered with add_extension, it emits EXT1 (1-byte code, values 1-255), EXT2 (2-byte code, values 256-65535), or EXT4 (4-byte code) instead of the verbose GLOBAL opcode + module_name\nattr_name\n sequence. The unpickler reverses the lookup via _inverted_registry and caches the resolved object in _extension_cache for subsequent unpickling. remove_extension also clears _extension_cache so stale resolved objects are not returned.

gopy mirror

copyreg is a prerequisite for gopy's pickle port. The three dicts (dispatch_table, _extension_registry, _inverted_registry) are module-level globals that the pickle module imports by reference; gopy's module system must expose them as mutable Python dicts that pickle can read and write after import.