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.