Articles

Eccezione e gestione degli errori in Python

Introduzione


Fonte

Prima di arrivare al perché la gestione delle eccezioni è essenziale e ai tipi di eccezioniin eccezioni che Python supporta, è necessario capire che c’è una sottile differenza tra un error e un exception.

Gli errori non possono essere gestiti, mentre le eccezioni Python possono essere gestite in fase di esecuzione. Un errore può essere un syntax (parsing), mentre ci possono essere molti tipi di eccezioni che potrebbero verificarsi durante l’esecuzione e non sono incondizionatamente inoperanti. Un Error potrebbe indicare problemi critici che un’applicazione ragionevole non dovrebbe cercare di prendere, mentre un Exception potrebbe indicare condizioni che un’applicazione dovrebbe cercare di prendere. Gli errori sono una forma di eccezione non controllata e sono irrecuperabili come un OutOfMemoryError, che un programmatore non dovrebbe cercare di gestire. Immaginate se avete scritto un codice che viene distribuito in produzione e tuttavia, termina a causa di un’eccezione, il vostro cliente non lo apprezzerebbe, quindi è meglio gestire l’eccezione particolare in anticipo ed evitare il caos.

Gli errori possono essere di vari tipi:

  • Errore di sintassi
  • Errore di esaurimento della memoria
  • Errore di ricorsione
  • Eccezioni

Vediamoli uno per uno.

Errore di sintassi

Gli errori di sintassi, spesso chiamati errori di parsing, sono principalmente causati quando il parser rileva un problema sintattico nel vostro codice.

Facciamo un esempio per capirlo.

a = 8b = 10c = a b
 File "<ipython-input-8-3b3ffcedf995>", line 3 c = a b ^SyntaxError: invalid syntax

La freccia qui sopra indica quando il parser ha incontrato un errore durante l’esecuzione del codice. Il token che precede la freccia causa l’errore. Per correggere questi errori fondamentali, Python farà la maggior parte del vostro lavoro poiché vi stamperà il nome del file e il numero della linea in cui si è verificato l’errore.

Out of Memory Error

Gli errori di memoria dipendono principalmente dalla RAM del vostro sistema e sono legati a Heap. Se avete grandi oggetti (o) oggetti referenziati in memoria, allora vedrete OutofMemoryError (Fonte). Può essere causato da vari motivi:

  • Utilizzando un’architettura Python a 32 bit (l’allocazione massima di memoria data è molto bassa, tra 2GB – 4GB).
  • Caricando un file di dati molto grande
  • Eseguendo un modello di Machine Learning/Deep Learning e molti altri.

È possibile gestire l’errore memory con l’aiuto della gestione delle eccezioni, un’eccezione di ripiego per quando l’interprete esaurisce completamente la memoria e deve immediatamente fermare l’esecuzione corrente. In questi rari casi, Python solleva un OutofMemoryError, permettendo allo script di uscire in qualche modo catch da solo e break dall’errore di memoria e recuperare se stesso.

Tuttavia, poiché Python adotta l’architettura di gestione della memoria del linguaggio C (funzione malloc()), non è certo che tutti i processi dello script si riprenderanno – in alcuni casi, un MemoryError provocherà un crash irrecuperabile. Quindi, non è una buona pratica usare la gestione delle eccezioni per un tale errore, né è consigliabile.

Errore di ricorsione

È legato a stack e si verifica quando si chiamano funzioni. Come suggerisce il nome, l’errore recursion si verifica quando vengono eseguiti troppi metodi, uno dentro l’altro (una ricorsione infinita), che è limitata dalla dimensione dello stack.

Tutte le vostre variabili locali e i dati associati alle chiamate ai metodi saranno messi sullo stack. Per ogni chiamata al metodo, verrà creato uno stack frame, e i dati locali e quelli relativi alla chiamata al metodo saranno posizionati all’interno di questo stack frame. Una volta che l’esecuzione del metodo è completata, lo stack frame sarà rimosso.

Per riprodurre questo errore, definiamo una funzione recursion che sarà ricorsiva, cioè continuerà a chiamare se stessa come una chiamata di metodo in loop infinito, si vedrà StackOverflow o un errore di ricorsione perché lo stack frame sarà popolato con i dati del metodo per ogni chiamata, ma non sarà liberato.

def recursion(): return recursion()
recursion()
---------------------------------------------------------------------------RecursionError Traceback (most recent call last)<ipython-input-3-c6e0f7eb0cde> in <module>----> 1 recursion()<ipython-input-2-5395140f7f05> in recursion() 1 def recursion():----> 2 return recursion()... last 1 frames repeated, from the frame below ...<ipython-input-2-5395140f7f05> in recursion() 1 def recursion():----> 2 return recursion()RecursionError: maximum recursion depth exceeded

Errore di indentazione

L’errore di indentazione è simile nello spirito all’errore di sintassi e vi rientra. Tuttavia, è specifico per i soli problemi relativi all’indentazione nello script.

Quindi facciamo un rapido esempio per capire un errore di indentazione.

for i in range(10):print('Hello world')
 File "<ipython-input-6-628f419d2da8>", line 2 print('Hello world') ^IndentationError: expected an indented block

Eccezioni

Anche se la sintassi di una dichiarazione o espressione è corretta, può ancora causare un errore quando viene eseguita. Le eccezioni Python sono errori che vengono rilevati durante l’esecuzione e non sono incondizionatamente fatali: imparerete presto nel tutorial come gestirle nei programmi Python. Un oggetto eccezione viene creato quando uno script Python solleva un’eccezione. Se lo script non gestisce esplicitamente l’eccezione, il programma sarà costretto a terminare bruscamente.

I programmi di solito non gestiscono le eccezioni, e danno luogo a messaggi di errore come mostrato qui:

Errore di tipo

a = 2b = 'DataCamp'a + b
---------------------------------------------------------------------------TypeError Traceback (most recent call last)<ipython-input-7-86a706a0ffdf> in <module> 1 a = 2 2 b = 'DataCamp'----> 3 a + bTypeError: unsupported operand type(s) for +: 'int' and 'str'

Errore di divisione zero

100 / 0
---------------------------------------------------------------------------ZeroDivisionError Traceback (most recent call last)<ipython-input-43-e9e866a10e2a> in <module>----> 1 100 / 0ZeroDivisionError: division by zero

Esistono vari tipi di eccezioni Python, e il tipo viene stampato come parte del messaggio: i tipi nei due esempi precedenti sono ZeroDivisionError e TypeError. Entrambe le stringhe di errore stampate come il tipo di eccezione è il nome dell’eccezione integrata di Python.

La parte rimanente della linea di errore fornisce i dettagli di ciò che ha causato l’errore in base al tipo di eccezione.

Guardiamo ora le eccezioni integrate di Python.

Eccezioni integrate


Fonte

Prima di iniziare ad imparare le eccezioni integrate, rivediamo velocemente i quattro componenti principali della gestione delle eccezioni, come mostrato in questa figura.

  • Prova: Eseguirà il blocco di codice in cui ci si aspetta che si verifichi un errore.
  • Except: Qui, si definirà il tipo di eccezione che ci si aspetta nel blocco try (built-in o personalizzato).
  • Else: Se non c’è alcuna eccezione, allora questo blocco di codice verrà eseguito (consideratelo come un rimedio o un’opzione fallback se vi aspettate che una parte del vostro script produca un’eccezione).
  • Infine: Indipendentemente dal fatto che ci sia un’eccezione o meno, questo blocco di codice verrà sempre eseguito.

Nella seguente sezione del tutorial, imparerete i tipi comuni di eccezioni e imparerete anche a gestirle con l’aiuto della gestione delle eccezioni.

Errore di interruzione della tastiera

L’eccezione KeyboardInterrupt viene sollevata quando si cerca di fermare un programma in esecuzione premendo ctrl+c o ctrl+z in una riga di comando o interrompendo il kernel in Jupyter Notebook. A volte si potrebbe non avere l’intenzione di interrompere un programma, ma per errore, succede, nel qual caso usare la gestione delle eccezioni per evitare tali problemi può essere utile.

Nell’esempio seguente, se si esegue la cella e si interrompe il kernel, il programma solleverà un’eccezione KeyboardInterrupt.inp = input()Gestiamo ora l’eccezione KeyboardInterrupt.

try: inp = input() print ('Press Ctrl+C or Interrupt the Kernel:')except KeyboardInterrupt: print ('Caught KeyboardInterrupt')else: print ('No exception occurred')
Caught KeyboardInterrupt

Standard Error

Impariamo alcuni degli errori standard che possono verificarsi durante la programmazione.

Errore aritmetico

  • Errore di divisione zero
  • Errore di overflow
  • Errore in virgola mobile

Tutte le eccezioni di cui sopra rientrano nella classe base Arithmetic e vengono sollevate per errori in operazioni aritmetiche, come discusso qui.

Divisione zero

Quando il divisore (secondo argomento della divisione) o il denominatore è zero, allora il risultato solleva un errore di divisione zero.

try: a = 100 / 0 print (a)except ZeroDivisionError: print ("Zero Division Exception Raised." )else: print ("Success, no error!")
Zero Division Exception Raised.

OverFlow Error

L’Overflow Error viene sollevato quando il risultato di un’operazione aritmetica è fuori portata. L’OverflowError viene sollevato per gli interi che sono fuori da un intervallo richiesto.

try: import math print(math.exp(1000))except OverflowError: print ("OverFlow Exception Raised.")else: print ("Success, no error!")
OverFlow Exception Raised.

Errore di asserzione

Quando una dichiarazione di asserzione fallisce, viene sollevato un errore di asserzione.

Facciamo un esempio per capire l’errore di asserzione. Diciamo che avete due variabili a e b, che dovete confrontare. Per controllare se a e b sono uguali o no, si applica una assert parola chiave prima, che solleverà un’eccezione Assertion quando l’espressione restituirà false.

try: a = 100 b = "DataCamp" assert a == bexcept AssertionError: print ("Assertion Exception Raised.")else: print ("Success, no error!")
Assertion Exception Raised.

Errore attributo

Quando un attributo inesistente viene referenziato, e quando il riferimento all’attributo o l’assegnazione fallisce, viene sollevato un errore attributo.

Nell’esempio seguente, si può osservare che l’oggetto di classe Attributes non ha un attributo con il nome attribute.

class Attributes(object): a = 2 print (a)try: object = Attributes() print (object.attribute)except AttributeError: print ("Attribute Exception Raised.")
2Attribute Exception Raised.

Errore di importazione

ImportError viene sollevato quando si cerca di importare un modulo che non esiste (incapace di caricarsi) nel suo percorso standard o anche quando si fa un refuso nel nome del modulo.

import nibabel
---------------------------------------------------------------------------ModuleNotFoundError Traceback (most recent call last)<ipython-input-6-9e567e3ae964> in <module>----> 1 import nibabelModuleNotFoundError: No module named 'nibabel'

Lookup Error

Lookup Error funge da classe base per le eccezioni che si verificano quando una chiave o un indice usato in una mappatura o sequenza di un elenco/dizionario non è valido o non esiste.

I due tipi di eccezioni sollevate sono:

  • IndexError
  • KeyError

Key Error

Se una chiave a cui si sta cercando di accedere non si trova nel dizionario, viene sollevata un’eccezione key.

try: a = {1:'a', 2:'b', 3:'c'} print (a) except LookupError: print ("Key Error Exception Raised.")else: print ("Success, no error!")
Key Error Exception Raised.

Errore indice

Quando si cerca di accedere ad un indice (sequenza) di una lista che non esiste in quella lista o che è fuori dal suo range, viene sollevato un errore indice.

try: a = print (a) except LookupError: print ("Index Error Exception Raised, list index out of range")else: print ("Success, no error!")
Index Error Exception Raised, list index out of range

Errore di memoria

Come discusso in precedenza, l’errore di memoria viene sollevato quando un’operazione non ha abbastanza memoria per essere processata ulteriormente.

Name Error

Name Error viene sollevato quando un nome locale o globale non viene trovato.

Nell’esempio seguente, la variabile ans non è definita. Quindi, si otterrà un name error.

try: print (ans)except NameError: print ("NameError: name 'ans' is not defined")else: print ("Success, no error!")
NameError: name 'ans' is not defined

Runtime Error

Not Implemented Error

Questa sezione del tutorial deriva da questo Source. Runtime Error funge da classe base per NotImplemented Error. I metodi astratti nelle classi definite dall’utente dovrebbero sollevare questa eccezione quando le classi derivate sovrascrivono il metodo.

class BaseClass(object): """Defines the interface""" def __init__(self): super(BaseClass, self).__init__() def do_something(self): """The interface, not implemented""" raise NotImplementedError(self.__class__.__name__ + '.do_something')class SubClass(BaseClass): """Implementes the interface""" def do_something(self): """really does something""" print (self.__class__.__name__ + ' doing something!')SubClass().do_something()BaseClass().do_something()
SubClass doing something!---------------------------------------------------------------------------NotImplementedError Traceback (most recent call last)<ipython-input-1-57792b6bc7e4> in <module> 14 15 SubClass().do_something()---> 16 BaseClass().do_something()<ipython-input-1-57792b6bc7e4> in do_something(self) 5 def do_something(self): 6 """The interface, not implemented"""----> 7 raise NotImplementedError(self.__class__.__name__ + '.do_something') 8 9 class SubClass(BaseClass):NotImplementedError: BaseClass.do_something

Type Error

Type Error Exception viene sollevata quando due tipi diversi o non correlati di operandi o oggetti vengono combinati.

Nell’esempio seguente, un intero e una stringa vengono aggiunti, il che risulta in un errore type.

try: a = 5 b = "DataCamp" c = a + bexcept TypeError: print ('TypeError Exception Raised')else: print ('Success, no error!')
TypeError Exception Raised

Errore di valore

L’errore di valore viene sollevato quando l’operazione integrata o una funzione riceve un argomento che ha un type corretto ma non valido value.

Nell’esempio seguente, l’operazione incorporata float riceve un argomento, che è una sequenza di caratteri (valore), che non è valido per un tipo float.

try: print (float('DataCamp'))except ValueError: print ('ValueError: could not convert string to float: \'DataCamp\'')else: print ('Success, no error!')
ValueError: could not convert string to float: 'DataCamp'

Python Custom Exceptions

Questa sezione del tutorial è derivata da questa fonte.

Come studiato nella sezione precedente del tutorial, Python ha molte eccezioni integrate che potete usare nel vostro programma. Tuttavia, a volte, potresti aver bisogno di creare eccezioni personalizzate con messaggi personalizzati per servire il tuo scopo.

Puoi ottenere questo creando una nuova classe, che sarà derivata dalla classe Exception predefinita in Python.

class UnAcceptedValueError(Exception): def __init__(self, data): self.data = data def __str__(self): return repr(self.data)Total_Marks = int(input("Enter Total Marks Scored: "))try: Num_of_Sections = int(input("Enter Num of Sections: ")) if(Num_of_Sections < 1): raise UnAcceptedValueError("Number of Sections can't be less than 1")except UnAcceptedValueError as e: print ("Received error:", e.data)
Enter Total Marks Scored: 10Enter Num of Sections: 0Received error: Number of Sections can't be less than 1

Nell’esempio precedente, come hai osservato, se inserisci qualcosa meno di 1, verrà sollevata e gestita un’eccezione personalizzata. Molti moduli standard definiscono le loro eccezioni per segnalare gli errori che possono verificarsi nelle funzioni che definiscono.

Demeriti della gestione delle eccezioni in Python

Fare uso della gestione delle eccezioni in Python ha anche un effetto collaterale. Ad esempio, i programmi che fanno uso dei blocchi try-except per gestire le eccezioni saranno leggermente più lenti, e la dimensione del vostro codice aumenterà.

Di seguito è riportato un esempio in cui il modulo timeit di Python viene usato per controllare il tempo di esecuzione di 2 istruzioni diverse. In stmt1, try-except è usato per gestire ZeroDivisionError, mentre in stmt2, l’istruzione if è usata come una normale condizione di controllo. Poi, si eseguono queste dichiarazioni 10000 volte con la variabile a=0. Il punto da notare qui è che il tempo di esecuzione di entrambe le dichiarazioni è diverso. Troverete che stmt1, che sta gestendo l’eccezione, ha richiesto un tempo leggermente più lungo di stmt2, che sta solo controllando il valore e non fa nulla se la condizione non è soddisfatta. Per esempio, quando non siete sicuri se l’input sarà un intero o un float per i calcoli aritmetici o non siete sicuri dell’esistenza di un file mentre cercate di aprirlo.

import timeitsetup="a=0"stmt1 = '''\try: b=10/aexcept ZeroDivisionError: pass'''
stmt2 = '''\if a!=0: b=10/a'''
print("time=",timeit.timeit(stmt1,setup,number=10000))print("time=",timeit.timeit(stmt2,setup,number=10000))
time= 0.003897680000136461time= 0.0002797570000439009

Congratulazioni per aver finito questo tutorial.

Come avete imparato, l’exceptional handling aiuta a rompere il tipico flusso di controllo del vostro programma fornendo un meccanismo per disaccoppiare la gestione degli errori di Python e rende il vostro codice più robusto.

Python exceptional handling è uno dei fattori principali per rendere il vostro codice pronto per la produzione e a prova di futuro, oltre all’aggiunta di test unitari e programmazione orientata agli oggetti.

È una tecnica potente ed è un concetto di soli quattro blocchi. Il blocco try cerca le eccezioni lanciate dal codice, mentre il blocco except gestisce queste eccezioni (built-in e personalizzate).

Un buon esercizio per tutti voi sarebbe quello di usare tutti e quattro i componenti della gestione delle eccezioni e cercare di rendere il vostro codice più robusto.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *