The pickle module can serialize many built-in types and ordinary user-defined classes, but some extension types, immutable objects, and library-owned classes need an external reconstruction rule. The Python copyreg module registers reduction functions for types, teaching pickle how to save objects that cannot or should not implement __reduce__() directly.
This guide covers copyreg.pickle(), reconstruction functions, compatibility, extension codes, and security boundaries. It complements our articles about Python pickle, pickletools, copy, shelve, and inspect.
What a reduction function does
To reconstruct an object, pickle needs a compact recipe describing which callable should run and which arguments it should receive.
def reduce_object(obj):
return (rebuild_object, (obj.value,))The most common result is a callable-and-arguments tuple. Advanced protocols can also carry state, iterators, and buffers.
When to use copyreg
Copyreg is useful when:
- the type belongs to a library you cannot modify;
- the object is implemented by a C extension;
- serialization policy should remain outside the class;
- several versions need one centralized strategy;
- support should be registered during application startup.
When you own the class and serialization is part of its contract, __reduce__(), __getstate__(), and __setstate__() may be clearer.
Register a type
The primary function is copyreg.pickle(type, function).
import copyreg
import pickle
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
def reduce_point(point):
return (Point, (point.x, point.y))
copyreg.pickle(Point, reduce_point)
data = pickle.dumps(Point(10, 20))
restored = pickle.loads(data)After registration, ordinary picklers consult the global dispatch table for instances of that exact type.
Use a separate reconstruction function
The returned callable does not have to be the class itself.
def rebuild_point(x, y):
point = Point.__new__(Point)
point.x = x
point.y = y
return point
def reduce_point(point):
return (rebuild_point, (point.x, point.y))This helps when the public constructor validates input, requires dependencies, or performs side effects that should not happen during restoration.
The callable must remain importable
Pickle normally stores a global reference to the reconstruction callable. It should be a module-level function importable under the same name during unpickling.
Lambdas, nested functions, and closures are poor choices. Renaming or moving the function can break old files.
Module-path compatibility
If code moves to a new package, keep an alias at the old path or migrate data before removing the symbol.
# old_module.py
from new_package.serialization import rebuild_pointPlan the expected lifetime of serialized data. For records that must survive for years or cross language boundaries, an explicit schema is usually better.
Register legacy constructors
copyreg.constructor() declares that an object can be used as a constructor by older extension mechanisms.
copyreg.constructor(rebuild_point)The helper verifies that the argument is callable and raises TypeError otherwise. Modern applications rarely need it outside compatibility scenarios.
Extension codes
add_extension() maps a module/name pair to an integer code used by pickle extension opcodes.
copyreg.add_extension(
"my_package.serialization",
"rebuild_point",
1001,
)The code must be between 1 and 0x7fffffff and must be globally unique in the ecosystem exchanging those pickles.
Remove an extension
copyreg.remove_extension(
"my_package.serialization",
"rebuild_point",
1001,
)All three values must match the registration. Pickles depending on the code become unreadable after removal unless an equivalent mapping is restored.
Clear the extension cache
clear_extension_cache() clears the internal cache of resolved extensions.
copyreg.clear_extension_cache()This is mainly useful in tests and dynamic environments. Ordinary applications should establish extension mappings once during startup.
Global registration
copyreg.pickle() changes a process-wide dispatch table. Any later pickle operation can observe the rule.
Avoid competing plugins registering different reducers for the same type. Centralize policy in one startup module and document ownership.
A private dispatch table
When a rule should not be global, create a custom pickle.Pickler with a copied dispatch table.
import copyreg
import io
import pickle
class LocalPickler(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
LocalPickler.dispatch_table[Point] = reduce_point
buffer = io.BytesIO()
LocalPickler(buffer).dump(Point(1, 2))This isolates policies among subsystems and tests.
Additional state
A reduction can include state beyond constructor arguments.
def reduce_session(obj):
return (
rebuild_session,
(obj.identifier,),
{"preferences": obj.preferences},
)The restored object receives state through __setstate__() when present or through dictionary updates. Test __slots__-based objects carefully.
Do not serialize live resources
Sockets, locks, open files, threads, database connections, and network clients should not be restored as though they still refer to the same resource.
Serialize declarative configuration or identifiers and reconnect explicitly in the new environment.
Version serialized state
Include a version when state may evolve.
def reduce_config(config):
state = {
"version": 2,
"data": config.data,
}
return (rebuild_config, (state,))The reconstruction function can migrate older versions and reject unknown future versions.
Validate during reconstruction
Even internal data can be corrupt. Validate types, ranges, and required fields.
def rebuild_config(state):
if not isinstance(state, dict):
raise TypeError("invalid state")
if state.get("version") not in {1, 2}:
raise ValueError("unsupported version")
return Config(migrate(state))Validation improves robustness but does not make third-party pickle data safe.
Security boundary
The official copyreg documentation describes its integration with pickle. Because pickle can import and call functions, every reducer adds another reconstruction path.
Never unpickle untrusted data. A safe reducer for your own type does not prevent the same stream from containing unrelated malicious operations.
Authentication and integrity
For internally generated files, use HMAC or a digital signature to detect modification before unpickling. Keep the key separate from the serialized file.
Authentication confirms an expected producer; it does not replace access control, key rotation, or compatibility planning.
Interaction with copy
The copy module uses related reduction protocols for shallow and deep copying. A registration can influence how an object is copied.
Test pickle.dumps(), copy.copy(), and copy.deepcopy() when the type participates in all three operations.
Round-trip tests
def test_point_round_trip():
original = Point(3, 4)
restored = pickle.loads(pickle.dumps(original))
assert restored.x == 3
assert restored.y == 4
assert restored is not originalTest every supported protocol, empty state, extreme values, and older serialized versions.
Inspect with pickletools
Use pickletools.dis() to verify which globals and opcodes are stored without executing the stream.
import pickletools
pickletools.dis(pickle.dumps(Point(1, 2)))This reveals accidental dependencies on internal module paths and protocols newer than expected.
Common mistakes
- Registering a lambda or local function.
- Moving a reconstructor without migration.
- Using conflicting extension codes.
- Changing global registration on every request.
- Trying to persist live resources.
- Failing to version long-lived state.
- Assuming validation makes pickle trustworthy.
- Ignoring effects on copy and deepcopy.
Best practices
- Use module-level reconstruction functions.
- Centralize registrations during startup.
- Prefer private dispatch tables where possible.
- Serialize only declarative state.
- Version and validate reconstruction.
- Keep compatibility aliases for old data.
- Test protocols and copying behavior.
- Never load pickle from an unknown source.
Conclusion
The Python copyreg module registers external reduction rules for types that need to participate in pickle without modifying their classes. It is useful for extension types, third-party classes, and centralized serialization policies.
This flexibility requires discipline. Callables must remain importable, state should be versioned, and global registration must be controlled. Copyreg customizes how trusted objects are persisted; it does not turn pickle into a safe external data format.







