Per chi inizia a programmare, uno stack trace è un muro di testo rosso che annuncia un fallimento. Per chi ha esperienza, è una delle informazioni più utili che un programma possa dare: una mappa precisa del punto in cui le cose sono andate storte e del percorso seguito per arrivarci. Imparare a leggerlo con metodo fa risparmiare una quantità enorme di tempo.
Cos'è uno stack trace
Quando un programma chiama una funzione, che a sua volta ne chiama un'altra, l'interprete tiene traccia di questa catena in una struttura chiamata call stack, la pila delle chiamate. Se si verifica un'eccezione che nessuno gestisce, Python stampa il contenuto della pila in quel momento: è il traceback, il nome che Python dà allo stack trace.
In pratica, il traceback risponde a tre domande:
- che cosa è successo (tipo di eccezione e messaggio);
- dove è successo (file, riga, funzione);
- come ci si è arrivati (la sequenza di chiamate).
Un esempio commentato
Consideriamo questo programma:
def carica_ordini(righe):
return [analizza_riga(r) for r in righe]
def analizza_riga(riga):
codice, quantita = riga.split(";")
return codice, int(quantita)
carica_ordini(["A12;3", "B07;due"])
L'esecuzione produce:
Traceback (most recent call last):
File "ordini.py", line 8, in <module>
carica_ordini(["A12;3", "B07;due"])
File "ordini.py", line 2, in carica_ordini
return [analizza_riga(r) for r in righe]
File "ordini.py", line 6, in analizza_riga
return codice, int(quantita)
ValueError: invalid literal for int() with base 10: 'due'
La prima riga, «most recent call last», è la chiave di lettura: le chiamate sono elencate dalla più vecchia alla più recente. In Python, quindi, la parte più importante è in fondo.
Come leggerlo, nell'ordine giusto
- Parti dall'ultima riga.
ValueError: invalid literal for int() with base 10: 'due'dice che si è tentato di convertire in intero la stringa'due'. - Risali al frame più recente. Riga 6 di
analizza_riga: è lì che l'errore si è manifestato. - Continua a risalire per capire il contesto:
analizza_rigaè stata chiamata dacarica_ordini, che a sua volta è stata chiamata dal codice principale alla riga 8. - Chiediti dove nasce davvero il problema. Non sempre coincide con la riga dell'errore. Qui il codice è corretto: è il dato
"B07;due"a non rispettare il formato atteso. La correzione giusta potrebbe essere validare l'input, non modificare la conversione.
Il tuo codice e quello delle librerie
Nei progetti reali i traceback includono spesso decine di frame di librerie esterne o della libreria standard. Un trucco pratico è cercare i frame che appartengono ai tuoi file. L'errore può manifestarsi in profondità dentro una libreria, ma quasi sempre la causa è un argomento sbagliato passato dal tuo codice. Il frame più in basso tra quelli del tuo progetto è di solito il punto da cui partire.
Eccezioni concatenate
A volte il traceback contiene due blocchi separati da una frase. Le due varianti hanno significati diversi:
| Frase nel traceback | Significato |
|---|---|
| «During handling of the above exception, another exception occurred» | Mentre si gestiva un'eccezione, se n'è verificata un'altra, spesso nel blocco except stesso |
| «The above exception was the direct cause of the following exception» | Un'eccezione è stata rilanciata in modo esplicito con raise ... from ... |
In entrambi i casi conviene leggere tutti i blocchi: il primo contiene spesso la causa originale, il secondo il modo in cui il programma ha reagito.
Le eccezioni più comuni
Riconoscere al volo i tipi più frequenti accelera molto la diagnosi:
NameError: un nome non definito, spesso un refuso o una variabile usata prima di essere assegnata;TypeError: un'operazione su un tipo inadatto, come sommare una stringa e un numero, o una funzione chiamata con argomenti sbagliati;AttributeError: un attributo inesistente; il caso classico è'NoneType' object has no attribute ..., che indica una funzione che ha restituitoNonequando ci si aspettava un oggetto;KeyError: una chiave assente in un dizionario;IndexError: un indice oltre la fine di una lista;ValueError: un tipo corretto ma con un valore non accettabile, come nell'esempio sopra.
Le versioni recenti di Python hanno migliorato molto i messaggi, aggiungendo suggerimenti come «Did you mean...?» e indicatori che sottolineano la parte esatta dell'espressione che ha causato l'errore. Vale la pena leggerli con attenzione.
Traceback nei log
In produzione nessuno vede il terminale: il traceback deve finire nei log. Il modulo logging lo registra automaticamente se, all'interno di un blocco except, usi logger.exception("messaggio"). Un messaggio di log senza traceback, del tipo «Errore durante l'elaborazione», costringe a indovinare; uno con il traceback completo indica subito il punto da esaminare.
In sintesi
- In Python lo stack trace si legge dal basso: l'ultima riga indica tipo e messaggio dell'eccezione.
- Risalendo i frame ricostruisci il percorso che ha portato all'errore.
- La riga in cui l'errore si manifesta non è sempre quella in cui nasce il problema.
- Cerca i frame del tuo codice tra quelli delle librerie e leggi tutti i blocchi delle eccezioni concatenate.
- In produzione registra sempre il traceback completo nei log con
logger.exception.