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:
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:
{'id': 1842, 'cliente': {'nome': 'Ana Souza', 'cidade': 'Recife', 'uf': 'PE'}, 'itens': [{'sku': 'CAF-500', ...}], ...}
Com pprint, a estrutura fica evidente:
from pprint import pprint
pprint(pedido)
A saída será parecida com esta:
{'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.
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:
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.
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:
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:
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.
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:
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:
from pprint import pprint
metricas = {
"eventos_processados": 1842500,
"bytes_recebidos": 9876543210,
}
pprint(metricas, underscore_numbers=True)
Saída esperada:
{'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:
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:
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 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:
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 e no verbete sobre 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:
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:
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:
# 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:
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:
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:
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:
- transforma um objeto estruturado em um bloco de texto difícil de consultar;
- pode criar mensagens de muitas linhas;
- pode aumentar custos de armazenamento e observabilidade;
- pode vazar informações pessoais ou credenciais;
- dificulta filtros por campo, como
status,pedido_idouduracao_ms.
Para produção, prefira eventos estruturados com campos selecionados:
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 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:
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.loadsinterpreta o formato JSON;- a função valida a forma mínima esperada;
PrettyPrinterapresenta 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:
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.