O módulo copyreg permite registrar funções usadas pelo mecanismo de serialização pickle para tipos que não controlam diretamente sua própria redução. Ele é especialmente útil para extensões nativas, classes de terceiros, wrappers e objetos cuja forma serializada precisa ser definida fora da classe.
Na maioria das classes próprias, métodos como __reduce__(), __reduce_ex__(), __getstate__() e __setstate__() são mais fáceis de localizar e manter. copyreg deve ser usado quando o registro externo é realmente desejável. Também é importante lembrar que pickle não é seguro para dados não confiáveis: carregar um payload pode executar código.
Como pickle reconstrói um objeto
O pickle não grava uma cópia bruta da memória. Ele descreve como reconstruir o objeto: uma função ou callable, argumentos, estado adicional, itens de sequência e pares de mapping.
Uma função de redução simples retorna um callable e uma tupla de argumentos.
class Ponto:
def __init__(self, x, y):
self.x = x
self.y = y
def reconstruir_ponto(x, y):
return Ponto(x, y)
def reduzir_ponto(obj):
return reconstruir_ponto, (obj.x, obj.y)
copyreg.pickle() associa essa função ao tipo.
Registre um tipo
import copyreg
import pickle
copyreg.pickle(Ponto, reduzir_ponto)
dados = pickle.dumps(Ponto(2, 5))
restaurado = pickle.loads(dados)
print(restaurado.x, restaurado.y)
O registro é global ao processo e afeta serializações posteriores daquele tipo. Faça-o durante a inicialização, em um local previsível.
O construtor precisa ser importável
Funções usadas para reconstrução devem ser importáveis pelo nome de módulo quando o pickle for carregado em outro processo. Funções locais, lambdas e closures geralmente não funcionam.
Defina o reconstrutor no nível do módulo e mantenha seu caminho estável. Renomear ou mover a função pode quebrar dados antigos.
Função de redução no nível do módulo
A função registrada também deve permanecer disponível enquanto a aplicação serializa. Evite criar registros dentro de requests ou funções temporárias.
def registrar_serializacao():
copyreg.pickle(Ponto, reduzir_ponto)
Chame uma vez na inicialização do pacote ou do processo.
Estado adicional
A tupla de redução pode conter um terceiro item com estado adicional. Depois de criar a instância, pickle aplica o estado usando __setstate__() ou atualizando __dict__ quando apropriado.
def reduzir_documento(obj):
estado = {"titulo": obj.titulo, "tags": obj.tags}
return Documento, (), estado
O construtor chamado durante a reconstrução deve aceitar os argumentos fornecidos.
Objetos com __slots__
Classes com slots podem precisar de um estado explícito porque não possuem um __dict__ tradicional.
def reduzir_usuario(obj):
return criar_usuario, (obj.id, obj.nome)
Evite depender de detalhes internos do layout. Defina uma representação lógica e estável.
Tipos de extensões nativas
copyreg é comum quando um tipo vem de C ou de uma biblioteca externa e não pode ser alterado. Uma função Python pode extrair os valores necessários e escolher um reconstrutor.
Confirme que handles, ponteiros, conexões e recursos do sistema não são serializados como se fossem portáveis. Normalmente apenas identificadores ou dados devem ser preservados.
Não serialize recursos ativos
Sockets, arquivos abertos, locks, threads, processos e conexões de banco não podem ser reconstruídos com segurança a partir do estado bruto.
Serialização deve guardar configuração ou identificadores. O processo restaurado abre um novo recurso e trata falhas de conexão.
Compatibilidade entre versões
Pickles podem permanecer armazenados por anos. Uma alteração no callable, argumentos ou estado pode quebrar a leitura.
Inclua uma versão no estado e mantenha caminhos de migração.
estado = {
"versao": 2,
"nome": obj.nome,
"opcoes": obj.opcoes,
}
O reconstrutor ou __setstate__() pode aceitar versões anteriores.
Protocolos do pickle
O protocolo escolhido influencia eficiência e recursos. A função de redução registrada por copyreg não recebe automaticamente o número do protocolo.
Quando o comportamento precisa variar por protocolo, implementar __reduce_ex__(protocol) na classe pode ser mais adequado.
copyreg e classes próprias
Para uma classe que você controla, manter a lógica dentro dela geralmente melhora a descoberta.
class Ponto:
def __reduce__(self):
return type(self), (self.x, self.y)
Use registro externo para separar políticas, integrar tipos externos ou evitar modificar uma API pública.
dispatch_table personalizado
Picklers personalizados podem possuir uma dispatch_table própria, permitindo regras locais sem alterar o registro global.
import copyreg
import io
import pickle
buffer = io.BytesIO()
pickler = pickle.Pickler(buffer)
pickler.dispatch_table = copyreg.dispatch_table.copy()
pickler.dispatch_table[Ponto] = reduzir_ponto
pickler.dump(Ponto(1, 2))
Essa abordagem é melhor quando bibliotecas diferentes precisam de políticas distintas.
Evite conflito global
Dois pacotes podem tentar registrar redutores diferentes para o mesmo tipo. O último registro pode alterar o comportamento de toda a aplicação.
Prefira dispatch tables locais em bibliotecas reutilizáveis. Se o registro global for inevitável, documente-o e faça testes de integração.
constructor
A função copyreg.constructor() marca um callable como construtor seguro para determinados mecanismos históricos. Em código moderno, seu uso direto é raro.
Não interprete o nome “constructor” como garantia de segurança contra payload malicioso. A desserialização continua capaz de executar callables.
Códigos de extensão
add_extension(), remove_extension() e clear_extension_cache() gerenciam códigos compactos para referências globais em pickles.
import copyreg
copyreg.add_extension("meupacote.modelos", "Ponto", 1001)
Esses códigos fazem parte de um registro global e precisam ser coordenados. Colisões podem causar erros ou reconstrução incorreta.
Faixa e governança dos códigos
Não escolha números aleatórios em bibliotecas distribuídas sem uma política. O mesmo código deve identificar sempre o mesmo módulo e nome.
Remover ou reutilizar um código pode tornar pickles antigos perigosamente ambíguos.
Cache de extensões
O unpickler pode manter um cache para resolver códigos de extensão. clear_extension_cache() limpa esse estado, principalmente em testes ou cenários muito específicos.
Não limpe o cache continuamente em produção; isso pode prejudicar desempenho sem resolver problemas de compatibilidade.
Segurança do pickle
Nunca chame pickle.loads() com dados recebidos de usuário, rede, arquivo não confiável ou bucket compartilhado. O formato permite indicar funções a serem chamadas durante a reconstrução.
Assinatura criptográfica verifica origem e integridade, mas só é segura quando as chaves e o emissor são confiáveis. Para dados interoperáveis, prefira JSON, MessagePack ou outro formato sem execução automática.
Validação depois da carga
Mesmo um pickle confiável pode estar antigo ou corrompido. Valide tipos, limites, versões e invariantes após carregar.
Não presuma que a função registrada sempre recebe argumentos razoáveis. Um payload manipulado dentro de um ambiente confiável também pode causar consumo excessivo.
Limites de tamanho
Pickle pode representar estruturas enormes e profundamente aninhadas. Limite o tamanho do arquivo antes da carga e execute processamentos de risco em ambiente com memória e CPU controladas.
Não há um parâmetro universal de profundidade segura no loads().
Multiprocessing
Process pools usam serialização para enviar tarefas e resultados. Um registro de copyreg pode permitir que um tipo seja transferido, mas o worker também precisa importar o código de registro.
Faça a configuração em um módulo importável e teste com o método de início spawn. Veja multiprocessing no Python.
Objetos grandes
Personalizar a redução pode diminuir o payload ao guardar somente estado essencial. Meça tamanho e tempo antes e depois.
Não sacrifique compatibilidade e clareza por uma micro-otimização. Dados redundantes às vezes tornam migrações mais simples.
Herança
Um registro para uma classe não deve ser presumido como regra automática adequada para todas as subclasses. Subclasses podem adicionar estado ou invariantes.
Teste o tipo exato e decida se cada subclasse precisa de redutor próprio.
Dataclasses e named tuples
Muitos tipos Python comuns já são serializáveis sem configuração extra. Não registre uma função apenas porque o tipo possui vários campos.
Use copyreg quando a serialização padrão é incorreta, instável ou impossível.
Testes de round trip
Um teste básico serializa, desserializa e compara estado e comportamento.
original = Ponto(2, 5)
restaurado = pickle.loads(pickle.dumps(original))
assert (restaurado.x, restaurado.y) == (2, 5)
Inclua protocolos diferentes, processos separados, versões antigas e dados inválidos.
Teste em processo novo
Um round trip no mesmo processo pode esconder dependências do registro e imports já carregados. Execute a leitura em um subprocesso limpo ou em testes de integração.
Isso revela funções locais, caminhos de módulo errados e inicialização ausente.
Observabilidade
Registre tipo, versão do estado, protocolo, tamanho e duração sem armazenar o payload completo. Pickles podem conter segredos.
Falhas de reconstrução devem incluir o identificador do objeto ou job, não os bytes serializados.
Erros comuns
Os erros mais frequentes são registrar lambdas, mover o reconstrutor sem migração, serializar handles ativos, carregar dados não confiáveis, alterar o registro global em uma biblioteca, ignorar subclasses, reutilizar código de extensão e testar apenas no mesmo processo.
Conclusão
copyreg permite ensinar ao pickle como reduzir e reconstruir tipos que não controlam sua própria serialização. Use funções de módulo estáveis, estado versionado, dispatch tables locais quando possível e testes em processos limpos.
Não use pickle como formato para entrada não confiável. Consulte a documentação oficial de copyreg e a documentação de pickle.







