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.

09 Sep 2026 12 min de leitura Equipe Python Dev BR

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

RecursoO que fazQuando usar
pprint(objeto)Formata e escreve o objeto em um fluxoInspeção rápida no terminal
pp(objeto)Atalho moderno para pprintREPL e código de diagnóstico
pformat(objeto)Retorna a representação formatada como strMensagens, testes e ferramentas
isreadable(objeto)Indica se a representação pode ser reconstruída com eval em condições adequadasDiagnóstico específico, não validação de segurança
isrecursive(objeto)Detecta referência recursiva na estruturaInvestigar coleções cíclicas
PrettyPrinter(...)Guarda uma configuração reutilizávelMuitas 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:

  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:

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.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:

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.

E
Equipe Python Dev BR

Contribuidor do Python Dev BR

Artigos relacionados