Módulo 16 - Projeto final: mini biblioteca de utilidades
Exceções próprias e logging
10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026
O que você vai aprender
- Projetar uma hierarquia de exceções próprias para a biblioteca.
- Levantar o erro certo na lógica de preço.
- Configurar e usar o módulo logging em vez de print.
- Escolher o nível de log adequado para cada evento.
Ouvir o resumo desta aula
Um recap de cerca de 2 minutos na voz do Valim, para ouvir no trânsito ou na academia.
Ler a transcrição do resumo
Resumo da aula: Exceções próprias e logging.
Os objetivos desta aula. Projetar uma hierarquia de exceções próprias para a biblioteca. Levantar o erro certo na lógica de preço. Configurar e usar o módulo logging em vez de print. Escolher o nível de log adequado para cada evento.
Veja o essencial, parte por parte.
A hierarquia de exceções do catalogo. Uma exceção base do domínio permite capturar todos os erros da biblioteca de uma vez.
Juntando tudo na lógica de preço. Agora a lógica de preço reúne as peças das aulas anteriores.
logging no lugar do print. Falta a biblioteca contar o que faz.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
A hierarquia de exceções do catalogo
Uma biblioteca bem feita não deixa vazar erros genéricos e confusos; ela define os seus próprios. O desenho recomendado é uma hierarquia: uma exceção base, digamos CatalogoError, da qual todas as outras herdam, e filhas específicas para cada tipo de falha. Assim quem usa a biblioteca ganha uma escolha valiosa: pode capturar um erro exato, como PrecoInvalidoError, para tratar aquele caso, ou capturar a base CatalogoError para pegar qualquer problema da biblioteca de uma vez. É a flexibilidade que só uma hierarquia própria oferece.
# src/catalogo/erros.py
class CatalogoError(Exception):
"""Erro base da biblioteca catalogo."""
class PrecoInvalidoError(CatalogoError):
"""Preco ou percentual fora do permitido."""
class ProdutoNaoEncontradoError(CatalogoError):
"""Produto procurado nao existe no catalogo."""
# Quem usa pode escolher a granularidade:
# except PrecoInvalidoError -> trata so esse caso
# except CatalogoError -> trata qualquer erro da bibliotecaA hierarquia de exceções: uma base CatalogoError e filhas específicas para cada falha.
Note que herdamos de Exception, não de BaseException, que é a recomendação para erros de aplicação. As docstrings curtas em cada classe documentam quando cada erro acontece, o que ajuda quem lê. Com a hierarquia definida no módulo erros, o resto da biblioteca passa a levantar esses tipos específicos em vez de um ValueError genérico. A diferença para quem usa é enorme: em vez de tentar adivinhar pela mensagem qual foi o problema, o tipo da exceção já diz, e o código de tratamento fica limpo e preciso.
Juntando tudo na lógica de preço
Agora a lógica de preço reúne as peças das aulas anteriores. Ela recebe um Produto, que já sabemos válido, aplica um desconto validado pelo decorador de faixa, e levanta a exceção própria quando um valor não faz sentido. O módulo precos importa os modelos, os erros e a infra, e monta a função pública da biblioteca. Repare como cada camada confia na anterior: o modelo garantiu o produto, o decorador protege o percentual, e a exceção comunica a falha com clareza. É a arquitetura da primeira aula ganhando vida.
# src/catalogo/precos.py
from decimal import Decimal
from .modelos import Produto
from .erros import PrecoInvalidoError
def preco_com_desconto(produto: Produto, percentual: Decimal) -> Decimal:
if not (0 <= percentual <= 100):
raise PrecoInvalidoError(f"percentual invalido: {percentual}")
fator = (Decimal("100") - percentual) / Decimal("100")
return (produto.preco * fator).quantize(Decimal("0.01"))
# Uso:
# from catalogo import Produto, Categoria, preco_com_desconto
# preco_com_desconto(cafe, Decimal("10")) -> Decimal("17.01")A lógica de preço junta modelo, exceção própria e Decimal, levantando o erro certo do domínio.
O uso de quantize com dois dígitos garante o arredondamento correto para centavos, coerente com a escolha do Decimal para dinheiro. E a exceção levantada é a PrecoInvalidoError do domínio, não um erro genérico. Se você importar essa função pela fachada do pacote, ela oferece uma interface limpa: recebe um produto e um percentual, devolve o preço final, e falha com um erro nomeado se algo estiver errado. É exatamente o tipo de contrato claro que uma boa biblioteca entrega.
logging no lugar do print
Falta a biblioteca contar o que faz. A tentação do iniciante é espalhar print para diagnosticar, mas isso é problemático: você não controla o que aparece, para onde vai, nem consegue desligar sem editar o código. A solução profissional é o módulo logging. Com ele, você registra eventos com um nível de severidade e decide, na configuração, o que exibir. Uma biblioteca bem-educada cria um logger com o próprio nome e apenas registra; quem usa a biblioteca decide como e onde esses registros aparecem. Isso separa o diagnóstico da apresentação.
# src/catalogo/precos.py (com logging)
import logging
from decimal import Decimal
from .modelos import Produto
from .erros import PrecoInvalidoError
logger = logging.getLogger(__name__) # logger com o nome do modulo
def preco_com_desconto(produto: Produto, percentual: Decimal) -> Decimal:
if not (0 <= percentual <= 100):
logger.error("percentual invalido: %s", percentual)
raise PrecoInvalidoError(f"percentual invalido: {percentual}")
logger.info("desconto de %s%% em %s", percentual, produto.nome)
fator = (Decimal("100") - percentual) / Decimal("100")
return (produto.preco * fator).quantize(Decimal("0.01"))A biblioteca cria um logger com getLogger(__name__) e registra eventos com o nível certo.
| Nível | Quando usar | Exemplo no catalogo |
|---|---|---|
| DEBUG | Detalhe fino para depurar | Valores intermediários do cálculo |
| INFO | Evento normal esperado | Desconto aplicado com sucesso |
| WARNING | Algo estranho, mas não fatal | Percentual muito alto, perto do limite |
| ERROR | Falha que impede a operação | Percentual inválido antes de levantar erro |
Os níveis de log e quando cada um se aplica na biblioteca.
Escolher o nível certo é o que torna o log útil. Um evento normal, como um desconto aplicado, é info. Uma situação suspeita que não impede a operação é warning. Uma falha que quebra a operação é error, e costuma acompanhar o levantamento de uma exceção. Usar getLogger com o nome do módulo, em vez de configurar o log dentro da biblioteca, é a etiqueta correta: a biblioteca registra, o aplicativo que a usa decide o formato e o destino. Com isso, o catalogo está completo por dentro. Falta prová-lo, e é o que a última aula faz com testes.
Teste rápido
Qual a vantagem de uma hierarquia de exceções com uma base CatalogoError?
Perguntas frequentes
- Por que criar exceções próprias em vez de usar ValueError?
- Porque exceções próprias dão a cada falha um tipo específico do seu domínio, que quem usa pode capturar com precisão. Com uma hierarquia, dá para tratar um erro exato ou todos os da biblioteca de uma vez. Um ValueError genérico obriga a adivinhar o problema pela mensagem, o que é frágil.
- Por que herdar de Exception e não de BaseException?
- BaseException é a raiz de tudo, incluindo interrupções do sistema como a de teclado. Erros de aplicação devem herdar de Exception, para não capturar acidentalmente essas interrupções especiais. Sua exceção base do domínio herda de Exception, e as filhas herdam dela.
- Por que não usar print para diagnóstico numa biblioteca?
- Porque o print escreve sempre na saída padrão, sem níveis nem controle, e não dá para desligá-lo sem editar o código. O logging registra com níveis de severidade e deixa quem usa a biblioteca decidir o que aparece e onde. É a diferença entre diagnóstico controlável e ruído fixo.
- Por que usar getLogger(__name__) na biblioteca?
- Porque cria um logger nomeado conforme o módulo, sem configurar o sistema de log inteiro. A biblioteca apenas registra; o aplicativo que a usa configura formato, nível e destino. Configurar o log dentro da biblioteca seria invadir uma decisão que pertence a quem a utiliza.
- Como escolho entre warning e error?
- Use warning para algo estranho que não impede a operação de continuar, como um valor perto de um limite. Use error para uma falha que interrompe a operação, geralmente acompanhando o levantamento de uma exceção. O nível comunica a gravidade a quem lê os registros depois.
Fontes
Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.