Photo by Ricardo Gomez Angel on Unsplash

Les erreurs et exceptions en Python

Aujourd’hui nous allons regarder comment les erreurs et les exceptions fonctionnent dans Python. Vous avez peut-être déjà vu en réalisant les exemples des tutoriels précédents qu’il y a 2 types d’erreurs: les erreurs de syntaxe et les exceptions.

Commençons par regarder les erreurs de syntaxe, ce sont les plus faciles à comprendre et à analyser.

Les erreurs de syntaxe

Les erreurs de syntaxe sont en fait des erreurs dans l’analyse de votre code, c’est-à-dire que c’est vous qui avez manqué quelque chose dans votre code, une virgule, un guillemet ou toute autre faute de frappe.

>>> while True print('Coucou')
   File "<stdin>", line 1
    while True print('Coucou')
                   ^

SyntaxError: invalid syntax

L’analyseur de Python, va vous indiquer la ligne qui pose problème et afficher une petite flèche indiquant l’endroit ou l’erreur à été détectée. Dans cet exemple, il manque : entre True et la fonction print(). L’analyseur va aussi vous indiquer le nom du fichier ainsi que la ligne pour vous aider à facilement repérer l’erreur dans vos scripts.

Les exceptions

Le prochain point que nous allons aborder parle des exceptions, pour expliquer brièvement, même si votre syntaxe est bonne le code génère une erreur lors de son exécution. Ces types d’erreurs sont des exceptions et ne sont pas toujours fatales, votre code ne va pas mourir.

voici un exemple d’exception.

>>> 20 + '20'
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly

La dernière ligne dans les erreurs de type exceptions indique ce qui s’est passé. Les exceptions peuvent avoir différents types par exemple TypeError, NameError ou encore ZeroDivisionError. Cela indique le contexte de l’erreur, cela contient en général les lignes du code source.

Vous pouvez trouver la liste des exceptions native sur la documentation de Python : Liens vers la documentation Python

La gestion des exceptions

Vous pouvez dans vos programmes, gérer certaines exceptions, pour cela c’est vous qui contrôlez l’arrêt du programme. Par exemple vous pouvez demander à l’utilisateur de rentrer son nom et si celui-ci rentre un chiffre et bien il y a une erreur, voici comment faire.

>>> while True:
...     try:
...         x = str(input("Veuillez entrer votre nom: "))
...         break
...     except ValueError:
...         print("Oops!  ce n'est pas un nom valide.  recommencer...")
...

l’instruction try fonctionne comme ça :

  • la clause try est executée, le code entre try et except
  • si tout va bien la clause except est ignorée
  • si une exception est trouvée alors, le clause try est stoppée.
  • Si le type d’exception est trouvé (ValueError) dans l’exemple, alors la clause est executée
  • Si le type d’exception n’est pas trouvé, alors elle est transmise au niveau supérieure du try.

Une instrution try peut comporter plusieurs except, ce qui va pour permettre de personnliser vos erreurs, la dernière clause except peut omettre le nom d’exception(OSError, ValueError…), mais faite attention car cela peut masquer la vraie erreur de programmation.

Voici un exemple de multiple exceptions.

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

l’instruction try … except peut être aussi utilisé avec une clause else, mais ce fameux else devra être après toutes les clauses except. Elle peut être utilisée quand aucune exception n’a été levée. Par exemple

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

Il est préférable d’utiliser la clause else pour ne pas capturer accidentellement une exception. Nous pouvons aussi ajouter un argument à nos exceptions, cela va nous permettre d’afficher ceci lorsqu’une exception est lancé.

>>> try:
...     raise Exception('jambon', 'oeufs')
... except Exception as inst:
...     print(type(inst))    # L'instance d'exception
...     print(inst.args)     # arguments est enregistré dans .args
...     print(inst)          # __str__ permet args d'être imprimé directement,
...     x, y = inst.args     # dépaquetage de args
...     print('x =', x)
...     print('y =', y)
...
<class 'Exception'>
('jambon', 'oeufs')
('jambon', 'oeufs')
x = jambon
y = oeufs

Si l’exception a un argument, il est affiché dans la dernière partie du message d’erreur.

Déclencher des exceptions

Il est aussi de déclencher des exceptions au besoin, avec la mot clé raise.

>>> raise NameError('Hello erreur')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: Hello erreur

Vos propres exceptions

Avec Python il est possible de définir vos exceptions grâce à de nouvelles classe d’exception. Les nouvelles classes sont dérivées de la classe exception de Python. Lorsque l’on crée un module qui peut déclencher plusieurs types d’erreurs distincts, nous pouvons créer une classe de base pour l’ensemble des exceptions définies dans ce module et de créer des sous-classes spécifiques d’exceptions pour les différentes conditions d’erreurs :

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

La plupart des exceptions ont un nom finissant par “Error”. Vous verrez avec le temps que beaucoup de modules ont leur propres exceptions, et lorsque vous allez créer vos propres modules je vous conseille de faire de même.

Actions de nettoyage

L’instruction try a aussi une autre clause bien pratique déstiné a définir des actions de nettoyage dans certaines ciconstance.

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Bye Bye')
...
Bye Bye
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>

Si la clause finally est déclarée, la clause finally sera déclenchée comme la dernière tache a éxecuter de la clause try. La clause sera executée qu’une exception soit produite ou pas.

Par exemple :

>>> def bool_return():
...     try:
...         return True
...     finally:
...         return False
...
>>> bool_return()
False

Dans cette exemple, nous passons dans la clause try, celle-ci retourne True. Donc aucune exception n’est levée. Par la suite nous passons dans le clause finally et nous retournons False étant donné que c’est le dernier status de la clause try alors c’est celle-ci qui sera retournée.

Pensez donc bien que la clause “finally” est exécutées dans tous les cas.

Dans les vraies applications, la clause finally est notamment utile pour libérer des ressources externes (telles que des fichiers ou des connexions réseau), quelle qu’ait été l’utilisation de ces ressources.

Voila qui conclue notre tutoriel. Maintenant vous pouvez gérer vos exceptions dans vos programmes.

A bientôt.