Gestire eccezioni

Completato

Quando si trovano per la prima volta eccezioni che mostrano traceback di grandi dimensioni come output, è possibile rilevare ogni errore per evitare che ciò accada.

Se si è in missione su Marte, cosa si può fare se un messaggio nel sistema di navigazione indica "Si è verificato un errore"? Si immagini che non ci siano altre informazioni o contesto, solo una luce rossa lampeggiante con il testo dell'errore. In qualità di sviluppatore, è utile mettersi dalla parte del programma: cosa può fare un utente quando si verifica un errore?

Anche se questo modulo illustra come gestire le eccezioni intercettandole, non è necessario intercettare sempre le eccezioni. In alcuni casi è utile consentire la generazione di eccezioni in modo che altri chiamanti possano gestire gli errori.

Blocchi try ed except

Si userà l'esempio di strumento di navigazione per creare un codice che apra i file di configurazione per la missione su Marte. I file di configurazione possono avere tutti i tipi di problemi, quindi è fondamentale segnalare i problemi in modo accurato quando si verificano. Sappiamo che se non esiste un file o una directory, viene generato FileNotFoundError. Se si vuole gestire tale eccezione, è possibile farlo con un blocco try ed except:

try:
     open('config.txt')
except FileNotFoundError:
     print("Couldn't find the config.txt file!")

Couldn't find the config.txt file!

Dopo la parola chiave try, si aggiunge il codice che può causare un'eccezione. Aggiungere quindi la parola chiave except insieme all'eccezione possibile, seguita da qualsiasi codice che deve essere eseguito quando si verifica tale condizione. Poiché config.txt non esiste nel sistema, Python esegue la stampa indicando che il file di configurazione non esiste. Il blocco try ed except, insieme a un messaggio utile, impedisce un traceback e informa comunque l'utente del problema.

Anche se un file che non esiste è comune, non è l'unico errore che si potrebbe trovare. Le autorizzazioni per i file non validi possono impedire la lettura di un file, anche se il file esiste. Verrà ora creato un nuovo file Python denominato config.py in Visual Studio Code. Aggiungere il codice seguente al file che trova e legge il file di configurazione del sistema di spostamento:

def main():
    try:
        configuration = open('config.txt')
    except FileNotFoundError:
        print("Couldn't find the config.txt file!")


if __name__ == '__main__':
    main()

Creare quindi una directory denominata config.txt. Provare a chiamare il file config.py per visualizzare un nuovo errore simile al seguente:

python3 config.py
Traceback (most recent call last):
  File "/tmp/config.py", line 9, in <module>
    main()
  File "/tmp/config.py", line 3, in main
    configuration = open('config.txt')
IsADirectoryError: [Errno 21] Is a directory: 'config.txt'

Una soluzione inadeguata per gestire questo errore sarebbe quella di intercettare tutte le possibili eccezioni per evitare un traceback. Per comprendere perché intercettare tutte le eccezioni è problematico, provare questa soluzione aggiornando la funzione main() nel file config.py appena creato:

def main():
    try:
        configuration = open('config.txt')
    except Exception:
        print("Couldn't find the config.txt file!")

Eseguire di nuovo il codice nella stessa posizione in cui si trova la directory config.txt con autorizzazioni non corrette:

python3 config.py
Couldn't find the config.txt file!

Il problema è che il messaggio di errore non è corretto. La directory esiste, ma ha autorizzazioni diverse e Python non riesce a leggerla. Quando si gestiscono errori software, può essere frustrante avere errori che:

  • Non indicano qual è il problema reale.
  • Forniscono un output che non corrisponde al problema effettivo.
  • Non suggeriscono come agire per risolvere il problema.

Correggere questo frammento di codice per risolvere tutte queste problematiche. Ripetere l'intercettazione di FileNotFoundError e quindi aggiungere un altro blocco except per intercettare PermissionError:

def main():
    try:
        configuration = open('config.txt')
    except FileNotFoundError:
        print("Couldn't find the config.txt file!")
    except IsADirectoryError:
        print("Found config.txt but it is a directory, couldn't read it")

Ora eseguire di nuovo il codice nella stessa posizione in cui la directory config.txt:

python3 config.py
Found config.txt but couldn't read it

Eliminare ora la directory config.txt per assicurarsi che venga raggiunto il primo blocco except:

 rm -f config.txt
 python3 config.py
Couldn't find the config.txt file!

Quando gli errori sono di natura simile e non è necessario gestirli singolarmente, è possibile raggruppare le eccezioni in un'eccezione unica usando le parentesi nella riga except. Ad esempio, se il sistema di navigazione è sottoposto a carichi elevati e il file system diventa troppo occupato, è opportuno intercettare BlockingIOError e TimeOutError insieme:

def main():
    try:
        configuration = open('config.txt')
    except FileNotFoundError:
        print("Couldn't find the config.txt file!")
    except IsADirectoryError:
        print("Found config.txt but it is a directory, couldn't read it")
    except (BlockingIOError, TimeoutError):
        print("Filesystem under heavy load, can't complete reading configuration file")

Suggerimento

Anche se è possibile raggruppare le eccezioni, procedere così solo quando non è necessario gestirle singolarmente. Evitare di raggruppare molte eccezioni per fornire un messaggio di errore generalizzato.

Se è necessario accedere all'errore associato all'eccezione, occorre aggiornare la riga except per includere la parola chiave as. Questa tecnica è utile se un'eccezione è troppo generica e il messaggio di errore può essere utile:

try:
    open("mars.jpg")
except FileNotFoundError as err:
     print("Got a problem trying to read the file:", err)
Got a problem trying to read the file: [Errno 2] No such file or directory: 'mars.jpg'

In questo caso, as err significa che err diventa una variabile con l'oggetto eccezione come valore. Usare quindi questo valore per stampare il messaggio di errore associato all'eccezione. Un altro motivo per usare questa tecnica è quello di accedere direttamente agli attributi dell'errore. Ad esempio, se si rileva un'eccezione OSError più generica, che è l'eccezione padre di FilenotFoundError e PermissionError, è possibile distinguerle in base all'attributo .errno:

try:
    open("config.txt")
except OSError as err:
     if err.errno == 2:
         print("Couldn't find the config.txt file!")
     elif err.errno == 13:
        print("Found config.txt but couldn't read it")
Couldn't find the config.txt file!

Provare sempre a usare la tecnica che offre la migliore leggibilità per il codice e consente di gestirla in futuro. A volte è necessario usare un codice meno leggibile per offrire un'esperienza utente migliore quando si verifica un errore.