El módulo copyreg permite registrar funciones utilizadas por pickle para serializar tipos que no definen su propia reducción. Es especialmente útil para extensiones nativas, clases de terceros, wrappers y objetos cuya forma serializada debe definirse fuera de la clase.
En clases propias, métodos como __reduce__(), __reduce_ex__(), __getstate__() y __setstate__() suelen ser más fáciles de encontrar y mantener. Usa copyreg cuando el registro externo sea una decisión deliberada. Recuerda además que pickle no es seguro para entrada no confiable: cargar un payload puede ejecutar código.
Cómo reconstruye objetos pickle
Pickle no guarda una imagen cruda de la memoria. Describe cómo reconstruir un objeto mediante un callable, argumentos, estado opcional, items de secuencia y pares de mapping.
Una función de reducción simple devuelve un callable y una tupla de argumentos.
class Punto:
def __init__(self, x, y):
self.x = x
self.y = y
def reconstruir_punto(x, y):
return Punto(x, y)
def reducir_punto(obj):
return reconstruir_punto, (obj.x, obj.y)
copyreg.pickle() asocia la función con el tipo.
Registra un tipo
import copyreg
import pickle
copyreg.pickle(Punto, reducir_punto)
datos = pickle.dumps(Punto(2, 5))
restaurado = pickle.loads(datos)
print(restaurado.x, restaurado.y)
El registro es global al proceso y afecta serializaciones posteriores. Realízalo durante una inicialización predecible.
El constructor debe ser importable
Las funciones de reconstrucción deben poder importarse por su nombre de módulo cuando el pickle se carga en otro proceso. Funciones locales, lambdas y closures normalmente fallan.
Define reconstructores a nivel de módulo y conserva rutas estables. Mover o renombrar una función puede romper datos antiguos.
Registra una sola vez
La función reductora debe permanecer disponible durante la serialización. Evita registrar dentro de requests o scopes temporales.
def configurar_serializacion():
copyreg.pickle(Punto, reducir_punto)
Llama el setup durante la inicialización del paquete o proceso.
Estado adicional
Una tupla de reducción puede incluir un tercer elemento con estado. Tras construir la instancia, pickle aplica ese estado mediante __setstate__() o actualizando __dict__.
def reducir_documento(obj):
estado = {"titulo": obj.titulo, "tags": obj.tags}
return Documento, (), estado
El callable de reconstrucción debe aceptar los argumentos proporcionados.
Objetos con __slots__
Las clases con slots pueden necesitar estado explícito porque no utilizan un diccionario de instancia normal.
def reducir_usuario(obj):
return crear_usuario, (obj.id, obj.nombre)
No dependas de detalles privados del layout. Define una representación lógica y estable.
Tipos de extensiones nativas
copyreg es común cuando un tipo viene de C o de un paquete externo y no puede modificarse. Un reducer Python extrae valores y elige un callable de reconstrucción.
Handles, punteros, conexiones y recursos del sistema no deberían serializarse como si fueran portables. Conserva datos o identificadores.
No serialices recursos activos
Sockets, archivos abiertos, locks, threads, procesos y conexiones de base no pueden reconstruirse con seguridad desde estado crudo.
Serializa configuración o IDs. El proceso restaurado abre un recurso nuevo y maneja fallos de conexión.
Compatibilidad entre versiones
Los pickles pueden permanecer almacenados durante años. Cambiar callable, argumentos o estado puede volver ilegible el contenido anterior.
Incluye una versión explícita y conserva migraciones.
estado = {
"version": 2,
"nombre": obj.nombre,
"opciones": obj.opciones,
}
El reconstructor o __setstate__() puede aceptar versiones anteriores.
Protocolos de pickle
El protocolo influye en eficiencia y recursos disponibles. Una función registrada con copyreg no recibe automáticamente el número del protocolo.
Si el comportamiento debe variar, __reduce_ex__(protocol) en la clase puede ser más apropiado.
copyreg frente a métodos de clase
Para una clase propia, mantener el comportamiento dentro de ella suele mejorar la descubribilidad.
class Punto:
def __reduce__(self):
return type(self), (self.x, self.y)
Usa registro externo para integrar tipos de terceros, separar políticas o no modificar una API pública.
dispatch_table personalizada
Un Pickler personalizado puede utilizar su propia dispatch_table y evitar cambios globales.
import copyreg
import io
import pickle
buffer = io.BytesIO()
pickler = pickle.Pickler(buffer)
pickler.dispatch_table = copyreg.dispatch_table.copy()
pickler.dispatch_table[Punto] = reducir_punto
pickler.dump(Punto(1, 2))
Es preferible cuando bibliotecas distintas requieren políticas diferentes.
Evita conflictos globales
Dos paquetes pueden registrar reducers distintos para el mismo tipo. El último registro puede alterar todo el proceso.
Las bibliotecas reutilizables deberían preferir tablas locales. Si el registro global es inevitable, documéntalo y añade tests de integración.
constructor
copyreg.constructor() marca un callable como constructor para ciertos mecanismos históricos. Su uso directo es raro en código moderno.
La palabra “constructor” no vuelve segura la carga. La deserialización sigue pudiendo ejecutar callables.
Códigos de extensión
add_extension(), remove_extension() y clear_extension_cache() administran códigos numéricos compactos para referencias globales.
import copyreg
copyreg.add_extension("mipaquete.modelos", "Punto", 1001)
Los códigos pertenecen a un registro global y deben coordinarse. Las colisiones causan errores o reconstrucción incorrecta.
Gobernanza de códigos
No elijas números aleatorios en bibliotecas distribuidas sin una política. Un código debe identificar siempre el mismo módulo y nombre.
Eliminar o reutilizar un código vuelve ambiguos los pickles antiguos.
Cache de extensiones
El unpickler puede cachear la resolución. clear_extension_cache() limpia ese estado, principalmente en tests.
No lo limpies repetidamente en producción: perjudica rendimiento y no resuelve compatibilidad.
Seguridad de pickle
Nunca pases datos de usuarios, red, archivos no confiables o buckets compartidos a pickle.loads(). El formato puede solicitar llamadas a funciones.
Una firma criptográfica solo verifica origen e integridad cuando claves y emisor son confiables. Para interoperabilidad, prefiere JSON, MessagePack u otro formato sin ejecución automática.
Valida después de cargar
Incluso un pickle confiable puede estar antiguo o corrupto. Valida tipos, límites, versión e invariantes.
No supongas argumentos razonables. Un payload manipulado dentro de un entorno confiable puede consumir demasiados recursos.
Límites de tamaño
Pickle puede representar estructuras enormes y profundas. Limita el tamaño del archivo antes de cargar y usa límites de CPU y memoria.
No existe un argumento universal de profundidad segura para loads().
Multiprocessing
Los pools de procesos serializan tareas y resultados. Un registro de copyreg puede permitir transferir un tipo, pero los workers también deben importar la configuración.
Coloca el setup en un módulo importable y prueba con spawn. Consulta multiprocessing en Python.
Objetos grandes
Una reducción personalizada puede disminuir el payload al guardar solo estado esencial. Mide tamaño y tiempo.
No sacrifiques claridad y compatibilidad por una micro-optimización. Algo de redundancia puede facilitar migraciones.
Herencia
Un registro para una clase no debe asumirse correcto para todas las subclases. Pueden añadir estado o invariantes.
Prueba el tipo exacto y decide si cada subclase necesita su propio reducer.
Dataclasses y named tuples
Muchos tipos comunes ya se serializan correctamente. No registres una función solo porque un objeto tiene varios campos.
Usa copyreg cuando la serialización predeterminada sea imposible, inestable o incorrecta.
Tests de round trip
Un test básico serializa, deserializa y compara estado.
original = Punto(2, 5)
restaurado = pickle.loads(pickle.dumps(original))
assert (restaurado.x, restaurado.y) == (2, 5)
Incluye protocolos distintos, procesos separados, versiones antiguas y estado inválido.
Prueba en un proceso limpio
Un round trip en el mismo proceso puede ocultar imports y registros ya cargados. Lee el contenido en un subprocess limpio.
Así aparecen funciones locales, rutas de módulo incorrectas e inicialización ausente.
Observabilidad
Registra tipo, versión del estado, protocolo, tamaño y duración sin guardar el pickle completo. Puede contener secretos.
Los fallos de reconstrucción deben incluir un ID del objeto o job, no los bytes.
Errores comunes
Los fallos frecuentes son registrar lambdas, mover el reconstructor sin migración, serializar handles activos, cargar datos no confiables, alterar el registro global desde una biblioteca, ignorar subclases, reutilizar códigos y probar solo en el mismo proceso.
Conclusión
copyreg enseña a pickle cómo reducir y reconstruir tipos que no controlan su serialización. Usa funciones estables a nivel de módulo, estado versionado, tablas locales cuando sea posible y tests en procesos limpios.
No uses pickle para entrada no confiable. Consulta la documentación oficial de copyreg y la documentación de pickle.







