pickle em Python: serializar objetos com segurança
Aprenda pickle em Python: dump, load, protocolo 5, o que serializa, diferenças para JSON e joblib, e por que nunca deserializar dados não confiáveis.
O módulo pickle do Python transforma objetos em bytes e reconstrói esses objetos depois — o caminho clássico para cache local, filas entre processos e checkpoints de um job. A regra prática é clara: use pickle.dump / pickle.load somente com dados que o seu próprio código gerou; nunca deserializar um arquivo, cookie, webhook ou upload vindo de fora. Para dados externos, prefira JSON.
Neste guia você vai serializar dataclasses e dicionários, gravar e ler arquivos .pkl, escolher o protocolo certo, entender o que pickle não serializa bem, comparar com JSON e joblib, e ver por que um __reduce__ malicioso torna loads equivalente a executar código. Os exemplos usam Python 3.10 ou superior e a biblioteca padrão.
Quando pickle faz sentido
| Cenário | Use pickle? | Alternativa melhor |
|---|---|---|
| Cache local de um resultado caro no mesmo projeto | Sim, com arquivo privado | tempfile + JSON se o dado for simples |
| Troca de objetos entre processos Python (multiprocessing, filas internas) | Sim, é o mecanismo padrão | — |
| Modelo scikit-learn / arrays NumPy grandes | Prefira joblib | Veja introdução ao scikit-learn |
| API pública, cookie, upload, mensagem de fila externa | Não | JSON, msgpack, Protobuf |
| Configuração versionada ou legível por humanos | Não | TOML com tomllib, JSON, .env |
| Checksum / assinatura de conteúdo | Não | hashlib, HMAC |
Pickle resolve fidelidade ao objeto Python. JSON resolve interoperabilidade e auditoria. Confundir os dois é a origem da maior parte dos incidentes com deserialização.
dump, dumps, load e loads
Há quatro funções. As formas com s no final trabalham em memória (bytes); as outras leem e escrevem em arquivos binários:
import pickle
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
@dataclass
class Pedido:
id: int
cliente: str
total: float
criado_em: datetime
pedido = Pedido(
id=42,
cliente="Ana Souza",
total=189.90,
criado_em=datetime(2026, 10, 13, 12, 0, 0),
)
# Em memória
blob = pickle.dumps(pedido, protocol=pickle.HIGHEST_PROTOCOL)
restaurado = pickle.loads(blob)
print(restaurado.cliente, restaurado.total)
# Ana Souza 189.9
# Em arquivo — sempre modo binário
caminho = Path("pedido.pkl")
with caminho.open("wb") as f:
pickle.dump(pedido, f, protocol=pickle.HIGHEST_PROTOCOL)
with caminho.open("rb") as f:
mesmo = pickle.load(f)
assert mesmo == pedido
Três detalhes que evitam dor:
- Abra o arquivo em modo binário (
"wb"/"rb"). Modo texto geraTypeError. - Passe
protocol=pickle.HIGHEST_PROTOCOLao gravar. O padrão histórico ainda é 4 em vários contextos; o 5 é mais eficiente combytese buffers. - O arquivo
.pklnão é texto. Não abra no editor esperando JSON legível; usepickletools.dis(blob)se precisar inspecionar o bytecode do pickle.
O que pickle serializa — e o que falha
Pickle lida bem com a maior parte dos tipos do dia a dia:
- tipos embutidos:
dict,list,tuple,set,str,bytes,int,float,bool,None; datetime,decimal.Decimal,pathlib.Path,uuid.UUID;dataclasses, namedtuples e instâncias de classes definidas no seu código;- funções e classes top-level importáveis pelo nome do módulo.
Ele falha, ou falha de forma surpreendente, com:
import pickle
def local():
def interna(x):
return x * 2
return interna
try:
pickle.dumps(local())
except AttributeError as erro:
print(type(erro).__name__, "→", erro)
# AttributeError → Can't pickle local object 'local.<locals>.interna'
try:
pickle.dumps(lambda x: x + 1)
except (AttributeError, pickle.PicklingError) as erro:
print(type(erro).__name__, "→", erro)
# PicklingError → Can't pickle <function <lambda> ...>: attribute lookup <lambda> ... failed
Lambdas, closures e funções definidas dentro de outras funções não têm um caminho de import estável. Conexões de banco, sockets, arquivos abertos e locks também não atravessam o pickle. Em multiprocessing, esse é o motivo clássico de um worker quebrar ao receber um callback “anônimo”.
Outro ponto: pickle grava uma referência ao caminho da classe (modulo.Classe), não o código-fonte da classe. Se você renomear o módulo ou a classe entre a gravação e a leitura, o load levanta ModuleNotFoundError ou AttributeError. Trate arquivos pickle como acoplados à versão do seu código — não como formato de arquivo de longa duração.
Protocolos: por que o 5 importa
| Protocolo | Desde | Uso típico |
|---|---|---|
| 0–1 | legado | texto / debug histórico |
| 2 | Python 2/3 transição | raramente necessário hoje |
| 3 | Python 3.0 | incompatível com Python 2 |
| 4 | Python 3.4 | padrão antigo ainda comum |
| 5 | Python 3.8 | recomendado: out-of-band buffers, melhor com bytes |
import pickle
dados = {"relatorio": b"\x00\x01" * 1000}
p4 = pickle.dumps(dados, protocol=4)
p5 = pickle.dumps(dados, protocol=5)
print(len(p4), len(p5), pickle.HIGHEST_PROTOCOL)
Se o leitor e o escritor rodam no mesmo deploy (mesmo interpretador, mesmo código), use HIGHEST_PROTOCOL. Só fixe protocol=4 se um consumidor antigo — por exemplo, um worker preso em Python 3.7 — ainda precisar ler o arquivo.
O risco real: __reduce__ executa código no load
Pickle não “só lê dados”. Para reconstruir um objeto, ele pode chamar um callable escolhido pelo payload. Qualquer classe pode personalizar isso com __reduce__:
import pickle
class PayloadMalicioso:
def __reduce__(self):
# O load vai chamar print(...) — no mundo real, poderia ser os.system
return (print, ("NÃO execute pickle.loads em dados não confiáveis",))
armazenado = pickle.dumps(PayloadMalicioso())
pickle.loads(armazenado)
# NÃO execute pickle.loads em dados não confiáveis
Substitua print por os.system, subprocess.Popen ou qualquer outra função e o impacto fica óbvio. Por isso:
- não aceite
.pklenviado por usuário, parceiro ou webhook; - não leia pickle de um bucket S3/R2 público sem autenticação e sem assinatura sua;
- não use pickle como formato de cookie, token de sessão ou mensagem em fila compartilhada com outros times;
- se a fila for Celery ou similar, configure serialização JSON — o glossário de Celery reforça o mesmo ponto.
Para uma visão mais ampla de ameaças em apps Python, veja Python é seguro? e segurança em aplicações Python.
Padrão seguro: cache privado com Path e protocolo alto
Um uso legítimo e frequente: guardar o resultado de um cálculo caro em disco, no diretório do projeto ou em um diretório temporário que só o seu processo escreve.
import pickle
from pathlib import Path
def carregar_ou_calcular(cache: Path, calcular):
if cache.is_file():
with cache.open("rb") as f:
return pickle.load(f)
resultado = calcular()
cache.parent.mkdir(parents=True, exist_ok=True)
with cache.open("wb") as f:
pickle.dump(resultado, f, protocol=pickle.HIGHEST_PROTOCOL)
return resultado
def gerar_resumo():
# Exemplo: agregação pesada que você controla ponta a ponta
return {"vagas": 128, "empresas": 37, "atualizado_em": "2026-10-13"}
resumo = carregar_ou_calcular(Path(".cache/resumo_vagas.pkl"), gerar_resumo)
print(resumo["vagas"])
Boas práticas nesse padrão:
- coloque o diretório de cache no
.gitignore; - versionar o nome do arquivo quando a estrutura do objeto mudar (
resumo_vagas_v2.pkl); - combine com pathlib em vez de concatenar strings de caminho;
- se o dado for só
dict/list/str/number, considere JSON — fica legível e audível no Git.
pickle vs JSON vs joblib
| Critério | pickle | JSON | joblib |
|---|---|---|---|
| Biblioteca | padrão | padrão | externa (pip install joblib) |
| Tipos ricos (datetime, set, dataclass) | Sim | Limitado / precisa de encoder | Sim (foco em NumPy) |
| Legível por humanos | Não | Sim | Não |
| Seguro com dados externos | Não | Sim (sem execução) | Não (mesma família de risco) |
| Interoperável com outras linguagens | Não | Sim | Não |
| Modelos ML / arrays grandes | Funciona, menos ideal | Ruim | Recomendado |
import json
import pickle
from datetime import datetime
evento = {
"nome": "PyNorte",
"quando": datetime(2026, 11, 20, 9, 0, 0),
"tags": {"python", "comunidade"},
}
# JSON exige conversão explícita
json_amigavel = {
"nome": evento["nome"],
"quando": evento["quando"].isoformat(),
"tags": sorted(evento["tags"]),
}
print(json.dumps(json_amigavel, ensure_ascii=False))
# pickle preserva os tipos originais
clone = pickle.loads(pickle.dumps(evento, protocol=pickle.HIGHEST_PROTOCOL))
print(type(clone["quando"]).__name__, type(clone["tags"]).__name__)
# datetime set
Resumo de decisão:
- Dado cruza a fronteira da sua aplicação → JSON (ou outro schema explícito).
- Dado fica entre processos Python sob o seu controle → pickle.
- Dado é modelo treinado / array NumPy → joblib.
Erros comuns e como ler a mensagem
| Sintoma | Causa provável | Correção |
|---|---|---|
TypeError: write() argument must be str, not bytes | arquivo aberto em modo texto | use "wb" / "rb" |
AttributeError / PicklingError ao serializar lambda ou função interna | lambda, closure ou função aninhada | extraia a função para o topo do módulo |
ModuleNotFoundError / AttributeError no load | classe renomeada ou módulo movido | mantenha aliases de import ou regenere o cache |
UnpicklingError | arquivo truncado ou não é pickle | valide origem e integridade; não “consertar” payload externo |
| processo worker quebra ao receber callback | objeto não serializável na fila | passe dados simples e importe a função no worker |
Se a mensagem citar FIND_CLASS ou falhar ao importar um módulo no load, desconfie imediatamente da origem do arquivo — é exatamente o gancho que um payload abusaria.
Checklist rápido antes de usar pickle
- O dado foi escrito pelo meu código nesta máquina ou neste cluster?
- Ninguém de fora consegue substituir o arquivo ou a mensagem?
- O consumidor roda a mesma versão (ou compatível) das classes envolvidas?
- Se a resposta for “não” para qualquer item → não use pickle.
Conclusão
pickle continua sendo a ferramenta certa para serializar objetos Python ricos entre partes de um sistema que você controla: cache local, multiprocessing, checkpoints internos. Ele deixa de ser a ferramenta certa no instante em que o payload atravessa uma fronteira de confiança. Nesses casos, JSON, schemas explícitos e, para ML, joblib com artefatos gerados por você são o caminho seguro.
Se você está montando automações, APIs ou pipelines e quer ver o que o mercado pede em Python no Brasil, acompanhe também as vagas de Python publicadas no site.