Python & AI Tutorials Logo
Programmazione Python

24. Comprendere errori e traceback

Gli errori sono una parte inevitabile della programmazione. Ogni programmatore, dal principiante all’esperto, li incontra regolarmente. La differenza tra lottare con gli errori e imparare da essi sta nel capire cosa Python sta cercando di dirti quando qualcosa va storto.

Quando Python incontra un problema nel tuo codice, non si limita a fermarsi in silenzio: fornisce informazioni dettagliate su cosa è andato storto, dove è successo e spesso suggerisce perché. Imparare a leggere e interpretare questi messaggi di errore è una delle competenze più preziose che puoi sviluppare come programmatore.

In questo capitolo esploreremo le due principali categorie di errori che incontrerai: errori di sintassi (problemi legati a come hai scritto il codice) ed eccezioni a runtime (problemi che si verificano mentre il codice è in esecuzione). Impareremo a leggere i traceback—i report di errore dettagliati di Python—e a capire come le eccezioni cambiano il flusso normale del tuo programma. Soprattutto, svilupperemo una mentalità orientata al debugging che tratta gli errori non come fallimenti, ma come informazioni preziose che ti aiutano a scrivere codice migliore.

24.1) Errori di sintassi vs eccezioni a runtime

Python distingue tra due tipi di problemi fondamentalmente diversi nel tuo codice: errori di sintassi ed eccezioni a runtime. Comprendere questa distinzione ti aiuta a diagnosticare i problemi più rapidamente e a sapere dove cercare soluzioni.

24.1.1) Cosa sono gli errori di sintassi

Un errore di sintassi si verifica quando Python non riesce a capire il tuo codice perché viola le regole grammaticali del linguaggio. Proprio come "The cat sat on the" è una frase inglese incompleta, un codice con errori di sintassi è Python incompleto o malformato che l’interprete non riesce ad analizzare.

Gli errori di sintassi vengono rilevati prima che il programma venga eseguito. Python legge prima l’intero script, verificando che rispetti le regole del linguaggio. Se trova un errore di sintassi, si rifiuta di eseguire qualsiasi parte del codice—anche le parti corrette.

Ecco un esempio semplice:

python
# AVVISO: Errore di sintassi - solo a scopo dimostrativo
# ERRORE: Due punti mancanti dopo l'istruzione if
age = 25
if age >= 18
    print("You are an adult")

Quando provi a eseguire questo codice, Python segnala immediatamente:

  File "example.py", line 3
    if age >= 18
                ^
SyntaxError: expected ':'

Nota diverse caratteristiche chiave di questo messaggio di errore:

  1. File e numero di riga: Python ti dice esattamente dove ha trovato il problema (line 3)
  2. Indicatore visivo: il simbolo di accento circonflesso (^) indica dove Python si è confuso
  3. Tipo di errore: SyntaxError identifica chiaramente che si tratta di un problema di grammatica
  4. Suggerimento utile: expected ':' ti dice cosa manca

Il codice non viene mai eseguito perché Python non può nemmeno iniziare a eseguirlo: la sintassi non è valida.

Vediamo un altro errore di sintassi comune:

python
# AVVISO: Errore di sintassi - solo a scopo dimostrativo
# ERRORE: Parentesi non corrispondenti
numbers = [1, 2, 3, 4, 5]
total = sum(numbers
print(f"Total: {total}")

Python segnala:

  File "example.py", line 2
    total = sum(numbers
               ^
SyntaxError: '(' was never closed

Qui, Python ha rilevato che abbiamo aperto una parentesi alla riga 2 ma non l’abbiamo mai chiusa. L’errore viene riportato alla riga 2 (dove si trova la parentesi non chiusa) e l’accento circonflesso indica dove Python si aspettava di trovare la parentesi di chiusura.

Caratteristiche chiave degli errori di sintassi:

  • Rilevati prima che venga eseguito qualsiasi codice
  • Impediscono l’esecuzione dell’intero programma
  • Di solito indicano refusi, punteggiatura mancante o indentazione errata
  • La posizione dell’errore potrebbe essere leggermente dopo l’errore effettivo

24.1.2) Cosa sono le eccezioni a runtime

Un’eccezione a runtime (o semplicemente "eccezione") si verifica quando un codice sintatticamente corretto incontra un problema durante l’esecuzione. Il codice è Python grammaticalmente valido, ma qualcosa va storto quando il programma viene effettivamente eseguito.

A differenza degli errori di sintassi, le eccezioni avvengono mentre il programma è in esecuzione. Python ha analizzato correttamente il codice e ha iniziato a eseguirlo, ma poi ha incontrato una situazione che non è riuscito a gestire.

Ecco un esempio semplice:

python
# Questo codice ha sintassi valida ma solleverà un'eccezione
numbers = [10, 20, 30]
print(numbers[0])  # Output: 10
print(numbers[5])  # Questa riga solleverà un IndexError
print("This line never executes")

Output:

10
Traceback (most recent call last):
  File "example.py", line 3, in <module>
    print(numbers[5])
          ~~~~~~~^^^
IndexError: list index out of range

Nota cosa è successo:

  1. La prima istruzione print è stata eseguita correttamente (abbiamo visto 10)
  2. La seconda print ha provato ad accedere all’indice 5, che non esiste
  3. Python ha sollevato un’eccezione IndexError
  4. Il programma si è fermato e la terza print non è mai stata eseguita

Il codice era sintatticamente corretto: Python non ha avuto problemi a capire cosa volevamo fare. Il problema è emerso durante l’esecuzione quando abbiamo provato ad accedere a un elemento di una lista (list) che non esisteva.

Ecco un altro esempio che mostra un tipo diverso di eccezione a runtime:

python
# Sintassi valida, ma divisione per zero a runtime
def calculate_average(total, count):
    return total / count
 
# Queste funzionano bene
print(calculate_average(100, 4))  # Output: 25.0
print(calculate_average(75, 3))   # Output: 25.0
 
# Questa solleva un'eccezione
print(calculate_average(50, 0))   # ZeroDivisionError

Output:

25.0
25.0
Traceback (most recent call last):
  File "example.py", line 8, in <module>
    print(calculate_average(50, 0))
          ^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 2, in calculate_average
    return total / count
           ~~~~~~^~~~~~~
ZeroDivisionError: division by zero

La funzione (function) ha funzionato perfettamente due volte, ma alla terza chiamata abbiamo passato 0 come conteggio, causando una divisione per zero. Python non poteva rilevare questo problema finché il codice non veniva effettivamente eseguito con quei valori specifici.

Caratteristiche chiave delle eccezioni a runtime:

  • Si verificano durante l’esecuzione del programma
  • Il codice è sintatticamente valido
  • Spesso dipendono da dati o condizioni specifiche
  • Il programma viene eseguito fino al punto in cui si verifica l’eccezione
  • Input diversi potrebbero causare eccezioni diverse (o nessuna eccezione)

24.1.3) Confrontare errori di sintassi ed eccezioni a runtime

Vediamo entrambi i tipi di errori fianco a fianco per capirne le differenze:

python
# Esempio 1: Errore di sintassi
# ERRORE: Virgolette di chiusura mancanti
print("Program started!")
message = "Hello, world
print(message)

Questo produce immediatamente un errore di sintassi:

  File "example.py", line 4
    message = "Hello, world
              ^
SyntaxError: unterminated string literal (detected at line 4)

Importante: Nota che non vedi "Program started!" nell’output. Python ha rilevato l’errore di sintassi prima di eseguire qualsiasi riga di codice.

Ora confrontalo con un’eccezione a runtime:

python
# Esempio 2: Eccezione a runtime
# Sintassi valida, ma la variabile non esiste
print("Program started!")
message = "Hello, world"
print(mesage)  # Refuso: 'mesage' invece di 'message'

Output:

Program started!
Traceback (most recent call last):
  File "example.py", line 5, in <module>
    print(mesage)
          ^^^^^^
NameError: name 'mesage' is not defined

Importante: Questa volta vedi "Program started!" nell’output. Python ha eseguito correttamente le prime due print e le istruzioni di assegnazione (righe 3-4), ma ha incontrato un problema alla riga 5 cercando di trovare mesage.

La differenza chiave: nel primo esempio, Python non ha nemmeno provato a eseguire il codice: ha trovato l’errore di sintassi durante il parsing. Nel secondo esempio, Python ha avviato correttamente l’esecuzione del programma e ha eseguito diverse righe prima di incontrare l’errore a runtime.

No

No

Python legge il tuo codice

Sintassi valida?

Errore di sintassi
Il programma non parte mai

Il programma inizia l'esecuzione

Problema durante
l'esecuzione?

Il programma termina
correttamente

Eccezione a runtime
Il programma si ferma

24.2) Tipi comuni di eccezioni built-in

Python ha molti tipi di eccezioni integrate, ciascuno dei quali rappresenta un tipo specifico di problema. Imparare a riconoscere queste eccezioni comuni ti aiuta a capire rapidamente cosa è andato storto e come risolverlo. Ogni tipo di eccezione ha un nome descrittivo che suggerisce il problema.

24.2.1) NameError: usare nomi non definiti

Un NameError si verifica quando provi a usare una variabile, una funzione (function) o un altro nome che Python non riconosce. Di solito significa che hai dimenticato di definire qualcosa, hai scritto male un nome o stai cercando di usare qualcosa prima che venga creato.

python
# Esempio 1: hai dimenticato di definire una variabile
print(greeting)  # NameError: name 'greeting' is not defined

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    print(greeting)
          ^^^^^^^^
NameError: name 'greeting' is not defined

Python ti sta dicendo che non sa cosa sia greeting. Devi crearlo prima:

python
# Versione corretta
greeting = "Hello, Python!"
print(greeting)  # Output: Hello, Python!

Ecco un esempio più sottile con un refuso:

python
# Esempio 2: refuso nel nome della variabile
user_name = "Alice"
age = 30
 
print(f"{username} is {age} years old")  # NameError: name 'username' is not defined

Abbiamo definito user_name (con un underscore) ma abbiamo provato a usare username (senza underscore). Python vede questi come nomi completamente diversi.

24.2.2) TypeError: tipo sbagliato per un’operazione

Un TypeError si verifica quando provi a eseguire un’operazione su un valore del tipo sbagliato. Per esempio, non puoi sommare una stringa a un intero, o chiamare qualcosa che non è una funzione (function).

python
# Esempio 1: mescolare tipi incompatibili
age = 25
message = "You are " + age + " years old"  # TypeError

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    message = "You are " + age + " years old"
              ~~~~~~~~~~~~^~~~~
TypeError: can only concatenate str (not "int") to str

Python ti sta dicendo che l’operatore + può concatenare stringhe con stringhe, ma non stringhe con interi. Devi convertire l’intero in stringa:

python
# Versione corretta
age = 25
message = "You are " + str(age) + " years old"
print(message)  # Output: You are 25 years old

I TypeError si verificano anche quando passi il numero sbagliato di argomenti a una funzione (function):

python
# Esempio 3: numero errato di argomenti
def calculate_area(length, width):
    return length * width
 
area = calculate_area(5)  # TypeError: missing 1 required positional argument

Output:

Traceback (most recent call last):
  File "example.py", line 4, in <module>
    area = calculate_area(5)
TypeError: calculate_area() missing 1 required positional argument: 'width'

La funzione si aspetta due argomenti, ma noi ne abbiamo fornito solo uno.

24.2.3) ValueError: tipo giusto, valore sbagliato

Un ValueError si verifica quando passi un valore del tipo corretto, ma il valore in sé non è appropriato per l’operazione. Il tipo è giusto, ma il valore specifico non ha senso in quel contesto.

python
# Esempio 1: convertire una stringa non valida in intero
user_input = "twenty-five"
age = int(user_input)  # ValueError: invalid literal for int()

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    age = int(user_input)
ValueError: invalid literal for int() with base 10: 'twenty-five'

La funzione int() si aspetta una stringa, e noi le abbiamo dato una stringa—quindi il tipo è corretto. Ma la stringa "twenty-five" non può essere convertita in un intero perché contiene lettere. La stringa "25" invece funzionerebbe:

python
# Versione corretta
user_input = "25"
age = int(user_input)
print(age)  # Output: 25

I ValueError si verificano anche con i metodi delle liste (list):

python
# Esempio 3: rimuovere un elemento inesistente
fruits = ["apple", "banana", "orange"]
fruits.remove("grape")  # ValueError: 'grape' is not in list

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    fruits.remove("grape")
    ~~~~~~~~~~~~~^^^^^^^^^
ValueError: list.remove(x): x not in list

Il metodo remove() si aspetta un valore che esiste nella lista (list). Dovremmo controllare prima:

python
# Versione corretta
fruits = ["apple", "banana", "orange"]
if "grape" in fruits:
    fruits.remove("grape")
else:
    print("Grape not found in list")  # Output: Grape not found in list

24.2.4) IndexError: indice non valido di una sequenza

Un IndexError si verifica quando provi ad accedere a una sequenza (lista, tuple, stringa) usando un indice che non esiste. Ricorda che Python usa l’indicizzazione a partire da zero, e gli indici validi vanno da 0 a len(sequence) - 1.

python
# Esempio 1: indice troppo grande
colors = ["red", "green", "blue"]
print(colors[0])  # Output: red
print(colors[3])  # IndexError: list index out of range

Output:

red
Traceback (most recent call last):
  File "example.py", line 3, in <module>
    print(colors[3])
          ~~~~~~^^^
IndexError: list index out of range

La lista ha tre elementi agli indici 0, 1 e 2. L’indice 3 non esiste. Questo è un errore molto comune quando dimentichi che l’indicizzazione parte da 0:

python
# Versione corretta
colors = ["red", "green", "blue"]
print(colors[2])  # Output: blue (il terzo elemento)

24.2.5) KeyError: chiave di dizionario mancante

Un KeyError si verifica quando provi ad accedere a una chiave di un dizionario che non esiste. A differenza delle liste, dove puoi controllare la lunghezza, i dizionari possono avere qualsiasi chiave, quindi devi verificare che una chiave esista prima di accedervi.

python
# Esempio 1: accesso a una chiave inesistente
student = {
    "name": "Alice",
    "age": 20,
    "major": "Computer Science"
}
 
print(student["name"])   # Output: Alice
print(student["grade"])  # KeyError: 'grade'

Output:

Alice
Traceback (most recent call last):
  File "example.py", line 7, in <module>
    print(student["grade"])
          ~~~~~~~^^^^^^^^^
KeyError: 'grade'

Il dizionario non ha una chiave "grade". Puoi controllare prima se la chiave esiste:

python
# Versione corretta usando 'in'
student = {
    "name": "Alice",
    "age": 20,
    "major": "Computer Science"
}
 
if "grade" in student:
    print(student["grade"])
else:
    print("Grade not available")  # Output: Grade not available

Oppure usare il metodo get(), che restituisce None (o un valore di default) invece di sollevare un errore:

python
# Alternativa usando get()
grade = student.get("grade")
if grade is not None:
    print(f"Grade: {grade}")
else:
    print("Grade not available")  # Output: Grade not available

I KeyError si verificano comunemente quando si elaborano dati con struttura incoerente:

python
# Esempio 2: elaborazione di più record
students = [
    {"name": "Alice", "age": 20, "grade": "A"},
    {"name": "Bob", "age": 21},  # Chiave 'grade' mancante
    {"name": "Carol", "age": 19, "grade": "B"}
]
 
for student in students:
    print(f"{student['name']}: {student['grade']}")  # KeyError su Bob

Output:

Alice: A
Traceback (most recent call last):
  File "example.py", line 7, in <module>
    print(f"{student['name']}: {student['grade']}")
                                ~~~~~~~^^^^^^^^^
KeyError: 'grade'

Usa get() con un valore di default per gestire le chiavi mancanti in modo elegante:

python
# Versione corretta
students = [
    {"name": "Alice", "age": 20, "grade": "A"},
    {"name": "Bob", "age": 21},
    {"name": "Carol", "age": 19, "grade": "B"}
]
 
for student in students:
    grade = student.get("grade", "Not assigned")
    print(f"{student['name']}: {grade}")

Output:

Alice: A
Bob: Not assigned
Carol: B

24.2.6) AttributeError: accesso non valido a un attributo

Un AttributeError si verifica quando provi ad accedere a un attributo o a un metodo che non esiste su un oggetto. Questo succede spesso quando confondi i metodi tra tipi diversi o scrivi male i nomi degli attributi.

python
# Esempio 1: metodo sbagliato per il tipo
numbers = [1, 2, 3, 4, 5]
numbers.append(6)  # Funziona - le liste hanno append()
print(numbers)     # Output: [1, 2, 3, 4, 5, 6]
 
text = "Hello"
text.append("!")   # AttributeError: 'str' object has no attribute 'append'

Output:

[1, 2, 3, 4, 5, 6]
Traceback (most recent call last):
  File "example.py", line 6, in <module>
    text.append("!")
    ^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'append'

Le stringhe non hanno un metodo append() perché sono immutabili. Devi usare la concatenazione o altri metodi delle stringhe:

python
# Versione corretta
text = "Hello"
text = text + "!"  # Concatenazione
print(text)        # Output: Hello!

Gli AttributeError si verificano anche per refusi:

python
# Esempio 2: nome del metodo scritto male
message = "Python Programming"
result = message.uppper()  # AttributeError: 'str' object has no attribute 'uppper'

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    result = message.uppper()
             ^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'uppper'. Did you mean: 'upper'?

Nota che Python 3.10+ spesso suggerisce la grafia corretta! Il metodo corretto è upper():

python
# Versione corretta
message = "Python Programming"
result = message.upper()
print(result)  # Output: PYTHON PROGRAMMING

24.2.7) ZeroDivisionError: divisione per zero

Un ZeroDivisionError si verifica quando provi a dividere un numero per zero, cosa matematicamente indefinita. Questo succede spesso con input dell’utente o valori calcolati che non ti aspettavi fossero zero.

python
# Esempio 1: divisione diretta per zero
result = 10 / 0  # ZeroDivisionError: division by zero

Output:

Traceback (most recent call last):
  File "example.py", line 1, in <module>
    result = 10 / 0
             ~~~^~~
ZeroDivisionError: division by zero

Questo vale anche per la divisione intera e per il modulo:

python
# Esempio 2: altre operazioni di divisione
a = 10 // 0  # ZeroDivisionError
b = 10 % 0   # ZeroDivisionError

Un esempio più realistico riguarda i calcoli:

python
# Esempio 3: calcolare la media
def calculate_average(numbers):
    total = sum(numbers)
    count = len(numbers)
    return total / count
 
scores = [85, 90, 78, 92]
print(calculate_average(scores))  # Output: 86.25
 
empty_scores = []
print(calculate_average(empty_scores))  # ZeroDivisionError

Output:

86.25
Traceback (most recent call last):
  File "example.py", line 9, in <module>
    print(calculate_average(empty_scores))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 4, in calculate_average
    return total / count
           ~~~~~~^~~~~~~
ZeroDivisionError: division by zero

Una lista vuota ha lunghezza 0, causando divisione per zero. Controlla sempre questa condizione:

python
# Versione corretta
def calculate_average(numbers):
    if len(numbers) == 0:
        return 0  # Oppure return None, o sollevare un errore più descrittivo
    
    total = sum(numbers)
    count = len(numbers)
    return total / count
 
scores = [85, 90, 78, 92]
print(calculate_average(scores))  # Output: 86.25
 
empty_scores = []
print(calculate_average(empty_scores))  # Output: 0

Tipi comuni di eccezioni

NameError
Variabile/funzione non definita

TypeError
Tipo sbagliato per l'operazione

ValueError
Tipo giusto, valore sbagliato

IndexError
Indice di sequenza non valido

KeyError
Chiave di dizionario mancante

AttributeError
Attributo/metodo non valido

ZeroDivisionError
Divisione per zero

Comprendere questi tipi di eccezioni comuni ti aiuta a diagnosticare rapidamente i problemi. Quando vedi un’eccezione, il nome del tipo ti dice subito quale categoria di problema si è verificata, e il messaggio di errore fornisce dettagli specifici su cosa è andato storto.

24.3) Leggere e interpretare i traceback in dettaglio

Quando si verifica un’eccezione a runtime, Python non si limita a dirti cosa è andato storto: fornisce un traceback dettagliato che mostra esattamente come il tuo programma è arrivato a quel punto. Imparare a leggere i traceback è essenziale per un debugging efficace. Un traceback è come una scia di briciole di pane che mostra il percorso che il tuo programma ha seguito prima di incontrare l’errore.

24.3.1) Anatomia di un traceback

Iniziamo con un esempio semplice ed esaminiamo ogni parte del traceback:

python
# Programma semplice con un errore
def calculate_discount(price, discount_percent):
    discount_amount = price * (discount_percent / 100)
    final_price = price - discount_amount
    return final_price
 
def process_order(item_price, discount):
    discounted_price = calculate_discount(item_price, discount)
    tax = discounted_price * 0.08
    total = discounted_price + tax
    return total
 
# Programma principale
original_price = "50"  # Ops! Questo dovrebbe essere un numero
discount_rate = 10
final_cost = process_order(original_price, discount_rate)
print(f"Final cost: ${final_cost:.2f}")

Output:

Traceback (most recent call last):
  File "example.py", line 16, in <module>
    final_cost = process_order(original_price, discount_rate)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 8, in process_order
    discounted_price = calculate_discount(item_price, discount)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 2, in calculate_discount
    discount_amount = price * (discount_percent / 100)
                      ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
TypeError: can't multiply sequence by non-int of type 'float'

Scomponiamo ogni componente di questo traceback:

1. L’intestazione: "Traceback (most recent call last):"

Questa riga ti dice che ciò che segue è un traceback: un record delle chiamate di funzione (function). La frase "most recent call last" significa che il traceback è mostrato in ordine cronologico: la prima funzione chiamata appare per prima, e la posizione in cui l’errore si è effettivamente verificato appare per ultima.

2. Lo stack delle chiamate (leggere dall’alto verso il basso):

  File "example.py", line 16, in <module>
    final_cost = process_order(original_price, discount_rate)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Questa è la prima chiamata di funzione nella catena. Mostra:

  • Nome del file: "example.py" - dove si trova il codice
  • Numero di riga: line 16 - la riga esatta che ha effettuato questa chiamata
  • Contesto: in <module> - questo codice è al livello principale (non dentro una funzione)
  • Codice: la riga effettivamente eseguita
  • Evidenziazione: i caratteri ^ indicano la parte specifica della riga coinvolta

Il contesto <module> significa che questo codice è in esecuzione a livello di modulo (la parte principale del tuo script), non all’interno di alcuna funzione.

  File "example.py", line 8, in process_order
    discounted_price = calculate_discount(item_price, discount)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Questa è la seconda chiamata di funzione. La funzione process_order è stata chiamata dalla riga 16, e ora siamo dentro quella funzione alla riga 8, dove chiama calculate_discount.

  File "example.py", line 2, in calculate_discount
    discount_amount = price * (discount_percent / 100)
                      ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~

Qui è dove l’errore si è effettivamente verificato. Ora siamo dentro la funzione calculate_discount alla riga 2, e questa è la riga che ha causato il problema.

3. Il messaggio di errore:

TypeError: can't multiply sequence by non-int of type 'float'

Questo è l’errore effettivo che si è verificato:

  • Tipo di eccezione: TypeError - ti dice la categoria di errore
  • Descrizione: il resto spiega nello specifico cosa è andato storto

In questo caso, Python ci sta dicendo che abbiamo provato a moltiplicare una sequenza (in questo caso, una stringa) per un float, cosa non consentita.

24.3.2) Leggere il traceback dal basso verso l’alto

Anche se il traceback viene stampato in ordine cronologico (dall’alto verso il basso), i programmatori esperti spesso lo leggono dal basso verso l’alto perché l’errore effettivo è in fondo, e le righe sopra mostrano come ci siamo arrivati.

Leggiamo il traceback precedente dal basso verso l’alto:

Passo 1: inizia dal messaggio di errore

TypeError: can't multiply sequence by non-int of type 'float'

"Ok, abbiamo provato a moltiplicare una sequenza per un float. Non è consentito."

Passo 2: guarda dove si è verificato l’errore

  File "example.py", line 2, in calculate_discount
    discount_amount = price * (discount_percent / 100)
                      ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~

"L’errore è avvenuto nella funzione calculate_discount alla riga 2. Stiamo cercando di moltiplicare price per qualcosa."

Passo 3: torna indietro per vedere come ci siamo arrivati

  File "example.py", line 8, in process_order
    discounted_price = calculate_discount(item_price, discount)

"La funzione calculate_discount è stata chiamata da process_order alla riga 8, passando item_price come parametro price."

Passo 4: continua a tornare indietro

  File "example.py", line 16, in <module>
    final_cost = process_order(original_price, discount_rate)

"E process_order è stata chiamata dal programma principale alla riga 16, passando original_price come item_price."

Passo 5: trova la causa principale

Ora possiamo risalire al problema: original_price è "50" (una stringa), che viene passata come item_price a process_order, che la passa come price a calculate_discount, dove proviamo a moltiplicarla per un float. La soluzione è rendere original_price un numero:

python
# Versione corretta
def calculate_discount(price, discount_percent):
    discount_amount = price * (discount_percent / 100)
    final_price = price - discount_amount
    return final_price
 
def process_order(item_price, discount):
    discounted_price = calculate_discount(item_price, discount)
    tax = discounted_price * 0.08
    total = discounted_price + tax
    return total
 
# Programma principale - corretto il tipo
original_price = 50  # Ora è un numero, non una stringa
discount_rate = 10
final_cost = process_order(original_price, discount_rate)
print(f"Final cost: ${final_cost:.2f}")  # Output: Final cost: $48.60

Leggi il traceback

1. Leggi il messaggio di errore
Ultima riga
2. Trova la posizione dell'errore
Ultimo blocco di codice
3. Ripercorri lo stack delle chiamate
Risalendo
4. Formula un'ipotesi
Cosa è andato storto?
5. Verifica e correggi
Testa la soluzione

Comprendere come leggere i traceback li trasforma da muri di testo intimidatori in strumenti di debugging utili. Ogni riga fornisce informazioni preziose sul percorso di esecuzione del tuo programma e, con la pratica, sarai in grado di identificare e risolvere rapidamente i problemi seguendo le indicazioni del traceback.

24.4) Come le eccezioni cambiano il flusso normale di un programma

Quando si verifica un’eccezione, non si limita a fermare il tuo programma: cambia in modo fondamentale come il programma viene eseguito. Comprendere questo comportamento è cruciale per scrivere codice robusto e per capire cosa succede quando si verificano errori.

24.4.1) Flusso normale del programma vs flusso con eccezioni

Nell’esecuzione normale, Python esegue il codice riga per riga, dall’alto verso il basso:

python
# Flusso normale del programma
print("Step 1: Starting calculation")
result = 10 + 5
print(f"Step 2: Result is {result}")
final = result * 2
print(f"Step 3: Final value is {final}")
print("Step 4: Program complete")

Output:

Step 1: Starting calculation
Step 2: Result is 15
Step 3: Final value is 30
Step 4: Program complete

Ogni riga viene eseguita in ordine. Ora vediamo cosa succede quando si verifica un’eccezione:

python
# Flusso del programma con un'eccezione
print("Step 1: Starting calculation")
result = 10 / 0  # Questo solleva ZeroDivisionError
print(f"Step 2: Result is {result}")  # Questo non viene mai eseguito
final = result * 2  # Questo non viene mai eseguito
print(f"Step 3: Final value is {final}")  # Questo non viene mai eseguito
print("Step 4: Program complete")  # Questo non viene mai eseguito

Output:

Step 1: Starting calculation
Traceback (most recent call last):
  File "example.py", line 2, in <module>
    result = 10 / 0
             ~~~^~~
ZeroDivisionError: division by zero

Nota che è stata eseguita solo la prima istruzione di stampa. Non appena l’eccezione si è verificata alla riga 2, Python ha smesso di eseguire il resto del codice. L’eccezione ha interrotto il flusso normale.

24.4.2) Le eccezioni si propagano lungo lo stack delle chiamate

Quando si verifica un’eccezione dentro una funzione, Python non si ferma solo a quella funzione: si propaga (risale) lungo lo stack delle chiamate finché qualcuno non la gestisce o il programma termina.

python
# Esempio 1: eccezione che si propaga attraverso le funzioni
def calculate_average(numbers):
    total = sum(numbers)
    count = len(numbers)
    return total / count  # Potrebbe sollevare ZeroDivisionError
 
def process_scores(score_list):
    print("Processing scores...")
    avg = calculate_average(score_list)
    print(f"Average calculated: {avg}")
    return avg
 
def main():
    print("Program starting")
    scores = []  # Lista vuota
    result = process_scores(scores)
    print(f"Final result: {result}")
    print("Program ending")
 
main()

Output:

Program starting
Processing scores...
Traceback (most recent call last):
  File "example.py", line 18, in <module>
    main()
  File "example.py", line 14, in main
    result = process_scores(scores)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 9, in process_scores
    avg = calculate_average(score_list)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 4, in calculate_average
    return total / count
           ~~~~~~^~~~~~~
ZeroDivisionError: division by zero

Ripercorriamo cosa è successo:

  1. main() ha iniziato l’esecuzione e ha stampato "Program starting"
  2. main() ha chiamato process_scores()
  3. process_scores() ha stampato "Processing scores..."
  4. process_scores() ha chiamato calculate_average()
  5. calculate_average() ha provato a dividere per zero
  6. Si è verificata l’eccezione e si è propagata verso l’alto:
    • calculate_average() si è fermata immediatamente (senza restituire un valore)
    • Il controllo è tornato a process_scores(), ma non in modo normale: l’eccezione ha continuato a propagarsi
    • process_scores() si è fermata immediatamente (la stampa dopo calculate_average() non è mai stata eseguita)
    • Il controllo è tornato a main(), ma di nuovo l’eccezione ha continuato a propagarsi
    • main() si è fermata immediatamente (le stampe dopo process_scores() non sono mai state eseguite)
  7. Il programma è terminato con il traceback

Nessuna delle righe dopo l’eccezione è stata eseguita in nessuna delle funzioni. L’eccezione è “risalita” attraverso tutte le chiamate fino a raggiungere il livello superiore e terminare il programma.

24.5) Mentalità di debugging: trattare gli errori come informazioni, non come fallimenti

Una delle competenze più importanti nella programmazione non è scrivere codice perfetto: è imparare a lavorare in modo efficace con codice imperfetto. Ogni programmatore, indipendentemente dal livello di esperienza, scrive codice che produce errori. La differenza tra programmatori in difficoltà e programmatori efficaci non sta nell’evitare gli errori, ma nel modo in cui reagiscono.

24.5.1) Gli errori non sono fallimenti

Quando stai imparando a programmare, è naturale sentirsi frustrati quando incontri errori. Potresti pensare di aver fatto qualcosa di sbagliato o di non “capirci.” Questo mindset è controproducente e, cosa ancora più importante, inesatto.

Gli errori non sono fallimenti: sono feedback.

Pensa agli errori come a un GPS che ricalcola il percorso. Quando sbagli una svolta, il GPS non dice “Hai fallito!” Dice “Ricalcolo percorso” e ti dà nuove indicazioni. I messaggi di errore di Python funzionano allo stesso modo: ti stanno dicendo che il percorso che hai seguito non ha funzionato e ti stanno fornendo informazioni per aiutarti a trovare un percorso che funzioni.

Considera questo semplice esempio:

python
# Primo tentativo di calcolare la media
def calculate_average(numbers):
    total = sum(numbers)
    average = total / len(numbers)
    return average
 
scores = []
result = calculate_average(scores)
print(f"Average: {result}")

Output:

Traceback (most recent call last):
  File "example.py", line 8, in <module>
    result = calculate_average(scores)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 4, in calculate_average
    average = total / len(numbers)
              ~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero

Questo errore non ti sta dicendo che sei un cattivo programmatore. Ti sta dicendo qualcosa di specifico e utile: “Hai provato a dividere per zero, cosa che succede quando la lista è vuota. Devi gestire quel caso.”

Con queste informazioni, puoi migliorare il tuo codice:

python
# Versione migliorata basata sul feedback dell'errore
def calculate_average(numbers):
    if len(numbers) == 0:
        return 0  # Oppure return None, o sollevare un errore più descrittivo
    
    total = sum(numbers)
    average = total / len(numbers)
    return average
 
scores = []
result = calculate_average(scores)
print(f"Average: {result}")  # Output: Average: 0

L’errore ti ha aiutato a scrivere codice migliore. Senza quell’errore, potresti non esserti reso conto che la tua funzione non era in grado di gestire liste vuote.

24.5.2) Ogni errore ti insegna qualcosa

Ogni errore che incontri ti insegna qualcosa su Python, sul tuo codice o sulla programmazione in generale. Vediamo diversi esempi di cosa ci insegnano errori diversi:

Esempio 1: imparare i tipi

python
# Tentativo di sommare tipi incompatibili
age = 25
message = "You are " + age + " years old"

Output:

TypeError: can only concatenate str (not "int") to str

Cosa insegna: Python ha regole di tipo rigide. Non puoi mescolare stringhe e numeri nella concatenazione. Questo errore ti insegna la compatibilità dei tipi e introduce il concetto di conversione di tipo.

Esempio 2: imparare le strutture dati

python
# Tentativo di accedere a un dizionario come a una lista
student = {"name": "Alice", "age": 20}
first_value = student[0]

Output:

KeyError: 0

Cosa insegna: I dizionari usano chiavi, non indici numerici. Questo errore ti insegna la differenza tra dizionari e liste, e come accedere correttamente ai valori di un dizionario.

Esempio 3: imparare lo scope

python
# Tentativo di usare una variabile prima di definirla
def greet():
    print(f"Hello, {name}!")
 
greet()
name = "Alice"

Output:

NameError: name 'name' is not defined

Cosa insegna: Le variabili devono essere definite prima di essere usate, e l’ordine di esecuzione conta. Questo errore ti insegna lo scope delle variabili e l’importanza dell’inizializzazione.

Ognuno di questi errori fornisce informazioni specifiche e attuabili che ti aiutano a capire Python meglio. Invece di vederli come ostacoli, considerali opportunità di apprendimento.

24.5.3) Abbracciare la mentalità di debugging

I programmatori professionisti passano una parte significativa del loro tempo a fare debugging. Non è un segno di debolezza: è una parte fondamentale del lavoro. I migliori programmatori non sono quelli che non sbagliano mai; sono quelli che:

  1. Si aspettano errori: sanno che gli errori accadranno e non si sorprendono né si scoraggiano
  2. Leggono con attenzione gli errori: estraggono il massimo delle informazioni dai messaggi di errore
  3. Fanno debugging in modo sistematico: seguono un processo logico invece di fare cambiamenti casuali
  4. Imparano dagli errori: usano ogni errore come opportunità per capire Python meglio
  5. Rimangono curiosi: chiedono “Perché è successo?” invece di solo “Come lo risolvo?”

No

No

Incontri un errore

Leggi il messaggio di errore
con attenzione

Comprendi cosa
è andato storto

Formula un'ipotesi
sulla causa

Testa l'ipotesi
con output di debug

Ipotesi
corretta?

Implementa la correzione

Testa la soluzione

Funziona
correttamente?

Impara
dall'esperienza

Procedi
con sicurezza

Ricorda: ogni errore è un’opportunità per imparare qualcosa di nuovo su Python, sulla programmazione o sulla risoluzione dei problemi. Accogli gli errori come feedback prezioso, affrontali in modo sistematico e celebra i tuoi successi nel debugging. Questa mentalità ti accompagnerà per tutto il tuo percorso di programmazione.


Comprendere errori e traceback è fondamentale per diventare un programmatore Python efficace. In questo capitolo, abbiamo imparato a distinguere tra errori di sintassi (problemi nella struttura del codice) ed eccezioni a runtime (problemi durante l’esecuzione), a riconoscere tipi comuni di eccezioni e cosa indicano, a leggere e interpretare traceback dettagliati per trovare la causa principale dei problemi, a capire come le eccezioni cambiano il flusso del programma propagandosi lungo lo stack delle chiamate, e a sviluppare una mentalità orientata al debugging che tratta gli errori come informazioni preziose piuttosto che come fallimenti.

Queste competenze costituiscono le basi per il prossimo capitolo, in cui impareremo a gestire le eccezioni in modo elegante usando i blocchi try ed except, permettendo ai nostri programmi di recuperare dagli errori e continuare a funzionare. Ma prima di poter gestire le eccezioni in modo efficace, dobbiamo comprenderle a fondo—ed è esattamente ciò che abbiamo fatto qui.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai