O módulo pickle sabe serializar muitos tipos built-in e classes definidas normalmente, mas algumas extensões, tipos imutáveis e objetos controlados por bibliotecas precisam de uma regra externa de reconstrução. O módulo copyreg no Python registra funções de redução associadas a tipos, permitindo ensinar o pickle a salvar objetos que não podem ou não devem implementar diretamente __reduce__().
Neste guia, você aprenderá a usar copyreg.pickle(), criar funções de reconstrução, preservar compatibilidade e compreender os limites de segurança. O conteúdo complementa nossos artigos sobre pickle no Python, pickletools, copy, shelve e inspect.
O que é uma função de redução
Para reconstruir um objeto, pickle precisa de uma descrição compacta: qual função deve ser chamada e quais argumentos devem ser fornecidos. Essa descrição é chamada de redução.
def reduzir_objeto(objeto):
return (reconstruir_objeto, (objeto.valor,))O resultado mais comum é uma tupla com callable e argumentos. Protocolos avançados também permitem estado adicional, iteradores e buffers.
Quando usar copyreg
Use copyreg quando:
- o tipo pertence a uma biblioteca que você não pode modificar;
- o objeto é implementado em C ou extensão;
- a regra de serialização deve ficar fora da classe;
- várias versões precisam compartilhar uma estratégia centralizada;
- você deseja registrar suporte apenas durante a inicialização da aplicação.
Quando a classe é sua e a regra faz parte do contrato do tipo, métodos como __reduce__(), __getstate__() e __setstate__() podem ser mais claros.
Registrar um tipo
A função principal é copyreg.pickle(type, function).
import copyreg
import pickle
class Ponto:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
def reduzir_ponto(ponto):
return (Ponto, (ponto.x, ponto.y))
copyreg.pickle(Ponto, reduzir_ponto)
dados = pickle.dumps(Ponto(10, 20))
restaurado = pickle.loads(dados)A partir do registro, picklers comuns consultam a dispatch table global para instâncias daquele tipo.
Função de reconstrução separada
O callable retornado não precisa ser a própria classe.
def reconstruir_ponto(x, y):
ponto = Ponto.__new__(Ponto)
ponto.x = x
ponto.y = y
return ponto
def reduzir_ponto(ponto):
return (reconstruir_ponto, (ponto.x, ponto.y))Isso é útil quando o construtor público valida parâmetros, exige dependências ou produz efeitos que não devem ocorrer na restauração.
O callable deve ser importável
Pickle normalmente grava uma referência global ao callable. A função de reconstrução deve estar no nível do módulo e ser importável pelo mesmo nome durante o unpickle.
Lambdas, funções locais e closures não são escolhas adequadas. Renomear ou mover a função pode quebrar arquivos antigos.
Compatibilidade de módulos
Se o código muda de pacote, mantenha um alias no caminho antigo ou migre os dados antes de remover o símbolo.
# modulo_antigo.py
from pacote_novo.serializacao import reconstruir_pontoPlaneje a vida útil dos pickles. Para dados que precisam durar anos ou atravessar linguagens, um formato com esquema explícito geralmente é melhor.
Registrar construtores antigos
copyreg.constructor() declara que um objeto pode ser usado como construtor seguro pelo mecanismo de extensão antigo.
copyreg.constructor(reconstruir_ponto)A função verifica se o argumento é chamável e gera TypeError caso contrário. A documentação informa que esse mecanismo existe por compatibilidade histórica e raramente é necessário em código moderno.
Códigos de extensão
add_extension() associa um par módulo/nome a um código inteiro usado por opcodes de extensão do pickle.
copyreg.add_extension(
"meu_pacote.serializacao",
"reconstruir_ponto",
1001,
)O código deve estar entre 1 e 0x7fffffff e ser globalmente único dentro do ecossistema que troca esses arquivos.
Remover uma extensão
copyreg.remove_extension(
"meu_pacote.serializacao",
"reconstruir_ponto",
1001,
)Os três valores precisam corresponder exatamente ao registro. Arquivos que dependem do código deixam de ser carregáveis depois da remoção.
Limpar o cache
clear_extension_cache() remove o cache interno de extensões resolvidas.
copyreg.clear_extension_cache()A função é destinada principalmente a testes e cenários dinâmicos. Aplicações normais devem registrar extensões uma vez durante a inicialização.
Registro global
copyreg.pickle() altera uma tabela global do processo. Qualquer código que use pickle posteriormente pode observar a nova regra.
Evite registrar comportamentos diferentes para o mesmo tipo em plugins concorrentes. Centralize o registro em um módulo de inicialização e documente quem é responsável pela política.
Dispatch table privada
Quando uma regra não deve ser global, crie um pickle.Pickler personalizado com uma cópia da tabela.
import copyreg
import io
import pickle
class PicklerLocal(pickle.Pickler):
dispatch_table = copyreg.dispatch_table.copy()
PicklerLocal.dispatch_table[Ponto] = reduzir_ponto
buffer = io.BytesIO()
PicklerLocal(buffer).dump(Ponto(1, 2))Esse padrão isola políticas entre subsistemas e testes.
Estado adicional
Uma redução pode incluir estado além dos argumentos do construtor.
def reduzir_sessao(objeto):
return (
reconstruir_sessao,
(objeto.identificador,),
{"preferencias": objeto.preferencias},
)O objeto restaurado recebe o estado por __setstate__() quando disponível ou por atualização de __dict__. Teste cuidadosamente objetos com __slots__.
Não serializar recursos vivos
Sockets, locks, arquivos abertos, threads, conexões de banco e clientes de rede não devem ser restaurados como se ainda representassem o mesmo recurso.
Armazene somente configuração ou identificadores necessários e reconecte explicitamente no ambiente novo.
Versão do estado
Inclua uma versão quando a estrutura pode evoluir.
def reduzir_config(config):
estado = {
"versao": 2,
"dados": config.dados,
}
return (reconstruir_config, (estado,))A função de reconstrução pode migrar versões antigas e rejeitar versões futuras desconhecidas.
Validação na restauração
Mesmo dados internos podem estar corrompidos. Valide tipos, limites e campos obrigatórios na função de reconstrução.
def reconstruir_config(estado):
if not isinstance(estado, dict):
raise TypeError("estado inválido")
if estado.get("versao") not in {1, 2}:
raise ValueError("versão não suportada")
return Config(migrar(estado))Essa validação melhora robustez, mas não torna seguro carregar pickles de terceiros.
Segurança
A documentação oficial de copyreg descreve a integração com pickle. Como pickle pode importar e chamar funções, qualquer registro amplia os caminhos de reconstrução disponíveis.
Nunca faça unpickle de dados não confiáveis. Uma função de redução “segura” para seu tipo não impede que o mesmo arquivo contenha outras instruções maliciosas.
Assinatura e integridade
Para arquivos produzidos internamente, use HMAC ou assinatura para detectar alterações antes do unpickle. Mantenha a chave fora do arquivo e compare a assinatura de forma segura.
Autenticação só confirma a origem esperada. Ela não substitui controle de acesso, rotação de chaves e compatibilidade de versão.
copyreg e copy
O módulo copy utiliza protocolos relacionados para cópia rasa e profunda. Um registro pode influenciar como determinados objetos são copiados.
Teste tanto pickle.dumps() quanto copy.copy() e copy.deepcopy() quando o tipo participa dos dois fluxos.
Testar round-trip
def test_ponto_round_trip():
original = Ponto(3, 4)
restaurado = pickle.loads(pickle.dumps(original))
assert restaurado.x == 3
assert restaurado.y == 4
assert restaurado is not originalTeste todos os protocolos suportados, valores extremos, estado vazio e versões antigas.
Inspecionar com pickletools
Use pickletools.dis() para confirmar quais globals e opcodes foram gravados sem executar o arquivo.
import pickletools
pickletools.dis(pickle.dumps(Ponto(1, 2)))Isso ajuda a detectar dependência acidental de caminhos internos e protocolos mais novos que o esperado.
Erros frequentes
- Registrar lambda ou função local.
- Mover o reconstrutor sem migração.
- Usar códigos de extensão conflitantes.
- Alterar a tabela global em cada requisição.
- Tentar persistir recursos vivos.
- Não versionar estados duradouros.
- Presumir que validação torna pickle confiável.
- Esquecer impactos em copy e deepcopy.
Boas práticas
- Registre funções no nível do módulo.
- Centralize registros durante o startup.
- Use dispatch table privada quando possível.
- Serialize apenas estado declarativo.
- Versione e valide a reconstrução.
- Mantenha aliases para dados antigos.
- Teste protocolos e cópias.
- Nunca carregue pickle de origem desconhecida.
Conclusão
O módulo copyreg no Python permite registrar regras externas de redução para tipos que precisam participar do protocolo pickle. Ele é útil para extensões, classes de terceiros e políticas de serialização centralizadas.
Essa flexibilidade exige disciplina. Callables precisam permanecer importáveis, estados devem ser versionados e a tabela global deve ser controlada. Copyreg personaliza como objetos confiáveis são persistidos; ele não transforma pickle em um formato seguro para entrada externa.





