---
title: "pprint em Python: formate dicionários e JSON para depurar"
url: "https://python.dev.br/blog/python-pprint-formatar-dicionarios-json/"
markdown_url: "https://python.dev.br/blog/python-pprint-formatar-dicionarios-json.MD"
description: "Aprenda pprint em Python para formatar dicionários, listas e JSON legíveis, controlar largura e profundidade e gerar textos úteis para depuração."
date: "2026-09-09"
author: "Equipe Python Dev BR"
---

# pprint em Python: formate dicionários e JSON para depurar

Aprenda pprint em Python para formatar dicionários, listas e JSON legíveis, controlar largura e profundidade e gerar textos úteis para depuração.


O módulo **`pprint` do Python** formata dicionários, listas, tuplas e outras coleções aninhadas para que sejam mais fáceis de ler durante a depuração. Ele adiciona quebras de linha e indentação conforme a estrutura do objeto, sem exigir instalação. Para uma inspeção rápida, use `pprint(objeto)`; para receber o resultado como texto, use `pformat(objeto)`; para produzir **JSON válido**, continue usando `json.dumps(objeto, indent=2, ensure_ascii=False)`.

A recomendação prática é usar `pprint` no terminal, no REPL e em scripts de diagnóstico, especialmente ao examinar respostas de APIs e configurações grandes. Ele melhora a representação de objetos Python, mas não substitui serialização JSON nem logging estruturado. Este guia mostra as diferenças, as opções de largura e profundidade e os cuidados necessários para não vazar dados sensíveis.

## Exemplo rápido: `print` versus `pprint`

Considere uma resposta já convertida de uma API brasileira de pedidos:

```python
pedido = {
    "id": 1842,
    "cliente": {
        "nome": "Ana Souza",
        "cidade": "Recife",
        "uf": "PE",
    },
    "itens": [
        {"sku": "CAF-500", "nome": "Café especial 500 g", "quantidade": 2},
        {"sku": "FIL-100", "nome": "Filtro de papel", "quantidade": 1},
    ],
    "pagamento": {"meio": "pix", "status": "aprovado"},
}

print(pedido)
```

`print` normalmente coloca toda a coleção em uma linha. Isso funciona, mas fica desconfortável quando há muitos níveis e valores:

```text
{'id': 1842, 'cliente': {'nome': 'Ana Souza', 'cidade': 'Recife', 'uf': 'PE'}, 'itens': [{'sku': 'CAF-500', ...}], ...}
```

Com `pprint`, a estrutura fica evidente:

```python
from pprint import pprint

pprint(pedido)
```

A saída será parecida com esta:

```text
{'cliente': {'cidade': 'Recife', 'nome': 'Ana Souza', 'uf': 'PE'},
 'id': 1842,
 'itens': [{'nome': 'Café especial 500 g',
            'quantidade': 2,
            'sku': 'CAF-500'},
           {'nome': 'Filtro de papel',
            'quantidade': 1,
            'sku': 'FIL-100'}],
 'pagamento': {'meio': 'pix', 'status': 'aprovado'}}
```

A representação ainda é de um **objeto Python**: strings usam aspas simples quando apropriado, tuplas continuam tuplas e valores como `None`, `True` e `False` mantêm a sintaxe Python. Essa diferença será importante ao comparar `pprint` com JSON.

## Funções principais do módulo

| Recurso | O que faz | Quando usar |
| --- | --- | --- |
| `pprint(objeto)` | Formata e escreve o objeto em um fluxo | Inspeção rápida no terminal |
| `pp(objeto)` | Atalho moderno para `pprint` | REPL e código de diagnóstico |
| `pformat(objeto)` | Retorna a representação formatada como `str` | Mensagens, testes e ferramentas |
| `isreadable(objeto)` | Indica se a representação pode ser reconstruída com `eval` em condições adequadas | Diagnóstico específico, não validação de segurança |
| `isrecursive(objeto)` | Detecta referência recursiva na estrutura | Investigar coleções cíclicas |
| `PrettyPrinter(...)` | Guarda uma configuração reutilizável | Muitas impressões com o mesmo padrão |

Na maior parte dos scripts, `pprint` e `pformat` resolvem tudo. A classe `PrettyPrinter` se torna útil quando você quer aplicar sempre a mesma largura, indentação e ordenação.

## `pprint`, `pp` e `pformat`

`pprint()` imprime diretamente. `pp()` é uma função de conveniência com finalidade semelhante e disponível nas versões modernas do Python. Já `pformat()` não escreve nada: ela devolve uma string.

```python
from pprint import pformat, pp, pprint

configuracao = {
    "aplicacao": "conciliador-pix",
    "filiais": ["SP", "RJ", "PE"],
    "regras": {
        "tolerancia_centavos": 1,
        "reprocessar": True,
    },
}

pprint(configuracao)
pp(configuracao)

texto = pformat(configuracao)
print(type(texto))  # <class 'str'>
```

Use `pformat` quando outra API espera texto:

```python
from pprint import pformat

mensagem = f"Configuração carregada:\n{pformat(configuracao, width=60)}"
print(mensagem)
```

Essa possibilidade não significa que seja adequado jogar objetos inteiros no log de produção. Antes de registrar uma estrutura, remova tokens, senhas, cookies, CPF, e-mail, endereço e outros dados pessoais. Mais adiante veremos uma função simples de mascaramento.

## Controlando a largura com `width`

O argumento `width` informa a largura desejada para a representação. Ele não é uma garantia absoluta para toda string indivisível, mas orienta onde o formatador deve quebrar linhas.

```python
from pprint import pprint

produto = {
    "nome": "Kit para preparo de café especial",
    "categorias": ["cafés", "acessórios", "presentes"],
    "centros_distribuicao": ["Cajamar", "Extrema", "Jaboatão"],
}

print("Largura 80:")
pprint(produto, width=80)

print("\nLargura 40:")
pprint(produto, width=40)
```

Uma largura menor costuma gerar mais linhas. Para saída em terminal dividido, comentários de teste ou relatórios em texto, valores entre 60 e 100 são pontos de partida razoáveis.

Não use `width` para cortar segredos ou limitar efetivamente o tamanho da saída. Uma string longa, como um token ou conteúdo em Base64, ainda pode aparecer inteira. Se o objeto for grande, selecione os campos relevantes ou aplique truncamento explícito antes de formatar.

## Ajustando indentação com `indent`

`indent` controla quantos espaços são adicionados por nível de aninhamento:

```python
from pprint import pprint

resultado = {
    "lote": "2026-09-09-manha",
    "resumo": {
        "processados": 120,
        "aprovados": 117,
        "falhas": [
            {"linha": 18, "motivo": "CEP inválido"},
            {"linha": 74, "motivo": "SKU inexistente"},
        ],
    },
}

pprint(resultado, indent=2, width=70)
```

Aumentar a indentação pode deixar estruturas pequenas mais claras, mas também faz linhas atingirem o limite mais cedo. Em objetos muito profundos, uma indentação de um ou dois espaços é suficiente.

## Limitando níveis com `depth`

Ao inspecionar respostas enormes, talvez você queira ver apenas o formato geral. `depth` limita quantos níveis são expandidos:

```python
from pprint import pprint

resposta = {
    "empresa": {
        "filiais": [
            {
                "cidade": "Belo Horizonte",
                "equipes": [
                    {"nome": "Dados", "projetos": ["fraude", "crédito"]},
                    {"nome": "Plataforma", "projetos": ["api", "observabilidade"]},
                ],
            }
        ]
    }
}

pprint(resposta, depth=2)
```

Nos pontos abaixo do limite, a saída usa `...`. Isso é ótimo para reconhecer chaves de alto nível, mas não altera nem resume o objeto original.

`depth` também não é um mecanismo de segurança. Um segredo localizado nos primeiros níveis ainda será exibido. Para dados sensíveis, sanitize a estrutura antes.

## Ordenando ou preservando a ordem dos dicionários

Por padrão, `pprint` usa `sort_dicts=True`, portanto pode mostrar chaves em ordem alfabética. Esse comportamento deixa saídas reproduzíveis, mas às vezes esconde a ordem semântica em que você montou o dicionário.

```python
from pprint import pprint

etapas = {
    "receber": "arquivo CSV",
    "validar": "campos obrigatórios",
    "transformar": "normalizar datas",
    "salvar": "banco de dados",
}

print("Ordenado por chave:")
pprint(etapas, sort_dicts=True)

print("Ordem de inserção:")
pprint(etapas, sort_dicts=False)
```

Desde o Python 3.7, a ordem de inserção dos dicionários faz parte da especificação da linguagem. Use `sort_dicts=False` quando a sequência em que os campos foram adicionados ajuda a contar a história do processamento. Mantenha o padrão ordenado quando você quer comparar saídas com menos ruído.

## Compactando sequências com `compact`

O argumento `compact=True` tenta colocar vários elementos de sequências na mesma linha quando eles cabem na largura configurada:

```python
from pprint import pprint

codigos_uf = [
    "AC", "AL", "AP", "AM", "BA", "CE", "DF", "ES", "GO",
    "MA", "MT", "MS", "MG", "PA", "PB", "PR", "PE", "PI",
    "RJ", "RN", "RS", "RO", "RR", "SC", "SP", "SE", "TO",
]

pprint(codigos_uf, width=50, compact=True)
```

Esse recurso é útil para listas de valores curtos. Para dicionários profundamente aninhados, a melhoria costuma ser menor, pois a estrutura exige mais quebras.

## Números grandes mais legíveis com `underscore_numbers`

Em versões modernas do Python, `underscore_numbers=True` exibe separadores de sublinhado em inteiros grandes:

```python
from pprint import pprint

metricas = {
    "eventos_processados": 1842500,
    "bytes_recebidos": 9876543210,
}

pprint(metricas, underscore_numbers=True)
```

Saída esperada:

```text
{'bytes_recebidos': 9_876_543_210, 'eventos_processados': 1_842_500}
```

Essa representação é válida em código Python e facilita conferir ordens de grandeza. Ela não altera os inteiros originais e não deve ser usada como formatação localizada de números para usuários finais. Em uma interface brasileira, regras de milhar, decimal e moeda devem ser tratadas na camada de apresentação.

## Como formatar uma resposta JSON

Uma dúvida comum é tentar passar uma string JSON diretamente para `pprint`:

```python
from pprint import pprint

texto_json = '{"cidade":"São Paulo","temperaturas":[18,21,27]}'
pprint(texto_json)
```

O resultado continua sendo uma string entre aspas. `pprint` não analisa JSON automaticamente. Primeiro transforme o texto em objetos Python com `json.loads`:

```python
import json
from pprint import pprint

texto_json = '{"cidade":"São Paulo","temperaturas":[18,21,27]}'
dados = json.loads(texto_json)

pprint(dados, sort_dicts=False)
```

Se você está consumindo uma API HTTP com uma biblioteca que já oferece `.json()`, provavelmente receberá o dicionário pronto. O guia de [APIs com Python](/blog/python-e-apis-consumindo-dados/) apresenta o fluxo completo de requisição, validação e tratamento de erros.

### `pprint` não produz JSON válido

Para gerar JSON indentado, use o módulo `json`:

```python
import json

print(
    json.dumps(
        dados,
        indent=2,
        ensure_ascii=False,
        sort_keys=True,
    )
)
```

A opção `ensure_ascii=False` preserva caracteres como `ã`, `é` e `ç` na saída, em vez de convertê-los para sequências `\uXXXX`. Veja mais exemplos no tutorial de [JSON em Python](/blog/trabalhando-com-json-python/) e no verbete sobre [JSON](/glossario/json/).

A diferença central é:

- `pprint`: representação legível de objetos Python;
- `json.dumps`: serialização de tipos compatíveis para o formato JSON;
- `print`: saída textual genérica;
- `repr`: representação técnica de um objeto, usada internamente por muitas dessas ferramentas.

## Reutilizando opções com `PrettyPrinter`

Se um script imprime várias estruturas com o mesmo padrão, crie uma instância configurada:

```python
from pprint import PrettyPrinter

visualizador = PrettyPrinter(
    indent=2,
    width=72,
    depth=4,
    sort_dicts=False,
    compact=True,
)

visualizador.pprint(pedido)
visualizador.pprint(configuracao)

texto = visualizador.pformat(resultado)
print(texto)
```

Isso evita repetir opções e padroniza o diagnóstico. Uma equipe pode, por exemplo, definir largura 100 para terminais de desenvolvimento e `sort_dicts=False` para preservar a sequência de etapas de pipelines.

## Objetos personalizados, dataclasses e Pydantic

`pprint` trabalha melhor quando o objeto já tem um `repr` informativo. Dataclasses geram uma representação útil por padrão:

```python
from dataclasses import dataclass
from pprint import pprint

@dataclass
class Vaga:
    titulo: str
    empresa: str
    tecnologias: list[str]
    remoto: bool

vaga = Vaga(
    titulo="Pessoa Desenvolvedora Python",
    empresa="Empresa Exemplo",
    tecnologias=["Python", "FastAPI", "PostgreSQL"],
    remoto=True,
)

pprint(vaga)
```

Para modelos de bibliotecas como Pydantic, normalmente vale converter explicitamente para um dicionário quando você quer visualizar os campos:

```python
# Em Pydantic v2:
pprint(modelo.model_dump(), sort_dicts=False)
```

Essa conversão deixa claro o que será inspecionado, mas exige o mesmo cuidado com campos secretos. Senhas e tokens não ficam seguros apenas porque pertencem a um modelo tipado.

Objetos com `__repr__` mal implementado também podem gerar uma saída ruim, longa ou até levantar exceção. Nesse caso, selecione atributos relevantes e monte um dicionário de diagnóstico em vez de imprimir a instância inteira.

## Estruturas recursivas

Uma coleção pode conter referência a si mesma:

```python
from pprint import isrecursive, pprint

itens = ["início"]
itens.append(itens)

print(isrecursive(itens))  # True
pprint(itens)
```

O formatador reconhece a recursão e evita um loop infinito, mostrando uma marca que identifica a referência recursiva. Isso é diferente de apenas ter muitos níveis: em uma estrutura recursiva, algum caminho volta ao próprio objeto.

`isrecursive` pode ajudar durante a investigação de grafos, árvores com ponteiros para pais e estruturas montadas acidentalmente. Ainda assim, a presença de um ciclo não significa necessariamente erro; depende do modelo de dados.

## Mascarando segredos antes de imprimir

Uma função recursiva simples pode ocultar chaves sensíveis em dicionários antes da depuração:

```python
from pprint import pprint
from typing import Any

CHAVES_SENSIVEIS = {
    "authorization",
    "cookie",
    "password",
    "senha",
    "token",
    "access_token",
    "refresh_token",
    "api_key",
}


def mascarar_segredos(valor: Any) -> Any:
    if isinstance(valor, dict):
        return {
            chave: (
                "***"
                if str(chave).lower() in CHAVES_SENSIVEIS
                else mascarar_segredos(item)
            )
            for chave, item in valor.items()
        }

    if isinstance(valor, list):
        return [mascarar_segredos(item) for item in valor]

    if isinstance(valor, tuple):
        return tuple(mascarar_segredos(item) for item in valor)

    return valor


resposta_api = {
    "usuario": "ana",
    "token": "segredo-que-nao-deve-aparecer",
    "preferencias": {"tema": "escuro"},
}

pprint(mascarar_segredos(resposta_api), sort_dicts=False)
```

Em sistemas reais, a lista de campos precisa refletir os dados da aplicação, e valores sensíveis podem aparecer em lugares inesperados — cabeçalhos, URLs, mensagens, objetos ou listas. A política mais segura é registrar somente os campos necessários, não tentar imprimir tudo e depois adivinhar o que deve ser removido.

## `pprint` em logs: quando evitar

É possível fazer isto:

```python
import logging
from pprint import pformat

logger = logging.getLogger(__name__)
logger.debug("Resposta recebida:\n%s", pformat(resposta_api))
```

Durante o desenvolvimento local, a técnica pode ajudar. Em produção, ela tem limitações:

1. transforma um objeto estruturado em um bloco de texto difícil de consultar;
2. pode criar mensagens de muitas linhas;
3. pode aumentar custos de armazenamento e observabilidade;
4. pode vazar informações pessoais ou credenciais;
5. dificulta filtros por campo, como `status`, `pedido_id` ou `duracao_ms`.

Para produção, prefira eventos estruturados com campos selecionados:

```python
logger.info(
    "pedido_processado",
    extra={
        "pedido_id": pedido["id"],
        "meio_pagamento": pedido["pagamento"]["meio"],
        "quantidade_itens": len(pedido["itens"]),
    },
)
```

A configuração concreta depende da biblioteca e da plataforma de logs. O tutorial de [logging em Python](/blog/logging-em-python/) mostra níveis, handlers, formatação e rotação. A regra continua simples: `pprint` é excelente para inspeção humana; logs operacionais devem favorecer pesquisa, métricas e privacidade.

## Exemplo prático: inspecionando dados de uma API

O exemplo abaixo usa apenas uma resposta simulada para permanecer executável sem internet:

```python
import json
from pprint import PrettyPrinter

CORPO_RESPOSTA = """
{
  "fonte": "servico-exemplo",
  "pagina": 1,
  "resultados": [
    {"municipio": "Curitiba", "uf": "PR", "valor": 128.5},
    {"municipio": "Salvador", "uf": "BA", "valor": 142.0}
  ],
  "proxima_pagina": null
}
"""


def carregar_resposta(texto: str) -> dict:
    dados = json.loads(texto)

    if not isinstance(dados.get("resultados"), list):
        raise ValueError("O campo 'resultados' deve ser uma lista")

    return dados


visualizador = PrettyPrinter(
    width=70,
    sort_dicts=False,
    compact=True,
)

dados = carregar_resposta(CORPO_RESPOSTA)
visualizador.pprint(dados)
```

Esse fluxo separa responsabilidades:

- `json.loads` interpreta o formato JSON;
- a função valida a forma mínima esperada;
- `PrettyPrinter` apresenta os objetos Python;
- uma aplicação real decidiria quais campos salvar ou registrar.

Se a resposta tiver milhares de itens, não imprima tudo. Mostre uma amostra e um resumo:

```python
resumo = {
    "fonte": dados["fonte"],
    "total_na_pagina": len(dados["resultados"]),
    "primeiros_resultados": dados["resultados"][:2],
    "tem_proxima_pagina": dados["proxima_pagina"] is not None,
}

visualizador.pprint(resumo)
```

Esse padrão — contagem, amostra pequena e indicadores — costuma revelar o problema sem inundar o terminal.

## Erros comuns

### Passar JSON textual sem usar `json.loads`

`pprint('{"status":"ok"}')` formata uma string, não um dicionário. Converta o JSON primeiro.

### Achar que a saída é JSON

Aspas simples, tuplas, conjuntos, `None` e outros elementos podem aparecer na representação Python. Para integrar com outro sistema, serialize com o módulo apropriado.

### Imprimir objetos enormes

Uma resposta completa, um DataFrame convertido em dicionário ou milhares de registros podem travar o terminal e ocultar o ponto relevante. Use fatias, contagens, `depth` e seleção de campos.

### Registrar credenciais durante a depuração

Respostas HTTP e configurações frequentemente contêm `Authorization`, cookies e tokens. Trabalhe com uma cópia sanitizada e, de preferência, com uma lista explícita dos campos permitidos.

### Depender da aparência exata em testes

Detalhes de representação podem mudar entre versões do Python ou conforme a largura configurada. Quando o teste quer validar dados, compare a estrutura; compare a string formatada apenas quando a formatação for de fato o comportamento sob teste.

## Checklist de decisão

Use **`pprint`** quando:

- estiver explorando uma coleção no REPL;
- quiser enxergar níveis de dicionários e listas;
- precisar de uma saída temporária para depuração local;
- quiser preservar tipos e representações do Python;
- estiver examinando uma amostra pequena de dados.

Use **`pformat`** quando:

- precisar da representação legível como string;
- estiver construindo uma mensagem de diagnóstico;
- quiser testar uma função cuja responsabilidade é produzir texto formatado.

Use **`json.dumps(..., indent=2)`** quando:

- a saída precisa ser JSON válido;
- outro programa consumirá o conteúdo;
- você quer gravar ou transmitir tipos compatíveis com JSON.

Use **logging estruturado** quando:

- o evento será consultado em produção;
- campos precisam ser filtrados, agregados ou correlacionados;
- volume, privacidade e retenção importam.

## Conclusão

`pprint` resolve um problema simples e frequente: transformar coleções Python difíceis de ler em uma representação adequada para inspeção humana. Comece com `pprint(objeto)`, ajuste `width` e `depth` para estruturas maiores, use `sort_dicts=False` quando a ordem de inserção tiver significado e escolha `pformat` quando precisar receber o texto.

O limite mais importante é conceitual: `pprint` não interpreta uma string JSON, não produz necessariamente JSON válido e não torna seguro registrar uma resposta completa. Converta dados com a ferramenta correta, selecione somente o que precisa ser inspecionado e remova informações sensíveis antes de imprimir. Com esses cuidados, o módulo se torna uma ferramenta pequena, mas muito útil, para aprender, automatizar e depurar projetos Python.
