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.

13 Oct 2026 7 min de leitura Equipe Python Dev BR

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árioUse pickle?Alternativa melhor
Cache local de um resultado caro no mesmo projetoSim, com arquivo privadotempfile + 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 grandesPrefira joblibVeja introdução ao scikit-learn
API pública, cookie, upload, mensagem de fila externaNãoJSON, msgpack, Protobuf
Configuração versionada ou legível por humanosNãoTOML com tomllib, JSON, .env
Checksum / assinatura de conteúdoNãohashlib, 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:

  1. Abra o arquivo em modo binário ("wb" / "rb"). Modo texto gera TypeError.
  2. Passe protocol=pickle.HIGHEST_PROTOCOL ao gravar. O padrão histórico ainda é 4 em vários contextos; o 5 é mais eficiente com bytes e buffers.
  3. O arquivo .pkl não é texto. Não abra no editor esperando JSON legível; use pickletools.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

ProtocoloDesdeUso típico
0–1legadotexto / debug histórico
2Python 2/3 transiçãoraramente necessário hoje
3Python 3.0incompatível com Python 2
4Python 3.4padrão antigo ainda comum
5Python 3.8recomendado: 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 .pkl enviado 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ériopickleJSONjoblib
Bibliotecapadrãopadrãoexterna (pip install joblib)
Tipos ricos (datetime, set, dataclass)SimLimitado / precisa de encoderSim (foco em NumPy)
Legível por humanosNãoSimNão
Seguro com dados externosNãoSim (sem execução)Não (mesma família de risco)
Interoperável com outras linguagensNãoSimNão
Modelos ML / arrays grandesFunciona, menos idealRuimRecomendado
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:

  1. Dado cruza a fronteira da sua aplicação → JSON (ou outro schema explícito).
  2. Dado fica entre processos Python sob o seu controle → pickle.
  3. Dado é modelo treinado / array NumPy → joblib.

Erros comuns e como ler a mensagem

SintomaCausa provávelCorreção
TypeError: write() argument must be str, not bytesarquivo aberto em modo textouse "wb" / "rb"
AttributeError / PicklingError ao serializar lambda ou função internalambda, closure ou função aninhadaextraia a função para o topo do módulo
ModuleNotFoundError / AttributeError no loadclasse renomeada ou módulo movidomantenha aliases de import ou regenere o cache
UnpicklingErrorarquivo truncado ou não é picklevalide origem e integridade; não “consertar” payload externo
processo worker quebra ao receber callbackobjeto não serializável na filapasse 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

  1. O dado foi escrito pelo meu código nesta máquina ou neste cluster?
  2. Ninguém de fora consegue substituir o arquivo ou a mensagem?
  3. O consumidor roda a mesma versão (ou compatível) das classes envolvidas?
  4. 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.

E
Equipe Python Dev BR

Contribuidor do Python Dev BR

Artigos relacionados