---
title: "pickle em Python: serializar objetos com segurança"
url: "https://python.dev.br/blog/python-pickle-serializacao-segura/"
markdown_url: "https://python.dev.br/blog/python-pickle-serializacao-segura.MD"
description: "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."
date: "2026-10-13"
author: "Equipe Python Dev BR"
---

# 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](/blog/trabalhando-com-json-python/).

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](/blog/python-tempfile-arquivos-temporarios/) + JSON se o dado for simples |
| Troca de objetos entre processos Python ([multiprocessing](/blog/python-multiprocessing/), filas internas) | Sim, é o mecanismo padrão | — |
| Modelo scikit-learn / arrays NumPy grandes | Prefira joblib | Veja [introdução ao scikit-learn](/blog/introducao-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](/blog/tomllib-ler-toml-python/), JSON, `.env` |
| Checksum / assinatura de conteúdo | **Não** | [hashlib](/blog/python-hashlib-checksums-integridade/), [HMAC](/blog/python-hmac-assinatura-webhooks/) |

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:

```python
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:

```python
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](/blog/python-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` |

```python
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__`:

```python
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](/glossario/celery/) reforça o mesmo ponto.

Para uma visão mais ampla de ameaças em apps Python, veja [Python é seguro?](/perguntas/python-e-seguro/) e [segurança em aplicações Python](/blog/seguranca-em-aplicacoes-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.

```python
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](/blog/python-pathlib-manipulacao-caminhos-arquivos/) 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** |

```python
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

| 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

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](/blog/trabalhando-com-json-python/), 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](/vagas/) publicadas no site.
