2.26. Lançando erros¶
Uma função pode sinalizar um problema ao seu chamador lançando uma exceção. A palavra-chave é raise:
def square_root(x):
if x < 0:
raise ValueError("square_root expects a non-negative number")
return x ** 0.5
Chamar square_root(-1) para na linha raise, salta para fora da função e procura um except correspondente no chamador. Se nenhum chamador o capturar, o script termina com um traceback.
2.26.1. Por que lançar em vez de retornar um sentinela¶
Duas formas de relatar “entrada inválida”:
# signal with a sentinel
def square_root_or_none(x):
if x < 0:
return None
return x ** 0.5
# raise an exception
def square_root(x):
if x < 0:
raise ValueError("...")
return x ** 0.5
A forma com exceção costuma ser melhor:
O chamador tem que tratar o caso de erro deliberadamente – seja com um
try, seja deixando a exceção propagar. Sentinelas são fáceis de esquecer e fáceis de confundir com um resultado normal.A mensagem de erro viaja junto com a exceção; a abordagem do sentinela tem que anexar o diagnóstico em algum outro lugar.
O comportamento padrão para uma exceção não tratada é uma falha estrondosa com um traceback que aponta para a chamada ofensora. Retornos silenciosos de
Nonese tornam bugs sutis mais tarde.
Recorra a sentinelas apenas quando “não encontrado” for um resultado rotineiro e não excepcional – dict.get() retorna None em uma chave ausente justamente porque se espera que buscas às vezes falhem.
2.26.2. Classes de exceção personalizadas¶
Para lançar um problema que o chamador talvez queira distinguir dos erros embutidos, defina uma subclasse de Exception:
class ConfigError(Exception):
pass
def load_config(path):
try:
f = open(path)
except OSError as e:
raise ConfigError("missing config file: " + path)
try:
load_config("settings.json")
except ConfigError as e:
print("startup failed:", e)
Um corpo de class vazio está perfeito – o nome em si é o que importa, porque os chamadores capturam por classe. Agrupe erros relacionados sob uma base comum se um chamador puder querer capturar a família inteira em um único bloco.
2.26.2.1. Relançando¶
Um raise simples dentro de um bloco except relança a exceção atual para que ela propague ao próximo tratador:
try:
do_work()
except Exception as e:
log(e)
raise # let it keep going
Esta é a forma correta quando uma função quer observar um erro (registrá-lo, contá-lo, desfazer uma alteração parcial) sem de fato tratá-lo.
2.26.3. Quando capturar e quando propagar¶
Uma regra prática útil:
Capture uma exceção no nível que possa se recuperar de forma significativa – substituir por um padrão, tentar novamente, pular a entrada inválida.
Deixe-a propagar quando não houver nada útil a fazer além de falhar, ou quando a camada acima for a que sabe como se recuperar.
Uma função no meio de uma pilha de chamadas que engole todo erro e retorna silenciosamente torna as falhas impossíveis de rastrear. Prefira deixar as exceções viajarem até alcançarem um código que realmente tenha um plano.