Eccezioni

Sistemi software ove parti del programma reagiscono ad eventi o si scambiano messaggi sono molto comuni, fra questi abbiamo:

Un particolare utilizzo di questa tecnica puo' essere considerato il sistema che gestisce, in molti linguaggi, il verificarsi di errori: una routine, in cui si verifica un errore, lancia un'"eccezione", che e' un messaggio che puo' essere gestito dal programma stesso o puo' causare l'arresto del programma. Questo sistema puo' anche essere usato come sistema generale di comunicazione fra le diverse parti del programma.

Eccezioni in Python

Python usa, per le eccezioni, seguenti istruzioni:

try : delimita un blocco entro cui sono intercettate eccezioni

raise : genera un'eccezione, ed interrompe l'esecuzione

assert: genera un'eccezione, di tipo "AssertionError", in base a condizioni

except: intercetta l'eccezione e definisce un blocco di istruzioni
        da eseguire per una una data eccezione.

L'eccezione lanciata da"raise" puo' essere:

  • l'istanza di una classe;
  • una classe, ed in questo caso raise la istanzia automaticamente,
  • un'eccezione precedente, che viene rilanciata. Rilanciare un'eccezione puo' servire in strutture try-except annidate, per passare l'eccezione ad un blocco superiore nella gerarchia.

Sintassi dell'istruzione "raise":

raise nomeistanza
raise nomeclasse
raise
raise nomeclasse from altraclass

In versioni di Python precedenti alla 2.6 le eccezione potevano anche essere semplici stringhe. Ma in Python 3, sono sempre clessi, che ereditano la classe Exception;

Dopo l'istruzione "try", che identifica il blocco entro cui verificare se sono state lanciate eccezioni, ci sono uno o piu'blocchi individuati da istruzioni "except" , che sono eseguiti se si verificano eccezioni di un certo tipo. La sintassi e' la seguente:

try:
  ...
  ...
  ...        # blocco entro cui puo' verificarsi l'eccezione
  ...        # Es.:  if a<0: raise ecc1
  ...
except ecc1 as var :
  ...
  ...        # blocco eseguito per l'eccezione: ecc1
  ...
except (ecc2,ecc3) as var :
  ...
  ...    # blocco eseguito per le eccezioni: ecc2 od ecc3
  ...
except
  ...
  ...    # blocco per tutte le altre eccezioni
  ...
else:
  ...
  ...    # blocco eseguito se NON ci sono eccezioni
  ...
finally:
  ...
  ...    # blocco eseguito in ogni caso
  ...

I blocchi che seguono le istruzioni "except" vengono eseguiti se l'eccezione corrisponde al nome della classe che segue except (qui: ecc1,ecc2,ecc3). Il nome che conclude la linea con "except" (qui: var) e' un riferimento che punta all'eccezione, che puo' essere usato nel blocco except.

Il blocco else e' eseguito se non e' sollevata un'eccezione.

Il blocco finally viene eseguito in ogni caso, sia se ci sono eccezioni sia se l'esecuzione del blocco try non ne genera. In blocco "finally", e' opzionale e serve ad assicurarsi che certe operazioni siano eseguite in ogni caso, anche se si verificano errori

Esempio:

class Ecc(Exception):     # definizione di una eccezione
   nome="eccezione Ecc"

class Ecc1(Exception):    # definizione di un'altra eccezione
   nome="eccezione Ecc1"

def eccezsub(a):
   if a<2 : raise Ecc          # lancia eccezioni se a<=2
   if a==2 : raise Ecc1
   print "OK, valore grande:",a

for i in [1,2,3] :
    print "provo il numero:",i
    try:
      eccezsub(i)                    # blocco try, da testare
      print "OK niente eccezione"
    except Ecc as X :                # cattura eccezione : Ecc
      print "valoretroppo piccolo:",X.nome
    except Ecc1 as X :               # cattura eccezione : Ecc1
      print "valore ma quasi OK:",X.nome
    else:
      print " niente eccezione, valore OK"
    finally:
      print "valore verificato:",i
print "fine delle prove"

Se viene lanciata un'eccezione che non corrisponde ad un except questa non viene catturata dal try, ma dal Python, che interrompe il programma. Il blocco finally viene eseguito prima dell'interruzione.

In Python 3 raise puo' avere anche una sintassi del tipo:

except eccezione as E
    ....
    raise nuova_eccezione from E

L'eccezione: "E" finisce nell'attributo "__cause__" della nuova eccezione lanciata. In questo modo si puo' avere una gerarchia di eccezioni, che sono elencate nel messaggio di errore.

Classi di eccezioni generate da Python

In caso di errori durante l'esecuzione del programma, Python genera eccezioni, alcune di queste sono elencate nella tabella seguente:

ZeroDivisionError : divisione per zero;   Es.: 3/0
OverflowError     : valore troppo grande  Es.: 10.0**1000
IndexError        : indice sbagliato      Es.: a=[1,2,3] ;  a[8]
IOError           : errore di input/output
ImportError       : non si trova il file da importare
KeyError          : chiavi di dizionario inesistente
                    Es.: d={'a':1} ; d['b']
TypeError         : operazione non permessa su certi dati
                    (Es.: elevazione a potenza con stringhe)

Esempio di come si possano utilizzare le istruzioni try ed except in modo da evitare che un certo errore provochi l'interruzione del programma:

a=2.0
for i in [1,0] :
    print "divido per:",i
    try:
      b=a/i
    except ZeroDivisionError :
      print "sto dividendo per zero, metto -1"
      b=-1
    finally:
      print "valore di b:",b
print "fine delle prove"

Istruzione assert

L'istruzione "assert" verifica una condizione e, se non e' vera, lancia l'eccezione "AssertionError". Viene usata nella fase di test dei programmi. Se Python viene chiamato con l'opzione di ottimizzazione '-O' , la variabile interna __debug__ e' False e l'istruzione assert non ha effetto.

Esempio:

assert 5 > 2                : questa NON lancia un'eccezione

assert 2 > 5 , "messaggio"  : la condizione e' falsa,
                              viene lanciata un'eccezione
                              che contiene il messaggio (opzionale)