2.26. Lançar 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, sai da função e procura um except correspondente no chamador. Se nenhum chamador o capturar, o script termina com um traceback.
2.26.1. Porquê lançar em vez de devolver um sentinel¶
Duas formas de reportar «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 é geralmente melhor:
O chamador tem de deliberadamente tratar o caso de erro – seja com um
try, seja deixando a exceção propagar. Sentinels são fáceis de esquecer e fáceis de confundir com um resultado normal.A mensagem de erro viaja com a exceção; a abordagem com sentinel tem de anexar o diagnóstico noutro local.
O comportamento predefinido numa exceção não tratada é uma falha evidente com um traceback que aponta para a chamada ofensora. Devoluções silenciosas de
Nonetornam-se bugs subtis mais tarde.
Recorra a sentinels apenas quando «não encontrado» é um resultado rotineiro e não excecional – dict.get() devolve None para uma chave ausente precisamente porque se espera que as pesquisas falhem por vezes.
2.26.2. Classes de exceção personalizadas¶
Para lançar um problema que o chamador possa querer distinguir dos erros incorporados, 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)
O corpo class vazio está correto – o nome em si é o que importa, porque os chamadores capturam por classe. Agrupe erros relacionados sob uma base comum se um chamador quiser capturar toda a família num único bloco.
2.26.2.1. Relançar¶
Um raise simples dentro de um bloco except relança a exceção atual para que se propague para o 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 (registá-lo, contá-lo, desfazer uma alteração parcial) sem realmente o tratar.
2.26.3. Quando capturar e quando propagar¶
Uma regra prática útil:
Capture uma exceção ao nível que pode recuperar de forma significativa – substituir por um valor predefinido, tentar novamente, ignorar a entrada inválida.
Deixe-a propagar quando não há nada útil a fazer exceto falhar, ou quando a camada acima é a que sabe como recuperar.
Uma função no meio de uma pilha de chamadas que engole todos os erros e devolve silenciosamente torna as falhas impossíveis de rastrear. Prefira deixar as exceções propagar até chegarem ao código que genuinamente tem um plano.