lettura facile

Le eccezioni personalizzate in Python

Per comprendere meglio come funzionano le eccezioni in Python e la loro eredità, possiamo approfondire la gerarchia delle eccezioni built-in e l'uso di `BaseException` e `Exception`.

In Python, tutte le eccezioni devono derivare dalla classe BaseException, direttamente o indirettamente. BaseException è la classe base per tutte le eccezioni, e da essa derivano diverse sottoclassi per rappresentare vari tipi di eccezioni.

Quindi, se vuoi creare una classe personalizzata, devi definire una sottoclasse di `BaseException` o di una sua sottoclasse, ad esempio `Exception`.

Gerarchia delle Eccezioni Built-in

In Python, tutte le eccezioni built-in derivano dalla classe base `BaseException`. Ecco una panoramica della gerarchia:

Ecco una panoramica della gerarchia:

  • BaseException
    • SystemExit
    • KeyboardInterrupt
    • GeneratorExit
    • Exception
      • ArithmeticError
        • FloatingPointError
        • OverflowError
        • ZeroDivisionError
      • BufferError
      • LookupError
        • IndexError
        • KeyError
      • EnvironmentError
        • IOError
        • OSError
      • AssertionError
      • AttributeError
      • EOFError
      • ImportError
      • ModuleNotFoundError
      • KeyError
      • KeyboardInterrupt
      • MemoryError
      • NameError
      • NotImplementedError
      • RuntimeError
        • RecursionError
      • SyntaxError
        • IndentationError
        • TabError
      • SystemError
      • TypeError
      • ValueError
        • UnicodeError
          • UnicodeDecodeError
          • UnicodeEncodeError
          • UnicodeTranslateError
      • Warning
        • DeprecationWarning
        • PendingDeprecationWarning
        • RuntimeWarning
        • SyntaxWarning
        • UserWarning
        • FutureWarning
        • ImportWarning
        • UnicodeWarning
        • BytesWarning
        • ResourceWarning

Come avrai già notato non tutte le eccezioni sono errori ( Error ), una parte sono semplici avvisi ( Warning ).

Gli errori ( Error ) interrompono l'esecuzione del programma con un messaggio di errore mentre gli avvisi ( Warning ) non lo interrompono.

In genere, gli avvisi ( Warning ) non sono visualizzati da Python quando si verificano. Per visualizzare gli avvisi è necessario creare il bytecode di Python con l'opzione "-b".

In generale è preferibile ereditare da Exception anziché da ExceptionBase, perché ti permette di gestire le eccezioni inaspettate, come gli errori o i warning, senza interferire con il flusso normale del programma come l'interruzione o l'uscita intenzionale.

Ereditare da Exception ti consente di ignorare le altre eccezioni di sistema che dipendono da ExceptionBase, ma non da Exception, come SystemExit, KeyboardInterrupt e GeneratorExit.

In generale, quando si crea una nuova eccezione è necessario ereditare dal tipo di eccezione più specializzato per catturare eccezioni specifiche.

Creare nuove eccezioni

Quando si crea una nuova eccezione, si dovrebbe ereditare da `Exception` o da una delle sue sottoclassi per garantire che possa essere sollevata e catturata correttamente.

Ecco un esempio di come creare e utilizzare una nuova eccezione:

class MyException(Exception):
    pass

try:
    raise MyException("Questo è un messaggio di errore personalizzato")
except MyException as e:
    print(f"Catturata eccezione: {e}")

Questo codice definisce una nuova eccezione personalizzata 'MyException' come sottoclasse della classe Exception.

Poi solleva un'eccezione di tipo 'MyException' con un messaggio personalizzato tramite l'istruzione raise nel blocco try.

Infine, l'eccezione viene catturata dal blocco except e assegnata alla variabile 'e' e il messaggio di errore viene stampato.

Catturata eccezione: Questo è un messaggio di errore personalizzato

In questo modo puoi definire e sollevare un'eccezione personalizzata.

Questa classe che hai appena creato è considerata un'eccezione perché è una sottoclasse di "Extension"

print(issubclass(MyException, Exception))

True

E ovviamente è anche una sottoclasse della classe basse delle eccezioni ossia di "BaseExtension".

print(issubclass(MyException, BaseException))

True

Quando si crea un'eccezione è necessario ereditare dal tipo di eccezione più specifico. E' la regola generale.

Ad esempio, questa eccezione eredita da "ZeroDivisionError" una sottoclasse di "Exception".

class MyException(ZeroDivisionError):
    pass

try:
    raise MyException("Divisione per zero")
    
except ZeroDivisionError as e:
    print(f"Catturata eccezione: {e}")

In questo modo, possiamo essere sicuri che l'eccezione personalizzata verrà catturata dalla clausola except ZeroDivisionError.

Questo evita di catturare e gestire inaspettatamente altre eccezioni come FloatingPointError o OverflowError.

Il blocco except except ZeroDivisionError cattura correttamente l'eccezione.

Catturata eccezione: Divisione per zero

Ora prova a modificare la classe MyException facendola ereditare direttamente da "Exception".

class MyException(Exception):
    pass

try:
    raise MyException("Divisione per zero")
    
except ZeroDivisionError as e:
    print(f"Catturata eccezione: {e}")

In questo caso l'eccezione non viene catturata perché in Python, la cattura delle eccezioni funziona in modo gerarchico, e la clausola except cattura solo le eccezioni del tipo specificato o delle sue sottoclassi.

La cattura di un'eccezione con una clausola except di un tipo più specifico non funziona se l'eccezione non è direttamente o indirettamente una sottoclasse del tipo specificato nella clausola except. In questo caso ZeroDivisionError.

Traceback (most recent call last):
  File "/home/main.py", line 5, in <module>
    raise MyException("Divisione per zero")
__main__.MyException: Divisione per zero

Il concetto di ereditare da un tipo di eccezione specializzato in Python è cruciale per garantire che le eccezioni vengano catturate e gestite correttamente.

Questo principio riduce il rischio di gestire eccezioni inaspettate in modo improprio.

Ereditarietà delle eccezioni

Quando si cattura un'eccezione, è possibile catturare tutte le eccezioni di una classe base e delle sue sottoclassi. Ad esempio:

class Errore1(Exception):
    pass

class Errore2(Errore1):
    pass

class Errore3(Errore2):
    pass

try:
    raise Errore3("Errore di tipo 3")
except Errore3 as e:
    print(f"Catturata eccezione di tipo: {type(e).__name__} con messaggio: {e}")

In questo esempio abbiamo definito tre classi:

  • La classe 'Errore1' è una sottoclasse di 'Exception' ed è quindi un'eccezione.
  • La classe 'Errore2' è una sottoclasse di 'Errore1'
  • La classe 'Errore3' è una sottoclasse di 'Errore2'

Nel blocco try abbiamo sollevato un eccezione 'Errore3' tramite l'istruzione raise che viene poi intercettata e stampata dal blocco except.

Python ha riconosciuto la classe Errore3 come eccezione perché è una sottoclasse indiretta di 'Exception' tramite le classi 'Errore2' ed 'Errore3' .

In altre parole la classe 'Errore1' ha ereditato il tipo 'Exception'

Catturata eccezione di tipo: Errore3 con messaggio: Errore di tipo 3

Eccezioni che non sono sottoclassi di Exception

In Python, ci sono tre eccezioni particolari che non ereditano direttamente da Exception, ma piuttosto dalla classe base BaseException. Queste eccezioni sono:

  • SystemExit
    E' un'eccezione utilizzata per terminare il programma. Quando viene sollevata una SystemExit, Python esce dal programma senza mostrare un messaggio di errore. È possibile specificare un codice di uscita, che sarà restituito alla shell. Ad esempio, questa istruzione esce dal programma con codice 0.

    raise SystemExit(0)

  • KeyboardInterrupt
    Questa eccezione viene sollevata quando l'utente interrompe l'esecuzione del programma premendo Ctrl-C. Questa eccezione permette di gestire l'interruzione in modo pulito, ad esempio per eseguire operazioni di pulizia prima che il programma termini.
  • GeneratorExit
    Viene sollevata quando un generatore viene esplicitamente chiuso con il metodo close(). Questa eccezione non viene propagata al chiamante, ma viene gestita internamente da Python per informare il generatore che è stato chiuso. È importante ricordare di propagare questa eccezione se viene catturata all'interno del generatore per evitare errori.

Queste eccezioni sono progettate per scopi specifici: terminare il programma, gestire interruzioni dell'utente e chiusure di generatori. La loro gestione particolare garantisce che le operazioni critiche vengano eseguite correttamente anche in situazioni straordinarie.

Quando usi una struttura try except generica, questa cattura tutte le eccezioni perché una except generica equivale a scrivere except ExceptionBase.

try
    pass
except
    pass

Se vuoi catturare solo gli errori o gli avvisi ti consiglio di usare except Exception.

try
    pass
except Exception
    pass

In conclusione, la corretta gestione delle eccezioni e la comprensione della gerarchia delle eccezioni sono fondamentali se vuoi definire delle eccezioni personalizzate.




Se qualcosa non ti è chiaro, scrivi la tua domanda nei commenti.




FacebookTwitterLinkedinLinkedin