
Le eccezioni in Python
Le eccezioni in Python sono eventi che si verificano durante l'esecuzione di un programma.
Qual è la differenza tra le eccezioni e gli errori? Gli errori sono problemi nel codice stesso che fanno fallire il programma durante la compilazione (es. un errore di sintassi nel codice), mentre le eccezioni sono situazioni inaspettate che possono interrompere l'esecuzione del programma se non vengono gestite correttamente.
Python ti permette di gestire le eccezioni tramite l'istruzione composta try except.
In questo modo puoi evitare che l'esecuzione del programma venga interrotta.
Un esempio di eccezione
Ad esempio, scrivi questo codice
- x=1
- y=0
- z=x/y
- print(z)
Il codice è scritto sintatitticamente bene in linguaggio Python. Non ci sono errori di sintassi.
Tuttavia, nella terza riga c'è una divisione per zero, ossia un'operazione impossibile dal punto di vista matematico, perché y=0.
In questo caso Python solleva l'eccezione "ZeroDivisionError:".
Traceback (most recent call last):
File "/home/main.py", line 3, in <module>
z=x/y
ZeroDivisionError: division by zero
Le eccezioni non gestite causano l'interruzione dell'esecuzione del programma e la visualizzazione di un messaggio di errore che è composto in due parti.
- Il traceback indica la riga di codice dove è avvenuta l'eccezione
- Il tipo di eccezione che ha causato l'errore (es. ZeroDivisionError, IndexError, ecc.). I vari tipi di eccezioni sono istanze della sottoclassi di una classe built-in BaseException.
Per evitare questo problema, puoi intercettare l'eccezione usando il costrutto try e except.
- try:
- x=1
- y=0
- z=x/y
- print(z)
- except ZeroDivisionError:
- print("Hai provato a dividere per zero!")
In questo caso, quando Python solleva l'eccezione ZeroDivisionError, viene eseguito il blocco di codice dopo la parola chiave "except" e l'esecuzione del programma non viene interrotta.
Quindi, l'output del programma è
Hai provato a dividere per zero
In altre parole, Python ti consente di intercettare e gestire le eccezioni quando si verificano.
Altri esempi di eccezioni sono "TypeError" che viene sollevato quando tenti di eseguire un'operazione su un tipo di dato non appropriato, oppure "FileNotFoundError" che viene sollevata quando provi ad aprire un file inesistente, ecc. Volendo puoi anche definire delle eccezioni personalizzate creando una nuova classe che eredita dalla classe base Exception.
Le eccezioni generiche
Python ti permette anche di catturare tutte le eccezioni con una clausola `except` generica, senza indicare il tipo di errore.
In questo caso tutte le cause di errore sono intercettate.
Ecco un esempio pratico.
import sys
while True:
try:
i = int(input("Inserisci un numero intero: "))
print(10 / i)
except:
error_type, error_instance, _ = sys.exc_info()
print(f"È successo qualcosa di strano... Tipo di errore: {error_type}, Causa: {error_instance}")
print('Il programma continua la sua normale esecuzione')
In questo esempio se si verifica un'eccezione durante l'esecuzione, verranno stampati il tipo e la causa dell'errore, e il programma continuerà a chiedere all'utente di inserire un numero intero.
In questo modo, il programma gestisce tutte le eccezioni in modo generico, non si arresta e fornisce comunque informazioni utili all'utente o per il debugging.
Quando utilizzi le eccezioni generiche l'istruzione sys.exc_info() è molto utile perché memorizza la causa dell'errore e ti permette di capire cosa è andato storto nel programma, senza interromperne l'esecuzione.
La clausola except as
La clausola except as ti permette di assegnare l'eccezione a una variabile.
except as
In questo modo puoi utilizzare nel tuo codice le informazioni contenute nella variabile.
Ad esempio, puoi usare `except` con `as` per catturare specifiche eccezioni e fornire un feedback utile all'utente.
Supponiamo di voler dividere due numeri inseriti dall'utente e gestire eventuali errori che potrebbero verificarsi, come la divisione per zero o l'inserimento di un valore non numerico.
while True:
try:
num1 = float(input("Inserisci il primo numero: "))
num2 = float(input("Inserisci il secondo numero: "))
risultato = num1 / num2
print("Il risultato della divisione è:", risultato)
break
except ValueError as ex:
print("Errore: Valore non numerico inserito. Riprova.", ex)
except ZeroDivisionError as ex:
print("Errore: Divisione per zero. Riprova.", ex)
In questo codice ci sono due clausole except as:
- La clausola except ValueError as ex cattura l'eccezione 'ValueError' e la salva nella variabile 'ex', poi stampa un messaggio di errore mostrando anche l'errore. Ad esempio, viene visualizzato quando l'utente inserisce un valore non numerico.
- La clausola except ZeroDivisionError as ex invece cattura l'eccezione 'ZeroDivisionError' e stampa un altro messaggio di errore personalizzato. Ad esempio, quando l'utente tenta di dividere per zero.
Se provi a digitare i valori 10 e 2 Python calcola e restituisce il risultato della divisione
Il risultato della divisione è: 5.0
Se, invece, digiti in input i valori 10 e `a` il codice Python ti risponde "Errore: Valore non numerico inserito. Riprova.".
Poi visualizza il contenuto della variabile "ex" che in questo caso è "could not convert string to float:".
Errore: Valore non numerico inserito. Riprova. could not convert string to float: 'a'
Infine, se provi a digitare i numeri 10 e 0 il risultato in output è un altro messaggio personalizzato.
Errore: Divisione per zero. Riprova. float division by zero
Questo semplice esempio dimostra come gestire le eccezioni comuni quando il programma richiede input dall'utente ed esegue operazioni che potrebbero fallire.
La clausola finally
La clausola finally ti permette di definire un blocco di codice che verrà eseguito dopo il blocco try e dopo qualsiasi blocco except, indipendentemente dal fatto che un'eccezione sia stata sollevata o meno.
try:
# Codice che potrebbe sollevare un'eccezione
pass
except SomeException as e:
# Codice che gestisce l'eccezione
pass
finally:
# Codice che verrà eseguito sempre, sia in caso di eccezione che no
pass
E' particolarmente utile per il rilascio di risorse o per assicurarsi che certe operazioni vengano completate anche se qualcosa va storto.
Ad esempio, immagina di avere un programma che deve aprire un file, leggere i suoi contenuti e poi chiuderlo. Potrebbero verificarsi vari problemi durante l'apertura o la lettura del file: il file potrebbe non esistere, o potresti non avere i permessi per leggerlo. Anche in presenza di questi problemi, è importante assicurarsi che il file venga sempre chiuso per evitare perdite di risorse.
Vediamo un esempio pratico per capire meglio come funziona.
try:
file = open('esempio.txt', 'r')
print(file.read())
except FileNotFoundError:
print("Errore: Il file non esiste.")
finally:
print("Questo viene eseguito sempre.")
try:
file.close()
except NameError:
pass
Il primo blocco try tenta di aprire e leggere un file.
Se il file non esiste il blocco except stampa un messaggio di errore.
Infine, il blocco finally stampa un messaggio e chiude il file aperto, ignorando l'errore se il file non è stato aperto.
In questo modo la clausola finally ti assicura che il file venga sempre chiuso, indipendentemente dal fatto che il file sia stato letto con successo o meno.
La clausola finally è particolarmente utile nel rilascio delle risorse, quando devi chiudere file, connessioni di rete, database, ecc. Ti garantisce che certe operazioni vengano sempre eseguite anche in caso di errori.
La clausola else
La clausola `else` è una clausola facoltativa che ti permette di eseguire codice solo se non si verificano eccezioni all'interno del blocco try.
try:
# codice che potrebbe sollevare un'eccezione
except ExceptionType:
# codice per gestire l'eccezione
else:
# codice che viene eseguito se non viene sollevata alcuna eccezione
finally:
# codice che viene eseguito indipendentemente dal fatto che un'eccezione sia stata sollevata o meno
Devi inserire la clausola `else` dopo tutte le clausole `except` e prima della clausola `finally`, se presente.
E' utile per eseguire codice che deve funzionare solo in assenza di eccezioni.
Ad esempio, immagina di voler eseguire una semplice divisione e di voler gestire il caso in cui il denominatore sia zero che solleverebbe un'eccezione `ZeroDivisionError`.
try:
# Prova a eseguire la divisione
numeratore = 10
denominatore = 2
risultato = numeratore / denominatore
except ZeroDivisionError:
# Questo codice viene eseguito se si verifica una divisione per zero
print("Errore: divisione per zero.")
else:
# Questo codice viene eseguito solo se non si verifica alcuna eccezione
print(f"Il risultato della divisione è: {risultato}")
finally:
# Questo codice viene eseguito indipendentemente dal fatto che ci sia stata un'eccezione o meno
print("Fine Operazione di divisione.")
In questo esempio ogni blocco svolge una funzione specifica.
- Il blocco try tenta di eseguire la divisione 10 / 2.
- Il blocco except viene eseguito solo se si verifica un'eccezione di tipo ZeroDivisionError.
- Il blocco else viene eseguito solo se il blocco try non solleva alcuna eccezione. In questo caso, stampa il risultato della divisione.
- Il blocco finally viene eseguito indipendentemente dal fatto che si verifichi un'eccezione o meno. In questo esempio, stampa un messaggio indicando che l'operazione di divisione è finita.
In questo caso il denominatore è 2 e il risultato in output è il seguente:
Il risultato della divisione è: 5.0
Fine operazione di divisione.
Se il denominatore fosse stato zero (denominatore=0) il risultato in output sarebbe stato:
Errore: divisione per zero.
Fine operazione di divisione.
Questo esempio semplice mostra chiaramente come funziona la clausola else
all'interno della gestione delle eccezioni.
Sollevare un'eccezione
Per sollevare un'eccezione in Python, utilizziamo la parola chiave raise.
raise ExceptionType("Messaggio di errore")
Dove il tipo di eccezione e il messaggio tra parentesi sono facoltativi.
La parola chiave raise serve a sollevare manualmente un'eccezione in situazioni specifiche, anche quando Python non la solleverebbe automaticamente.
Questo ti permette di gestire in modo controllato errori o condizioni particolari nel tuo codice.
Ad esempio, immagina di voler sollevare un'eccezione quando un utente inserisce un numero negativo:
def check_positive_number(number):
if number < 0:
raise ValueError("Il numero non può essere negativo!")
return number
try:
check_positive_number(-5)
except ValueError as e:
print(f"Errore: {e}")
La funzione check_positive_number verifica se il numero tra parentesi è positivo.
In questo caso si tratta di un numero negativo (-5) quindi viene sollevata un'eccezione raise del tipo ValueError.
Errore: Il numero non può essere negativo!
Propagare un'eccezione
Propagare un'eccezione significa lasciare che l'eccezione salga attraverso i livelli della chiamata finché non viene gestita o il programma termina.
In altre parole, non gestisci l'eccezione nel punto in cui si verifica, ma permetti che venga sollevata ai livelli superiori della chiamata.
Ecco un esempio pratico.
def func_c():
print("In func_c")
raise ValueError("Errore in func_c")
def func_b():
print("In func_b")
func_c() # Chiamata a func_c
def func_a():
print("In func_a")
func_b() # Chiamata a func_b
try:
func_a() # Chiamata alla funzione principale
except ValueError as e:
print(f"Eccezione catturata nel blocco principale: {e}")
In questo codice viene chiamata la funzione func_a() nel blocco principale try-except .
La funzione func_a() chiama la funzione func_b() che a sua volta chiama la funzione func_c().
Nella funzione func_c() viene sollevata un'eccezione ValueError che risale attraverso le funzioni chiamanti fino ad essere catturata nel blocco try-except iniziale.
In func_a
In func_b
In func_c
Eccezione catturata nel blocco principale: Errore in func_c
Questo semplice esempio mostra come un'eccezione può propagarsi attraverso diversi livelli di chiamata di funzioni e venire infine gestita nel blocco principale del programma.