Evitando Armadilhas Comuns: Tratamento Adequado de Exceções em Python

Vejo muitas pessoas lidando com exceções da maneira errada. Talvez isso se aplique a você também. A seguinte situação soa familiar?
Você está escrevendo algum código, mas sabe que a biblioteca que está usando pode lançar uma exceção. Você não lembra exatamente qual. Neste ponto, é tentador usar os chamados blocos catch-all e seguir para as partes divertidas.
A pior maneira de fazer
O pior que você pode fazer é criar um bloco try-except que captura qualquer coisa. Por catch-all, quero dizer algo como:
try:
...
except:
pass
Blocos catch-all como estes são ruins porque:
- Você não tem ideia de quais outras exceções podem ser lançadas (mais sobre isso depois).
- Estamos escondendo a exceção silenciosamente usando
passem vez de registrar o erro.
Além disso, um except vazio capturará tudo, incluindo KeyboardInterrupt (Ctrl+C), SystemExit e até NameError! Isso significa que o seguinte código não pode ser parado limpo:
from time import sleep
while True:
try:
print("Tente me parar")
sleep(1)
except:
print("Não... me... pare... agora!")
Sinta-se à vontade para testar. Você precisa fechar a janela do terminal ou matar o processo Python para parar este programa.
Uma maneira um pouco melhor de capturar todas as exceções
Em contraste, ao usar except Exception, embora ainda seja uma maneira rápida e suja de capturar muitas exceções, pelo menos você conseguirá parar o processo em execução corretamente:
from time import sleep
while True:
try:
print("Tente me parar")
sleep(1)
except Exception:
print("Ok, eu paro!")
Ao capturar Exception, você não capturará SystemExit, KeyboardInterrupt e outras tais exceções. Por que é isso, você pergunta?
Todas as exceções herdam de uma classe chamada BaseException. Segundo a documentação oficial: "Em uma declaração try com uma cláusula except que menciona uma classe particular, essa cláusula também lida com quaisquer classes de exceção derivadas daquela classe." Um except vazio é equivalente a except BaseException, portanto, capturará todas as exceções possíveis.
Em contraste, a classe Exception é definida como: "Todas as exceções embutidas, não de saída do sistema, são derivadas desta classe. Todas as exceções definidas pelo usuário também devem ser derivadas desta classe."
Fica ainda pior
No exemplo a seguir, usamos a biblioteca os para obter o diretório de trabalho atual. No entanto, meus dedos gordos cometeram um erro de digitação:
import os
try:
working_dir = os.getcdw()
print(working_dir)
except:
print('erro')
Como os.getcdw não é uma função no módulo os, um NameError é lançado. Em vez de falhar, a cláusula except captura o erro, imprime 'erro' e o programa continua apesar do nosso erro flagrante. Infelizmente, este não é solucionável capturando Exception também!
Aparentemente, nosso pequeno truque do passo um não é uma correção para todos os nossos problemas. Então, o que devemos fazer?
Capture apenas o que você pode lidar
Uma frase frequentemente ouvida sobre exceções é: capture o que você pode lidar. Muitos desenvolvedores são tentados a lidar diretamente com exceções, enquanto muitas vezes é melhor deixar a exceção propagar para uma parte do seu programa que pode realmente lidar com ela.
Por exemplo, considere a parte de um editor de texto que abre e carrega arquivos, vamos chamá-la de classe OpenFile. Se o usuário solicitou abrir um arquivo que não existe, você pode lidar diretamente com esse erro ou deixá-lo propagar.
Neste caso, é melhor propagar a exceção para o chamador, porque OpenFile não tem ideia de quão ruim essa exceção é para o chamador. O chamador pode lidar com a situação de várias maneiras:
- Poderia criar um novo arquivo com aquele nome e continuar
- Talvez o chamador precise que o arquivo esteja lá, caso em que pode mostrar uma caixa de diálogo de erro para informar o usuário que este arquivo não existe.
De qualquer forma, não cabe à classe OpenFile decidir o que fazer em caso de FileNotFoundError.
Uma exceção deve sempre ser propagada? Não. Uma possível exceção que pode ser tratada na classe FileOpen é o TimeoutError. Você pode querer tentar novamente algumas vezes, por exemplo, sem incomodar o chamador com o erro. Esta é uma exceção que OpenFile pode lidar, então está OK capturá-la e tentar novamente.
Conclusão
Você nunca deve capturar mais exceções do que pode lidar. Blocos except genéricos são uma receita para bugs e código imprevisível. Em outras palavras: capture o que você pode lidar.
Se você escrever seu código com o mantra 'capture o que você pode lidar' em mente, escrever blocos catch-all está quebrando todas as regras. Então, por favor, pare de fazer isso. Como exercício, você pode revisitar algum código existente e ver se pode ser melhorado com este novo conhecimento!
python
- Dominando Operadores em Python: Guia Completo
- Dominando Erros e Exceções Built-in do Python
- Dominando Exceções Customizadas em Python: Guia Prático
- Obter Data e Hora Atuais em Python: Guia Prático
- Dominando Tratamento Multi-Exceção em Java
- Dominando o Treinamento de CMMS: Engajando Equipes e Aumentando Retenção
- Função Print() do Python: Guia Prático com Exemplos
- Dicionários Python: Adicionando, Atualizando e Gerenciando Pares Chave/Valor
- Print em Python Sem Nova Linha: Dominando o Parâmetro end e Outras Técnicas
- Calculando Médias em Python: Guia Prático