25. Gestire le eccezioni con eleganza
Nel Capitolo 24, abbiamo imparato come leggere e comprendere le eccezioni quando si verificano. Ora impareremo come gestire le eccezioni con eleganza, permettendo ai nostri programmi di recuperare dagli errori invece di andare in crash. Questo è essenziale per scrivere programmi robusti e facili da usare, in grado di affrontare situazioni impreviste.
Quando si verifica un'eccezione in Python, il normale flusso del programma si interrompe immediatamente. Ma cosa succederebbe se potessimo intercettare quell'eccezione prima che mandi in crash il nostro programma? Cosa succederebbe se potessimo rispondere all'errore, magari chiedendo all'utente di riprovare, oppure usando un valore predefinito, o ancora registrando il problema nei log e continuando? È esattamente questo che la gestione delle eccezioni ci consente di fare.
25.1) Usare i blocchi try ed except
25.1.1) La struttura di base di try ed except
Un blocco try-except è il modo di Python di dire: "prova a fare questo e, se si verifica un'eccezione, fai quest'altro invece". La struttura di base è questa:
try:
# Codice che potrebbe sollevare un'eccezione
risky_operation()
except:
# Codice che viene eseguito se si verifica QUALSIASI eccezione
print("Something went wrong!")Il blocco try contiene codice che potrebbe sollevare un'eccezione. Se si verifica un'eccezione in qualsiasi punto del blocco try, Python interrompe immediatamente l'esecuzione del blocco try e passa al blocco except. Se non si verifica alcuna eccezione, il blocco except viene completamente saltato.
Vediamo un esempio concreto. Ricorda dal Capitolo 24 che provare a convertire una stringa non valida in un intero solleva un ValueError:
# Without exception handling - program crashes
user_input = "hello"
number = int(user_input) # ValueError: invalid literal for int() with base 10: 'hello'
print("This line never executes")Ora gestiamo questa eccezione con eleganza:
# With exception handling - program continues
user_input = "hello"
try:
number = int(user_input)
print(f"You entered: {number}")
except:
print("That's not a valid number!")
number = 0 # Use a default value
print(f"Using number: {number}")Output:
That's not a valid number!
Using number: 0Il programma non è andato in crash! Quando int(user_input) ha sollevato un ValueError, Python è passato al blocco except, ha stampato il nostro messaggio di errore, ha impostato un valore predefinito e poi ha continuato con il resto del programma.
Ecco cosa succede passo dopo passo:
Comprendere il "salto" - cosa succede davvero
Quando diciamo che Python "salta" al blocco except, intendiamo che abbandona la normale esecuzione sequenziale. Questo è un cambiamento fondamentale nel modo in cui scorre il tuo programma, non solo un semplice ramo come un'istruzione if. Vediamolo in dettaglio con un esempio concreto:
# Osservare il flusso di esecuzione con le eccezioni
print("1. Starting program")
try:
print("2. Entered try block")
number = int("hello") # Exception happens HERE
print("3. After conversion") # This line NEVER executes
result = number * 2 # This line NEVER executes
print("4. After calculation") # This line NEVER executes
except ValueError:
print("5. In except block - handling the error")
print("6. After try-except block")Output:
1. Starting program
2. Entered try block
5. In except block - handling the error
6. After try-except blockNota che le righe 3 e 4 non vengono mai eseguite! Nel momento in cui int("hello") solleva un ValueError, Python:
- Interrompe immediatamente l'esecuzione del blocco try, esattamente sulla riga in cui si è verificata l'eccezione
- Cerca una clausola except corrispondente che possa gestire questo tipo di eccezione
- Salta a quel blocco except, saltando tutto il codice rimanente nel blocco try
- Continua dopo la struttura try-except una volta completato il blocco except
Questo è fondamentalmente diverso dal normale flusso del programma. Nell'esecuzione normale, Python esegue ogni riga in sequenza. Con un'eccezione, Python abbandona il percorso corrente e prende una strada completamente diversa. Senza gestione delle eccezioni, il programma andrebbe in crash alla riga 2 e terminerebbe. Con la gestione delle eccezioni, il programma si riprende e continua.
Perché questo è importante:
Comprendere questo comportamento di "salto" è cruciale perché:
- Qualsiasi codice dopo l'eccezione nel blocco try viene saltato: non puoi dare per scontato che le righe successive nel blocco try siano state eseguite
- Le variabili potrebbero non essere inizializzate se l'eccezione si verifica prima della loro assegnazione
- Devi pianificare in che stato si trova il tuo programma quando viene eseguito il blocco except
25.1.2) Gestire l'input dell'utente in modo sicuro
Uno degli utilizzi più comuni della gestione delle eccezioni è validare l'input dell'utente. Gli utenti possono digitare qualsiasi cosa e noi dobbiamo gestire gli input non validi con eleganza. Ecco un esempio pratico di un programma che chiede l'età di un utente:
# Input dell'età in modo sicuro con gestione delle eccezioni
print("Please enter your age:")
user_input = input()
try:
age = int(user_input)
print(f"You are {age} years old.")
# Calcola l'anno di nascita (assumendo che l'anno corrente sia 2024)
birth_year = 2024 - age
print(f"You were born around {birth_year}.")
except:
print("Invalid input! Age must be a number.")
print("Using default age of 0.")
age = 0Se l'utente inserisce "25", l'output è:
Please enter your age:
25
You are 25 years old.
You were born around 1999.Se l'utente inserisce "twenty-five", l'output è:
Please enter your age:
twenty-five
Invalid input! Age must be a number.
Using default age of 0.Nota come il programma gestisca l'errore con eleganza invece di andare in crash con un traceback. Questo è molto meglio per l'esperienza utente.
25.1.3) Gestire più operazioni in un blocco try
Puoi mettere più operazioni in un singolo blocco try. Se una qualsiasi di esse solleva un'eccezione, Python passa immediatamente al blocco except. Iniziamo con un esempio semplice:
# Due operazioni nel blocco try
print("Enter a number:")
user_input = input()
try:
number = int(user_input) # Prima operazione - potrebbe sollevare ValueError
result = 100 / number # Seconda operazione - potrebbe sollevare ZeroDivisionError
print(f"100 / {number} = {result}")
except:
print("Something went wrong!")Se l'utente inserisce "hello", l'eccezione si verifica alla prima operazione (conversione). Se l'utente inserisce "0", l'eccezione si verifica alla seconda operazione (divisione). In entrambi i casi, il nostro singolo blocco except la intercetta.
Ora estendiamolo a tre operazioni:
# Più operazioni nel blocco try
print("Enter two numbers to divide:")
numerator_input = input("Numerator: ")
denominator_input = input("Denominator: ")
try:
numerator = int(numerator_input) # Potrebbe sollevare ValueError
denominator = int(denominator_input) # Potrebbe sollevare ValueError
result = numerator / denominator # Potrebbe sollevare ZeroDivisionError
print(f"{numerator} / {denominator} = {result}")
except:
print("Something went wrong with the calculation!")
print("Make sure you enter valid numbers and don't divide by zero.")Se l'utente inserisce "10" e "2":
Enter two numbers to divide:
Numerator: 10
Denominator: 2
10 / 2 = 5.0Se l'utente inserisce "10" e "zero":
Enter two numbers to divide:
Numerator: 10
Denominator: zero
Something went wrong with the calculation!
Make sure you enter valid numbers and don't divide by zero.Se l'utente inserisce "10" e "0":
Enter two numbers to divide:
Numerator: 10
Denominator: 0
Something went wrong with the calculation!
Make sure you enter valid numbers and don't divide by zero.In questo esempio, tre cose diverse potrebbero andare storte: la conversione del numeratore potrebbe fallire, la conversione del denominatore potrebbe fallire oppure la divisione potrebbe fallire (se il denominatore è zero). Il nostro singolo blocco except intercetta tutti questi casi. Tuttavia, questo approccio ha un limite: non possiamo capire quale errore specifico si sia verificato. Affronteremo questo aspetto nella prossima sezione.
25.1.4) Il problema delle clausole except nude
Usare except: senza specificare un tipo di eccezione si chiama clausola except nuda (bare except clause). Anche se intercetta tutte le eccezioni, spesso è troppo generica e può nascondere problemi inattesi. Considera questo esempio:
# Un except nudo intercetta TUTTO - anche cose che non ci aspettiamo
numbers = [10, 20, 30]
try:
index = 5 # Ci aspettiamo IndexError se index è fuori intervallo
value = numbers[index]
print(f"Value at index {index}: {value}")
except:
print("Could not access the list element.")Sembra ragionevole: stiamo cercando di accedere a un elemento di una lista che potrebbe non esistere. Ma cosa succede se c'è un refuso nel nostro codice?
# E se ci fosse un refuso nel nostro codice?
numbers = [10, 20, 30]
try:
index = 2
value = numbrs[index] # Refuso: 'numbrs' invece di 'numbers'
print(f"Value at index {index}: {value}")
except:
print("Could not access the list element.")Output:
Could not access the list element.L'except nudo intercetta il NameError dovuto al refuso e stampa "Could not access the list element", dandoci l'informazione sbagliata su cosa sia andato storto! Pensiamo che l'indice sia fuori intervallo, ma in realtà abbiamo un refuso nel nome della variabile.
Un except nudo intercetta anche KeyboardInterrupt (quando l'utente preme Ctrl+C) e SystemExit (quando il programma tenta di uscire), che di solito non dovrebbero essere intercettati. Per questi motivi, è meglio intercettare eccezioni specifiche, cosa che impareremo a fare nella prossima sezione.
25.2) Intercettare eccezioni specifiche
25.2.1) Specificare i tipi di eccezione
Invece di intercettare tutte le eccezioni con un except nudo, possiamo specificare quali tipi di eccezione vogliamo gestire. Questo rende il nostro codice più preciso e ci aiuta a rispondere in modo appropriato a errori diversi:
# Intercettare un tipo di eccezione specifico
user_input = "hello"
try:
number = int(user_input)
print(f"You entered: {number}")
except ValueError:
print("That's not a valid number!")
number = 0
print(f"Using number: {number}")Output:
That's not a valid number!
Using number: 0Ora la nostra clausola except intercetta solo eccezioni ValueError. Se si verifica un diverso tipo di eccezione (come un NameError dovuto a un refuso), non verrà intercettata e vedremo il traceback completo, che in realtà è utile per il debugging!
La sintassi è: except ExceptionType: dove ExceptionType è il nome della classe dell'eccezione che vuoi intercettare (come ValueError, TypeError, ZeroDivisionError, ecc.).
Errore comune: intercettare il tipo di eccezione sbagliato
Cosa succede se specifichi un tipo di eccezione che non corrisponde a ciò che si verifica davvero? Vediamo:
# Intercettare il tipo di eccezione sbagliato
user_input = "hello"
try:
number = int(user_input) # This raises ValueError
print(f"You entered: {number}")
except TypeError: # Ma stiamo intercettando TypeError!
print("That's not a valid number!")
number = 0
print(f"Using number: {number}")Output:
Traceback (most recent call last):
File "example.py", line 4, in <module>
number = int(user_input)
ValueError: invalid literal for int() with base 10: 'hello'Il programma è andato in crash! Perché? Perché int("hello") solleva un ValueError, ma la nostra clausola except intercetta solo TypeError. Poiché non esiste alcuna clausola except corrispondente, l'eccezione non viene intercettata e il programma termina.
Questo è in realtà utile durante lo sviluppo: se intercetti il tipo di eccezione sbagliato, vedrai il traceback completo e ti accorgerai dell'errore. Questo è uno dei motivi per cui intercettare eccezioni specifiche è meglio che usare un except nudo.
Come evitare questo errore:
- Leggi il traceback per vedere quale tipo di eccezione si è effettivamente verificato
- Usa quel tipo specifico nella tua clausola except
- Se non sei sicuro, esegui il codice e lascia che vada in crash: il traceback te lo dirà!
25.2.2) Gestire eccezioni diverse in modo diverso
Puoi avere più clausole except per gestire tipi diversi di eccezioni in modi diversi. Questo è estremamente utile quando errori diversi richiedono risposte diverse:
# Gestione diversa per eccezioni diverse
print("Enter two numbers to divide:")
numerator_input = input("Numerator: ")
denominator_input = input("Denominator: ")
try:
numerator = int(numerator_input)
denominator = int(denominator_input)
result = numerator / denominator
print(f"{numerator} / {denominator} = {result}")
except ValueError:
print("Error: Both inputs must be valid integers.")
print("You entered something that isn't a number.")
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
print("The denominator must be a non-zero number.")Se l'utente inserisce "10" e "abc":
Enter two numbers to divide:
Numerator: 10
Denominator: abc
Error: Both inputs must be valid integers.
You entered something that isn't a number.Se l'utente inserisce "10" e "0":
Enter two numbers to divide:
Numerator: 10
Denominator: 0
Error: Cannot divide by zero.
The denominator must be a non-zero number.Python controlla ogni clausola except in ordine. Quando si verifica un'eccezione, Python trova la prima clausola except che corrisponde al tipo di eccezione ed esegue quel blocco. Le altre clausole except vengono saltate.
25.2.3) Intercettare più tipi di eccezione in una sola clausola
A volte vuoi gestire diversi tipi di eccezione nello stesso modo. Invece di scrivere più blocchi except identici, puoi intercettare più tipi di eccezione in una singola clausola mettendoli tra parentesi come una tupla:
# Intercettare più tipi di eccezione insieme
print("Enter a number:")
user_input = input()
try:
number = int(user_input)
result = 100 / number
print(f"100 divided by {number} is {result}")
except (ValueError, ZeroDivisionError):
print("Invalid input or division by zero.")
print("Please enter a non-zero number.")Se l'utente inserisce "hello":
Enter a number:
hello
Invalid input or division by zero.
Please enter a non-zero number.Se l'utente inserisce "0":
Enter a number:
0
Invalid input or division by zero.
Please enter a non-zero number.Sia ValueError (da una conversione non valida) sia ZeroDivisionError (da una divisione per zero) vengono gestite dalla stessa clausola except. Questo è utile quando errori diversi dovrebbero attivare la stessa risposta.
25.2.4) Accedere alle informazioni dell'eccezione
A volte hai bisogno di conoscere più dettagli sull'eccezione che si è verificata. Puoi catturare l'oggetto eccezione usando la parola chiave as. Ma prima, capiamo cos'è davvero un oggetto eccezione.
Cos'è un oggetto eccezione?
Quando Python solleva un'eccezione, non si limita a segnalare che qualcosa è andato storto: crea un oggetto che contiene informazioni sull'errore. Questo oggetto eccezione è come un rapporto d'errore dettagliato che include:
- Il messaggio di errore: una descrizione di ciò che è andato storto
- Il tipo di eccezione: che tipo di errore si è verificato (ValueError, TypeError, ecc.)
- Attributi aggiuntivi: informazioni specifiche a seconda del tipo di eccezione
Pensa a un oggetto eccezione come a un contenitore che conserva tutte le informazioni su un errore. Proprio come un oggetto lista contiene elementi e ha metodi come append(), un oggetto eccezione contiene informazioni d'errore e ha attributi a cui puoi accedere.
Quando scrivi except ValueError as error:, stai dicendo a Python: "Se si verifica un ValueError, crea una variabile chiamata error e mettici dentro l'oggetto eccezione così posso esaminarlo."
Esploriamo cosa c'è dentro un oggetto eccezione:
# Esaminare il contenuto di un oggetto eccezione
try:
number = int("hello")
except ValueError as error:
print("Exception caught! Let's examine it:")
print(f"Type: {type(error)}")
print(f"String representation: {error}")
print(f"Args tuple: {error.args}")Output:
Exception caught! Let's examine it:
Type: <class 'ValueError'>
String representation: invalid literal for int() with base 10: 'hello'
Args tuple: ("invalid literal for int() with base 10: 'hello'",)L'oggetto eccezione ha:
- Un tipo (classe ValueError): questo ti dice che tipo di errore si è verificato
- Una rappresentazione come stringa (il messaggio di errore): è ciò che vedi nei traceback
- Un attributo args (una tupla contenente il messaggio e qualsiasi altro argomento): fornisce un accesso strutturato ai dettagli dell'errore
Perché questo è importante:
Tipi diversi di eccezione hanno attributi diversi che forniscono informazioni specifiche. Comprendere la struttura degli oggetti eccezione ti aiuta a estrarre informazioni utili per il debugging o per il feedback all'utente:
# Eccezioni diverse hanno attributi diversi
numbers = [10, 20, 30]
try:
value = numbers[10]
except IndexError as error:
print(f"IndexError message: {error}")
print(f"Exception args: {error.args}")
# Ora proviamo con un dizionario
grades = {"Alice": 95}
try:
grade = grades["Bob"]
except KeyError as error:
print(f"KeyError message: {error}")
print(f"Missing key: {error.args[0]}")Output:
IndexError message: list index out of range
Exception args: ('list index out of range',)
KeyError message: 'Bob'
Missing key: BobNota come KeyError includa nella sua stringa il valore della chiave mancante. Tipi diversi di eccezione forniscono informazioni utili diverse, a cui puoi accedere tramite l'oggetto eccezione.
25.3) Usare else e finally con i blocchi try
25.3.1) La clausola else: codice che viene eseguito solo in caso di successo
La clausola else in un blocco try-except viene eseguita solo se non si è verificata alcuna eccezione nel blocco try. Questo è utile per codice che dovrebbe essere eseguito solo quando l'operazione rischiosa ha successo:
# Usare else per codice da eseguire solo in caso di successo
print("Enter a number:")
user_input = input()
try:
number = int(user_input)
except ValueError:
print("That's not a valid number!")
else:
# Questo viene eseguito solo se int(user_input) è andata a buon fine
print(f"Successfully converted: {number}")
squared = number ** 2
print(f"The square of {number} is {squared}")Se l'utente inserisce "5":
Enter a number:
5
Successfully converted: 5
The square of 5 is 25Se l'utente inserisce "hello":
Enter a number:
hello
That's not a valid number!Perché usare else invece di mettere semplicemente il codice alla fine del blocco try? Ci sono due motivi importanti:
- Chiarezza: la clausola
elserende esplicito che questo codice viene eseguito solo in caso di successo - Ambito delle eccezioni: le eccezioni sollevate nella clausola
elsenon vengono intercettate dalle clausoleexceptprecedenti
Ecco un esempio che mostra perché il secondo punto è importante:
# Dimostrare perché else è utile per l'ambito delle eccezioni
try:
number_1 = int(input("Enter a number_1: "))
except ValueError:
print("Invalid input!")
else:
# Se qui si verifica un errore, non verrà intercettato dall'except sopra
# Questo aiuta a distinguere tra errori di input ed errori di elaborazione
number_2 = int(input("Enter a number_2: ")) # Potrebbe sollevare ValueErrorSe mettessimo number_2 = int(input(...)) nel blocco try insieme a number_1, qualsiasi ValueError proveniente da uno dei due input verrebbe intercettato dalla stessa clausola except ValueError. Questo rende impossibile capire quale input abbia causato il problema.
Mettendo number_2 = int(input(...)) nel blocco else, separiamo la gestione degli errori. La clausola except intercetta solo gli errori di number_1, mentre gli errori di number_2 solleveranno un'eccezione non intercettata con un traceback completo, rendendo chiaro che è fallito il secondo input e non il primo.
25.3.2) La clausola finally: codice che viene sempre eseguito
La clausola finally contiene codice che viene eseguito in ogni caso: che si sia verificata o meno un'eccezione, che sia stata intercettata o no. Questo è essenziale per operazioni di pulizia (cleanup) che devono sempre avvenire:
# Usare finally per la pulizia
print("Enter a number:")
user_input = input()
try:
number = int(user_input)
result = 100 / number
print(f"Result: {result}")
except ValueError:
print("Invalid number!")
except ZeroDivisionError:
print("Cannot divide by zero!")
finally:
print("Calculation attempt completed.")Se l'utente inserisce "5":
Enter a number:
5
Result: 20.0
Calculation attempt completed.Se l'utente inserisce "hello":
Enter a number:
hello
Invalid number!
Calculation attempt completed.Se l'utente inserisce "0":
Enter a number:
0
Cannot divide by zero!
Calculation attempt completed.Il blocco finally viene eseguito in tutti e tre i casi! Questo è il comportamento chiave di finally: viene eseguito sempre, indipendentemente da ciò che è successo nel blocco try.
25.3.3) Combinare try, except, else e finally
Puoi usare tutte e quattro le clausole insieme per creare una gestione completa delle eccezioni:
# Struttura completa di gestione delle eccezioni
print("Enter a number to calculate its reciprocal:")
user_input = input()
try:
# Operazioni rischiose
number = int(user_input)
reciprocal = 1 / number
except ValueError:
# Gestire gli errori di conversione
print("Error: Input must be a valid integer.")
except ZeroDivisionError:
# Gestire la divisione per zero
print("Error: Cannot calculate reciprocal of zero.")
else:
# Codice da eseguire solo in caso di successo
print(f"The reciprocal of {number} is {reciprocal}")
print(f"Verification: {number} × {reciprocal} = {number * reciprocal}")
finally:
# Codice di cleanup che viene sempre eseguito
print("Reciprocal calculation completed.")Se l'utente inserisce "4":
Enter a number to calculate its reciprocal:
4
The reciprocal of 4 is 0.25
Verification: 4 × 0.25 = 1.0
Reciprocal calculation completed.Se l'utente inserisce "hello":
Enter a number to calculate its reciprocal:
hello
Error: Input must be a valid integer.
Reciprocal calculation completed.Se l'utente inserisce "0":
Enter a number to calculate its reciprocal:
0
Error: Cannot calculate reciprocal of zero.
Reciprocal calculation completed.Il flusso di esecuzione è:
- Il blocco
tryviene sempre eseguito per primo - Se si verifica un'eccezione, viene eseguito il blocco
exceptcorrispondente - Se non si verifica alcuna eccezione, viene eseguito il blocco
else(se presente) - Il blocco
finallyviene sempre eseguito per ultimo, indipendentemente da ciò che è successo
25.4) Sollevare eccezioni deliberatamente con raise
25.4.1) Perché sollevare eccezioni?
Finora abbiamo intercettato eccezioni che Python solleva automaticamente. Ma a volte sei tu a dover sollevare deliberatamente un'eccezione nel tuo codice. Questo è utile quando:
- Rilevi una situazione non valida che il tuo codice non può gestire
- Vuoi imporre regole o vincoli
- Vuoi segnalare un errore al codice che ha chiamato la tua funzione
Sollevare un'eccezione è il modo di Python per dire: "Non posso continuare: c'è qualcosa che non va e chi mi ha chiamato deve occuparsene."
La sintassi è semplice: raise ExceptionType("error message")
Ecco un esempio di base:
# Sollevare deliberatamente un'eccezione
age = -5
if age < 0:
raise ValueError("Age cannot be negative!")
print(f"Age: {age}") # This line never executesOutput:
Traceback (most recent call last):
File "example.py", line 5, in <module>
raise ValueError("Age cannot be negative!")
ValueError: Age cannot be negative!Quando Python incontra raise, crea immediatamente un'eccezione e inizia a cercare un blocco except per gestirla. Se non ce n'è uno, il programma termina con un traceback.
25.4.2) Sollevare eccezioni nelle funzioni
Sollevare eccezioni è particolarmente utile nelle funzioni per validare gli input e imporre vincoli:
# Funzione che valida l'input sollevando eccezioni
def calculate_discount(price, discount_percent):
"""Calculate discounted price.
Args:
price: Original price (must be positive)
discount_percent: Discount percentage (must be 0-100)
Returns:
Discounted price
Raises:
ValueError: If inputs are invalid
"""
if price < 0:
raise ValueError("Price cannot be negative!")
if discount_percent < 0 or discount_percent > 100:
raise ValueError("Discount must be between 0 and 100!")
discount_amount = price * (discount_percent / 100)
return price - discount_amount
# Using the function
try:
final_price = calculate_discount(100, 20)
print(f"Final price: ${final_price}")
except ValueError as error:
print(f"Error: {error}")Output:
Final price: $80.0Ora proviamo con input non validi:
# Prezzo non valido
try:
final_price = calculate_discount(-50, 20)
print(f"Final price: ${final_price}")
except ValueError as error:
print(f"Error: {error}")Output:
Error: Price cannot be negative!# Sconto non valido
try:
final_price = calculate_discount(100, 150)
print(f"Final price: ${final_price}")
except ValueError as error:
print(f"Error: {error}")Output:
Error: Discount must be between 0 and 100!Sollevando eccezioni, la funzione comunica chiaramente cosa è andato storto. Il codice chiamante può poi decidere come gestire l'errore, magari chiedendo all'utente un nuovo input, usando valori predefiniti o registrando l'errore.
25.4.3) Scegliere il tipo di eccezione giusto
Python ha molti tipi di eccezioni built-in, e scegliere quello giusto rende il tuo codice più chiaro. Ecco le eccezioni più comunemente usate per la validazione:
- ValueError: usala quando un valore ha il tipo giusto ma un valore non appropriato (ad es. età negativa, percentuale non valida)
- TypeError: usala quando un valore è del tipo completamente sbagliato (ad es. stringa invece di numero)
- KeyError: usala quando una chiave di dizionario non esiste
- IndexError: usala quando un indice di una sequenza è fuori intervallo
Ecco un esempio che mostra diversi tipi di eccezione:
# Usare tipi di eccezione appropriati
def get_student_grade(grades, student_name):
"""Get a student's grade from the grades dictionary.
Args:
grades: Dictionary mapping student names to grades
student_name: Name of the student
Returns:
The student's grade
Raises:
TypeError: If grades is not a dictionary
KeyError: If student_name is not in grades
ValueError: If the grade is invalid
"""
if not isinstance(grades, dict):
raise TypeError("Grades must be a dictionary!")
if student_name not in grades:
raise KeyError(f"Student '{student_name}' not found!")
grade = grades[student_name]
if not (0 <= grade <= 100):
raise ValueError(f"Invalid grade: {grade} (must be 0-100)")
return grade
# Test con dati validi
grades = {"Alice": 95, "Bob": 87, "Carol": 92}
try:
grade = get_student_grade(grades, "Alice")
print(f"Alice's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
print(f"Error: {error}")Output:
Alice's grade: 95# Test con studente mancante
try:
grade = get_student_grade(grades, "David")
print(f"David's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
print(f"Error: {error}")Output:
Error: Student 'David' not found!# Test con tipo sbagliato
try:
grade = get_student_grade("not a dict", "Alice")
print(f"Alice's grade: {grade}")
except (TypeError, KeyError, ValueError) as error:
print(f"Error: {error}")Output:
Error: Grades must be a dictionary!Usare il tipo di eccezione appropriato aiuta altri programmatori (e te stesso in futuro) a capire che tipo di errore si è verificato.
25.4.4) Sollevare di nuovo (re-raise) le eccezioni
A volte vuoi intercettare un'eccezione, fare qualcosa (come registrare un log) e poi lasciare che l'eccezione continui a propagarsi. Puoi farlo usando raise senza argomenti all'interno di un blocco except:
# Sollevare di nuovo un'eccezione dopo il logging
def divide_numbers(a, b):
"""Divide two numbers with error logging."""
try:
result = a / b
return result
except ZeroDivisionError:
print("ERROR LOG: Division by zero attempted")
print(f" Numerator: {a}, Denominator: {b}")
raise # Solleva di nuovo la stessa eccezione
# Usare la funzione
try:
result = divide_numbers(10, 0)
print(f"Result: {result}")
except ZeroDivisionError:
print("Cannot divide by zero!")Output:
ERROR LOG: Division by zero attempted
Numerator: 10, Denominator: 0
Cannot divide by zero!L'istruzione raise senza argomenti solleva di nuovo l'eccezione che è stata appena intercettata. Questo è utile quando vuoi:
- Registrare o salvare l'errore
- Fare un po' di cleanup
- Lasciare che l'errore si propaghi al chiamante
25.4.5) Sollevare eccezioni a partire da eccezioni
A volte vuoi sollevare una nuova eccezione mentre ne stai gestendo un'altra, preservando il contesto dell'errore originale. Python 3 fornisce la sintassi raise ... from ... per questo:
# Sollevare una nuova eccezione a partire da una esistente
def load_config(config_dict, key):
"""Load configuration value from dictionary."""
try:
config_value = config_dict[key]
# Provare a interpretarlo come intero
parsed_value = int(config_value)
return parsed_value
except KeyError as error:
raise RuntimeError(f"Configuration key missing: {key}") from error
except ValueError as error:
raise RuntimeError(f"Invalid configuration format for {key}") from error
# Usare la funzione
config = {"timeout": "30", "retries": "5"}
try:
value = load_config(config, "timeout")
print(f"Config value: {value}")
except RuntimeError as error:
print(f"Configuration error: {error}")
print(f"Original cause: {error.__cause__}")Output:
Config value: 30Se la chiave non esiste:
try:
value = load_config(config, "missing_key")
print(f"Config value: {value}")
except RuntimeError as error:
print(f"Configuration error: {error}")
print(f"Original cause: {error.__cause__}")Output:
Configuration error: Configuration key missing: missing_key
Original cause: 'missing_key'La parola chiave from collega la nuova eccezione a quella originale. Questo crea una catena di eccezioni che aiuta nel debugging: puoi vedere sia cosa è andato storto a un livello alto (errore di configurazione) sia quale fosse la causa sottostante (chiave non trovata).
La gestione delle eccezioni è uno degli strumenti più importanti per scrivere programmi affidabili. Usando blocchi try-except, puoi anticipare i problemi, gestirli con eleganza e offrire un'esperienza migliore ai tuoi utenti. Ricorda:
- Usa
try-exceptper gestire con eleganza gli errori previsti - Intercetta tipi di eccezione specifici invece di usare
exceptnudo - Usa
elseper codice che dovrebbe essere eseguito solo in caso di successo - Usa
finallyper codice di cleanup che deve essere eseguito sempre - Solleva eccezioni nel tuo codice per segnalare problemi
- Scegli tipi di eccezione appropriati per rendere chiari gli errori
- Fornisci messaggi di errore utili che spieghino cosa è andato storto