2.26. Sollevare errori#
Una funzione può segnalare un problema al suo chiamante sollevando un’eccezione. La parola chiave è raise:
def square_root(x):
if x < 0:
raise ValueError("square_root expects a non-negative number")
return x ** 0.5
Chiamare square_root(-1) si interrompe alla riga raise, esce dalla funzione e cerca un except corrispondente nel chiamante. Se nessun chiamante la intercetta, lo script termina con un traceback.
2.26.1. Perché sollevare un’eccezione invece di restituire un valore sentinella#
Due modi per segnalare un «input errato»:
# 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
La forma con eccezione è di solito migliore:
Il chiamante deve gestire deliberatamente il caso di errore – o con un
try, oppure lasciando che l’eccezione si propaghi. I valori sentinella sono facili da dimenticare e facili da scambiare per un risultato normale.Il messaggio di errore viaggia insieme all’eccezione; l’approccio con sentinella deve allegare la diagnostica da qualche altra parte.
Il comportamento predefinito in caso di eccezione non gestita è un crash evidente con un traceback che punta alla chiamata incriminata. I ritorni silenziosi di
Nonediventano in seguito bug subdoli.
Ricorri ai valori sentinella solo quando «non trovato» è un esito di routine, non eccezionale – dict.get() restituisce None su una chiave mancante proprio perché ci si aspetta che le ricerche a volte falliscano.
2.26.2. Classi di eccezione personalizzate#
Per sollevare un problema che il chiamante potrebbe voler distinguere dagli errori built-in, definisci una sottoclasse di 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)
Il corpo vuoto della class va bene – ciò che conta è il nome stesso, perché i chiamanti intercettano per classe. Raggruppa gli errori correlati sotto una base comune se un chiamante potrebbe voler intercettare l’intera famiglia in un solo blocco.
2.26.2.1. Risollevare#
Un raise nudo all’interno di un blocco except risolleva l’eccezione corrente in modo che si propaghi al gestore successivo:
try:
do_work()
except Exception as e:
log(e)
raise # let it keep going
Questa è la forma corretta quando una funzione vuole osservare un errore (registrarlo, contarlo, annullare una modifica parziale) senza effettivamente gestirlo.
2.26.3. Quando intercettare e quando propagare#
Una regola pratica utile:
Intercetta un’eccezione al livello che può recuperare in modo significativo – sostituire un valore predefinito, riprovare, saltare l’input errato.
Lasciala propagare quando non c’è nulla di utile da fare se non andare in crash, oppure quando il livello superiore è quello che sa come recuperare.
Una funzione nel mezzo di uno stack di chiamate che inghiotte ogni errore e ritorna in silenzio rende i fallimenti non tracciabili. Preferisci lasciare che le eccezioni viaggino finché non raggiungono il codice che ha davvero un piano.