I propose to add two registration functions to copyreg, following the copyreg.pickle() precedent, for customizing copying of instances of types that cannot be modified:
copyreg.copy(SomeType, copy_function) # like __copy__
copyreg.deepcopy(SomeType, deepcopy_function) # like __deepcopy__
# e.g. treat an external library's immutable objects as atomic:
copyreg.deepcopy(thirdparty.Handle, lambda obj, memo: obj)
copy.copy() and copy.deepcopy() use the registered functions in preference to the special methods and the pickle interfaces. Registration is by exact type and does not affect pickling ā today the only out-of-class hook is copyreg.pickle(), which forces copy and pickle semantics to be the same.
This replaces the non-public way: the private _copy_dispatch and _deepcopy_dispatch tables have been monkey-patched in the wild for lack of a public API ā even by our own test suite. The new public tables take their place (_deepcopy_dispatch is removed, as _copy_dispatch already was in 3.14).
This was requested in gh-141737, which was closed pending a discussion here. It is also the companion of PEP 837 (copyreg.json()): together they make copyreg not only the pickle registry module, but the general registry module ā the place to register protocol implementations for types you cannot modify.
Issue: gh-153612, implementation: python/cpython#153613.