Ormai sappiamo bene cos'è un errore di runtime: se durante l'esecuzione di un programma chiediamo a Python di fare qualcosa di illegale (ad es. dividere per zero, usare un indice inesistente in una lista o chiamare una funzione con un numero errato di argomenti) Python blocca immediatamente l'esecuzione, esce dal programma e ci segnala l'errore. Questo comportamento va benissimo durante lo sviluppo del programma, ma sarebbe piuttosto imbarazzante in un programma "serio". E' facilissimo provocare un errore di runtime con un input sbagliato: ecco un esempio:
nomi = ["Gino", "Peppe", "Carlo", "Ciccio"]
n = int(input("Ho pensato quattro nomi. Quale vuoi sapere? "))
n -= 1 # ricorda che gli indici vanno da 0 a 3
print(nomi[n])
Quando lanciamo questo programma possiamo allegramente provocare un ValueError digitando "xyz" al prompt (Python deve tradurre la nostra stringa in un intero, ma la stringa non ha significato). Oppure possiamo provocare un IndexError digitando "10" (nell'istruzione successiva chiediamo a Python di leggere il decimo elemento di una lista con solo quattro elementi).
Il secondo errore potrebbe essere evitato usando la funzione input_int()
che abbiamo
programmato nella Lezione 17, ma per il primo non sembra esserci
soluzione: se un programma commerciale andasse in crash per un problema così stupido probabilmente nessuno
sarebbe disposto a spendere soldi per acquistarlo!
Naturalmente la soluzione c'è: la terminazione del programma è solo il comportamento predefinito, ma Python (come tutti i linguaggi più evoluti) permette di gestire gli errori di runtime, facendo in modo che, quando accadono, vengano eseguite delle istruzioni definite dal programmatore (che di solito servono a correggere l'errore) ed il programma possa continuare.
Nel linguaggio informatico il termine "eccezione" (Inglese: exception) ha via via sostituito il termine "errore" (error), sicchè oggi si usano entrambi come sinonimi. Modifichiamo il programma precedente in questo modo:
nomi = ["Gino", "Peppe", "Carlo", "Ciccio"]
try:
n = int(input("Ho pensato quattro nomi. Quale vuoi sapere? "))
n -= 1 # ricorda che gli indici vanno da 0 a 3
print(nomi[n])
except:
print("Input non valido!")
Lanciatelo e provate a scrivere al prompt qualcosa che non ha significato: il programma non vi darà più un errore,
ma scriverà "Input non valido". La coppia di istruzioni try ... except
si usa per
gestire le eccezioni. Esse devono essere allo stesso livello di indentazione e devono essere entrambe seguite
da un blocco di codice (indentato). Python tenta dapprima di eseguire il blocco try
; se in esso si
verifica un errore, salta immediatamente (senza terminarlo) al blocco except
, che esegue per intero.
Se invece non si verificano errori l'except
viene saltato. Notiamo che try
ed
except
, come tutte le istruzioni che modificano il flusso del programma, sono seguite dai
due punti.
In ogni caso, dopo aver eseguito il try
o l'except
il programma continua con
la prima istruzione successiva allo stesso livello di indentazione, e così possiamo evitare che un errore
mandi in crash il programma.
Quindi nel nostro esempio Python inizia eseguendo il blocco try
; se nella input()
si
verifica un errore salta le altre due righe del try
ed esegue immediatamente il blocco except
.
Se invece immettiamo un input corretto Python esegue tutto il try
e salta l'except
(e quindi
non scrive "Input non valido").
Vediamo ora come possiamo fare per far ripetere l'input fino a quando otteniamo il risultato voluto. Modificate così:
nomi = ["Gino", "Peppe", "Carlo", "Ciccio"]
while True:
try:
n = int(input("Ho pensato quattro nomi. Quale vuoi sapere? "))
n -= 1 # ricorda che gli indici vanno da 0 a 3
print(nomi[n])
break
except:
print("Input non valido!")
I due blocchi try ... except
sono ora inseriti all'interno di un ciclo while
infinito:
se nel try
si verifica un errore il programma salterà all'except
, avvisando dell'input
scorretto e ripetendo il while
; se invece tutto va bene viene eseguito il break
alla fine
del try
e si esce dal ciclo continuando il programma.
L'istruzione except
può essere seguita dal nome di una o più eccezioni (ad
es. NameError, TyperError, ValueError ...) separati da virgole (attenti alla corrispondenza tra maiuscole
e minuscole). In questo caso essa intercetta solo quel tipo di eccezioni, mentre le altre non vengono gestite.
Dopo una try
ci possono essere più except
in modo tale da gestire diversamente errori
diversi.
nomi = ["Gino", "Peppe", "Carlo", "Ciccio"]
while True:
try:
n = int(input("Ho pensato quattro nomi. Quale vuoi sapere? "))
n -= 1 # ricorda che gli indici vanno da 0 a 3
print(nomi[n])
break
except ValueError: # viene eseguita se abbiamo introdotto una stringa senza senso
print("Devi introdurre un numero!")
except IndexError: # viene eseguita se abbiamo introdotto un numero sbagliato
print("Hai introdotto un numero non valido!")
Va detto che la try ... except
non è un modo "miracoloso" per mascherare i nostri errori
di programmazione, ma dovrebbe essere usata solo per gestire delle eccezioni che il programmatore
ha previsto. Per questo i programmatori esperti considerano pericoloso l'uso di except
da solo,
in quanto potrebbe "catturare" anche eccezioni che derivano da nostri errori di programmazione. A volte si mette
un except
come ultima istruzione, ma questo va considerato una sorta di "ultima spiaggia" per non
mandare in crash il programma:
nomi = ["Gino", "Peppe", "Carlo", "Ciccio"]
while True:
try:
n = int(input("Ho pensato quattro nomi. Quale vuoi sapere? "))
n -= 1 # ricorda che gli indici vanno da 0 a 3
print(nomi[n])
break
except ValueError:
print("Devi introdurre un numero!")
except IndexError:
print("Hai introdotto un numero non valido!")
except:
print("Oops! Errore imprevisto! Non so cosa e' successo")
Probabilmente l'ultimo except
non verrà mai eseguito: nel caso lo fosse dovremmo probabilmente
eseguire il programma con il debugger per capire quale tipo di errore sia capitato (e gestirlo di conseguenza).
Inoltre anche la try
può essere seguita da una else
(che, se presente, va in
coda a tutte le except
), che viene eseguita solo se nel blocco try
non si è
verificato alcun errore. La prassi comune è di limitare al minimo le istruzioni nel try
(possibilmente solo quelle che potenzialmente potrebbero dare un errore che abbiamo previsto) ed aggiungere
le altre istruzioni nell'else
: anche questo in modo da limitare al massimo la possibilità che
nel try
si verifichi un'eccezione non prevista, "mascherando" un nostro errore di programmazione.
Ecco infine il nostro programma aggiornato:
nomi = ["Gino", "Peppe", "Carlo", "Ciccio"]
while True:
try:
n = int(input("Ho pensato quattro nomi. Quale vuoi sapere? "))
n -= 1 # ricorda che gli indici vanno da 0 a 3
print(nomi[n])
except ValueError:
print("Devi introdurre un numero!")
except IndexError:
print("Hai introdotto un numero non valido!")
except:
print("Oops! Errore imprevisto! Non so cosa e' successo")
else:
break
Nel try
sono state lasciate solo le due istruzioni potenzialmente pericolose (la input()
che potrebbe provocare un ValueError e la print(nomi[n])
che potrebbe dare un
IndexError), mentre il break
è stato spostato nell'else
.
Il blocco try ... except
gestisce anche le eccezioni che si verificano all'interno delle
funzioni che vengono chiamate nel try
:
from math import *
try:
n = float(input("Scrivi un numero "))
print(sqrt(n))
print(log(n))
except ValueError:
print("Input scorretto")
Se immettiamo 0 o un numero negativo le funzioni sqrt()
o log()
provocheranno un
errore, che sarà intercettato dal nostro except
. Quando usiamo le funzioni dobbiamo perciò
sapere quali possibili eccezioni esse possono provocare, e, se non vogliamo che esse interrompano l'esecuzione
del programma, dobbiamo preoccuparci di gestirle.
Questa istruzione ... non fa nulla! E' però talvolta necessaria in alcune situazioni come questa:
while True:
try:
n = float(input("Scrivi un numero "))
except ValueError:
pass
else:
break
In questo caso, se si verifica un'eccezione, vogliamo solo ripetere il prompt "Scrivi un numero ". Non
possiamo scrivere solo except
, in quanto essa deve essere obbligatoriamente seguita da un
blocco di codice indentato, così diventa necessaria un'istruzione "che non fa nulla".
Il pass
si può mettere ovunque, la sua funzione è principalmente quella di prendere
il posto di un blocco indentato. A volte viene usato provvisoriamente in un elif
,
come segnaposto per dei casi che non abbiamo ancora programmato.
n = int(input("Scrivi un numero "))
if(n == 1):
print("Questo e' il caso 1")
fai_calcoli_complicati_con_1()
elif(n == 2):
pass # questo caso lo scrivo dopo, ma per ora e' necessario un blocco indentato
elif(n == 3):
pass # idem
Possiamo infine forzare volontariamente un'eccezione con l'istruzione raise
. La sintassi è
il parametro è opzionale e, se presente, è di solito una stringa che viene scritta subito dopo il nome dell'errore come spiegazione. Ad esempio provate a scrivere in IDLE:
>>> raise NameError
Traceback (most recent call last): File "<pyshell#8>", line 1, in <module> raise NameError NameError
>>> raise NameError("Nome sbagliato")
Traceback (most recent call last): File "<pyshell#9>", line 1, in <module> raise NameError("Nome sbagliato") NameError: Nome sbagliato
Il raise
è usato spesso all'interno delle funzioni da noi programmate, per segnalare al
programma principale che qualcosa è andato storto: di solito se succede qualcosa di anomalo all'interno di
una funzione il programmatore scrive il raise
senza nessun blocco try
, così Python
genera un errore ed esce immediatamente dalla funzione. Ad esempio riprendiamo il programma
radice.py che avevamo implementato nella Lezione 15.
mysqrt()
che prenda come argomento un numero
e restituisca la sua radice, provocando un ValueError se il suo argomento è negativo. Dovete
eliminare, nella funzione, tutte le istruzioni che stampano i risultati intermedi e trasformare l'ultima istruzione
(che stampa la radice in una return
. Scrivete poi due istruzioni che chiamino tale funzione, una con
un numero positivo ed una con un numero negativo come argomento.Avevamo già notato che il nostro programma non funziona se chiediamo la radice di un numero negativo (che del resto non esiste) ed avevamo risolto il problema fermando il calcolo e stampando un messaggio. Questo può andare bene nel programma principale, ma non è auspicabile in una funzione: il programma chiamante si aspetta infatti che la funzione gli restituisca un valore, e se questo non avvenisse potrebbero verificarsi errori imprevisti. Molto meglio sollevare un'eccezione:
def mysqrt(n):
if n < 0:
raise ValueError("mysqrt e' stata chiamata con un argomento negativo")
. . .
In questo modo, se chiamate la funzione con un numero negativo come argomento, la funzione terminerà immediatamente e segnalerà l'errore al programma principale, che potrà eventualmente gestirlo
Riprendiamo il nostro modulo myinput.py ed introduciamo la gestione
delle eccezioni. Le funzioni "problematiche" sono quelle che convertono una stringa in intero: abbiamo visto
sopra che se la stringa non ha significato si ottiene un ValueError. Vediamo ad esempio
la funzione input_int()
def input_int(prompt, min, max):
while True:
risp = int(input(prompt))
if risp >= min and risp <= max:
return risp
print("Input scorretto")
Modifichiamola così:
def input_int(prompt, min, max):
while True:
try:
risp = int(input(prompt))
if risp >= min and risp <= max:
return risp
except ValueError:
print ("Input scorretto")
print("Input scorretto")
Ora la funzione fa quello che deve fare, ma per un informatico è "esteticamente brutto" ripetere due volte
la print()
(la ripetizione è necessaria: una riga viene eseguita in caso di eccezione, l'altra
se il numero immesso non è nei limiti giusti). Ecco un trucco per rimediare:
def input_int(prompt, min, max):
while True:
try:
risp = int(input(prompt))
if risp < min or risp > max:
raise ValueError
return risp
except ValueError:
print ("Input scorretto")
In questo modo, se l'utente ha immesso un numero non nei limiti giusti, generiamo un errore e
provochiamo l'esecuzione della except
.
Facciamo esplicitamente notare che dopo il miglioramento delle funzioni tutti i programmi che usano questo modulo saranno automaticamente aggiornati ogni volta che li lanceremo. Se avessimo scritto le funzioni direttamente nei programmi ora dovremmo cercare tutti i file in cui abbiamo utilizzato queste funzioni e modificarli. Questo è un altro motivo a favore del raccoglimento delle funzioni in moduli separati.
Fine della lezione