Recherche de site Web

Les exceptions intégrées de Python : une procédure pas à pas avec des exemples


Python dispose d'un ensemble complet d'exceptions intégrées qui fournissent un moyen rapide et efficace de gérer les erreurs et les situations exceptionnelles pouvant survenir dans votre code. Connaître les exceptions intégrées les plus couramment utilisées est essentiel pour vous en tant que développeur Python. Ces connaissances vous aideront à déboguer le code car chaque exception a une signification spécifique qui peut éclairer votre processus de débogage.

Vous serez également en mesure de gérer et de déclencher la plupart des exceptions intégrées dans votre code Python, ce qui constitue un excellent moyen de gérer les erreurs et les situations exceptionnelles sans avoir à créer vos propres exceptions personnalisées.

Dans ce didacticiel, vous allez :

  • Découvrez ce que sont les erreurs et les exceptions en Python
  • Comprendre comment Python organise les exceptions intégrées dans une hiérarchie de classes
  • Explorez les exceptions intégrées les plus couramment utilisées
  • Découvrez comment gérer et déclencher les exceptions intégrées dans votre code.

Pour parcourir ce didacticiel en douceur, vous devez être familier avec certains concepts de base de Python. Ces concepts incluent les classes Python, les hiérarchies de classes, les exceptions, les blocs trysauf et l'instruction raise.

Erreurs et exceptions en Python

Les erreurs et les exceptions sont des concepts importants en programmation, et vous passerez probablement beaucoup de temps à les gérer au cours de votre carrière de programmeur. Les erreurs sont des conditions concrètes, telles que des erreurs de syntaxe et logiques, qui font que votre code ne fonctionne pas correctement, voire plante.

Souvent, vous pouvez corriger les erreurs en mettant à jour ou en modifiant le code, en installant une nouvelle version d'une dépendance, en vérifiant la logique du code, etc.

Par exemple, disons que vous devez vous assurer qu’une chaîne donnée comporte un certain nombre de caractères. Dans ce cas, vous pouvez utiliser la fonction intégrée len() :

>>> len("Pythonista") = 10
  File "<input>", line 1
    ...
SyntaxError: cannot assign to function call here.
    Maybe you meant '==' instead of '='?

Dans cet exemple, vous utilisez le mauvais opérateur. Au lieu d'utiliser l'opérateur de comparaison d'égalité, vous utilisez l'opérateur d'affectation. Ce code génère une SyntaxError, qui représente une erreur de syntaxe comme son nom l'indique.

Pour corriger l'erreur, vous devez localiser le code concerné et corriger la syntaxe. Cette action supprimera l'erreur :

>>> len("Pythonista") == 10
True

Maintenant, le code fonctionne correctement et le SyntaxError a disparu. Ainsi, votre code ne sera pas interrompu et votre programme poursuivra son exécution normale.

Il y a quelque chose à apprendre de l’exemple ci-dessus. Vous pouvez corriger les erreurs, mais vous ne pouvez pas les gérer. En d’autres termes, si vous avez une erreur de syntaxe comme celle de l’exemple, vous ne pourrez pas gérer cette erreur et exécuter le code. Vous devez corriger la syntaxe.

D'autre part, les exceptions sont des événements qui interrompent l'exécution d'un programme. Comme leur nom l’indique, les exceptions se produisent dans des situations exceptionnelles qui devraient ou ne devraient pas se produire. Ainsi, pour éviter que votre programme ne plante après une exception, vous devez gérer l'exception avec le mécanisme de gestion des exceptions approprié.

Pour mieux comprendre les exceptions, disons que vous disposez d'une expression Python telle que a + b. Cette expression fonctionnera si a et b sont tous deux des chaînes ou des nombres :

>>> a = 4
>>> b = 3

>>> a + b
7

Dans cet exemple, le code fonctionne correctement car a et b sont tous deux des nombres. Cependant, l'expression lève une exception si a et b sont de types qui ne peuvent pas être additionnés :

>>> a = "4"
>>> b = 3

>>> a + b
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    a + b
    ~~^~~
TypeError: can only concatenate str (not "int") to str

Étant donné que a est une chaîne et b est un nombre, votre code échoue avec une exception TypeError. Puisqu’il n’existe aucun moyen d’ajouter du texte et des chiffres, votre code est confronté à une situation exceptionnelle.

Python utilise des classes pour représenter les exceptions et les erreurs. Ces classes sont génériquement appelées exceptions, indépendamment de ce qu'une classe concrète représente, une exception ou une erreur. Les classes d'exception nous donnent des informations sur une situation exceptionnelle ainsi que sur les erreurs détectées lors de l'exécution du programme.

Le premier exemple de cette section montre une erreur de syntaxe en action. La classe SyntaxError représente une erreur mais elle est implémentée comme une exception Python. Cela peut prêter à confusion, mais Python utilise des classes d'exceptions pour les erreurs et les exceptions.

Un autre exemple d’exception pourrait être lorsque vous travaillez sur un morceau de code qui traite un fichier texte et que ce fichier n’existe pas. Dans ce cas, vous n’avez pas d’erreur dans votre code. Vous êtes confronté à une situation exceptionnelle que vous devez gérer pour éviter que le programme ne plante. Vous n'avez aucun contrôle sur le problème car vous ne pouvez pas garantir que le fichier existe en modifiant votre code. Vous devez gérer l'exception.

Vous pouvez utiliser des blocs trysauf pour gérer les exceptions en Python. Dans la section suivante, vous apprendrez les bases de cette manipulation.

Gestion des exceptions

Si vous disposez d’un morceau de code qui déclenche une exception et que vous ne fournissez pas de code de gestionnaire pour cette exception, votre programme cessera de s’exécuter. Après cela, un traçage d’exception apparaîtra sur la sortie standard, votre écran.

En Python, vous pouvez gérer les exceptions à l'aide de l'instruction trysauf, qui vous permet d'intercepter l'exception et de fournir des actions de récupération.

Considérez l'exemple suivant. Une exception courante que vous verrez lorsque vous commencerez à utiliser les listes et tuples de Python est IndexError. Cette exception se produit lorsque vous essayez d’accéder à un index hors plage :

>>> numbers = [1, 2, 3]
>>> numbers[5]
Traceback (most recent call last):
    ...
IndexError: list index out of range

Dans cet exemple, la liste de nombres n'a que trois valeurs. Ainsi, lorsque vous essayez d'accéder à l'index 5 dans une opération d'indexation, vous obtenez un IndexError qui casse le code. Vous pouvez envelopper ce code dans un bloc trysauf pour éviter la panne :

>>> try:
...     numbers[5]
... except IndexError:
...     print("Your list doesn't have that index 😔")
...
Your list doesn't have that index 😔

Désormais, le code ne rompt pas avec une exception. Au lieu de cela, il imprime un message à l'écran. Notez que l'appel à print() n'est qu'une action d'espace réservé pour les besoins de l'exemple. Dans le code du monde réel, vous pouvez faire autre chose ici.

L'exemple ci-dessus illustre la construction la plus basique pour gérer les exceptions en Python. Vous pouvez consulter le didacticiel suggéré ci-dessus pour approfondir la gestion des exceptions. Il est maintenant temps de découvrir l’autre côté de la médaille. Vous pouvez également déclencher des exceptions en Python.

Lever des exceptions

Python a l'instruction raise dans sa syntaxe. Vous pouvez utiliser cette instruction pour déclencher des exceptions dans votre code en réponse à des situations exceptionnelles.

À titre d'exemple d'utilisation de l'instruction raise, disons que vous devez écrire une fonction pour calculer la note moyenne des étudiants. Vous obtenez la fonction suivante :

>>> def average_grade(grades):
...     return sum(grades) / len(grades)
...

>>> average_grade([5, 4, 5, 3])
4.25

>>> average_grade([])
Traceback (most recent call last):
    ...
ZeroDivisionError: division by zero

Cette fonction fonctionne bien. Cependant, lorsque la liste des notes est vide, vous obtenez une erreur de division zéro car len(grades) sera 0. Lorsque l'utilisateur du code voit le message d'erreur, il peut être confus. Une erreur de division zéro ? Quelle est la cause de cela ?

Une meilleure approche serait probablement de s'assurer que la liste d'entrée n'est pas vide et de déclencher une exception plus appropriée si tel est le cas :

>>> def average_grade(grades):
...     if not grades:
...         raise ValueError("empty grades not allowed")
...     return sum(grades) / len(grades)
...

>>> average_grade([5, 4, 5, 3])
4.25

>>> average_grade([])
Traceback (most recent call last):
    ...
ValueError: empty grades not allowed

Dans cette version mise à jour de average_grade(), vous ajoutez une instruction conditionnelle qui vérifie si les données d'entrée sont vides. Si tel est le cas, vous déclenchez une ValueError avec un message d'erreur explicite qui indique clairement ce qui ne va pas avec le code.

Les exceptions IndexError et ValueError sont des exemples d'exceptions intégrées couramment utilisées dans Python. Dans les sections suivantes, vous en apprendrez davantage sur ces exceptions et sur plusieurs autres exceptions intégrées.

Ces connaissances vous aideront de plusieurs manières. Tout d’abord, vous serez en mesure de déterminer rapidement le type d’erreur que vous pourriez avoir dans votre code, ce qui améliorera vos compétences en débogage. Deuxièmement, vous disposerez d’un large arsenal d’exceptions déjà disponibles à générer dans votre propre code, vous libérant ainsi de la création d’exceptions personnalisées.

Exceptions intégrées à Python

Python possède plus de soixante exceptions intégrées qui représentent un large éventail d'erreurs courantes et de situations exceptionnelles. Ces exceptions sont organisées en deux groupes :

  1. Exceptions de classe de base
  2. Des exceptions concrètes

Le premier groupe d’exceptions comprend les classes d’exceptions qui sont principalement utilisées comme classes de base pour d’autres exceptions. Par exemple, dans ce groupe, vous disposez de la classe Exception, spécialement conçue pour vous permettre de créer des exceptions personnalisées.

Le deuxième groupe contient des exceptions que vous verrez généralement dans le code Python ou que vous obtiendrez lors de l'exécution du code Python. Par exemple, vous avez probablement constaté certaines des exceptions concrètes suivantes dans votre codage quotidien :

ImportError

Apparaît lorsqu'une instruction import ne peut pas charger un module

ModuleNotFoundError

Se produit lorsque import ne parvient pas à localiser un module donné

NameError

Apparaît lorsqu'un nom n'est pas défini dans la portée globale ou locale

AttributeError

Se produit lorsqu'une référence d'attribut ou une affectation échoue

IndexError

Se produit lorsqu'une opération d'indexation sur une séquence utilise un index hors plage

KeyError

Se produit lorsqu'une clé est manquante dans un dictionnaire ou un autre mappage

ZeroDivisionError

Apparaît lorsque le deuxième opérande d'une opération de division ou modulo est 0

TypeError

Se produit lorsqu'une opération, une fonction ou une méthode opère sur un objet de type inapproprié

ValueError

Se produit lorsqu'une opération, une fonction ou une méthode reçoit le bon type d'argument mais la mauvaise valeur

Ce tableau n’est qu’un petit échantillon des exceptions intégrées de Python. Vous pouvez trouver une liste complète de toutes les exceptions intégrées sur la page Exceptions intégrées de la documentation de Python.

Un coup d'œil à la hiérarchie des exceptions

Comme vous le savez déjà, vous trouverez de nombreuses exceptions intégrées en Python. Vous pouvez les explorer en inspectant l'espace de noms builtins à partir d'une session REPL :

>>> import builtins

>>> dir(builtins)
[
    'ArithmeticError',
    'AssertionError',
    'AttributeError',
    'BaseException',
    ...
]

Dans cet exemple, vous importez d’abord l’espace de noms builtins. Ensuite, vous utilisez la fonction intégrée dir() pour lister les noms définis par ce module. Notez que vous obtiendrez la liste complète des noms intégrés. Entre eux, vous trouverez les exceptions intégrées.

Les exceptions intégrées de Python sont codées sous forme de classes et organisées dans une hiérarchie de classes qui comprend les niveaux suivants :

  • Classes de base : elles fournissent les classes de base pour d'autres exceptions. Vous ne devez les utiliser que comme classes parentes. Cependant, vous pouvez trouver certaines de ces exceptions, telles que la classe Exception, dans certaines bases de code.
  • Exceptions concrètes : ce sont des exceptions que Python déclenchera en réponse à différentes situations exceptionnelles. Ils fournissent également une excellente base d’exceptions concrètes que vous pouvez générer dans votre propre code le cas échéant.
  • Exceptions du système d'exploitation : elles fournissent des exceptions générées par le système d'exploitation. Python les transmet à votre application. Dans la plupart des cas, vous détecterez ces exceptions mais ne les déclencherez pas dans votre code.
  • Avertissements : ils fournissent des avertissements concernant des événements ou des actions inattendus qui pourraient entraîner des erreurs ultérieurement. Ces types particuliers d’exceptions ne représentent pas des erreurs. Les ignorer peut vous causer des problèmes plus tard, mais vous pouvez les ignorer.

Un diagramme de la hiérarchie des exceptions est ci-dessous :

BaseException
 ├── BaseExceptionGroup
 ├── GeneratorExit
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception
      ├── ArithmeticError
      │    ├── FloatingPointError
      │    ├── OverflowError
      │    └── ZeroDivisionError
      ├── AssertionError
      ├── AttributeError
      ├── BufferError
      ├── EOFError
      ├── ExceptionGroup [BaseExceptionGroup]
      ├── ImportError
      │    └── ModuleNotFoundError
      ├── LookupError
      │    ├── IndexError
      │    └── KeyError
      ├── MemoryError
      ├── NameError
      │    └── UnboundLocalError
      ├── OSError
      │    ├── BlockingIOError
      │    ├── ChildProcessError
      │    ├── ConnectionError
      │    │    ├── BrokenPipeError
      │    │    ├── ConnectionAbortedError
      │    │    ├── ConnectionRefusedError
      │    │    └── ConnectionResetError
      │    ├── FileExistsError
      │    ├── FileNotFoundError
      │    ├── InterruptedError
      │    ├── IsADirectoryError
      │    ├── NotADirectoryError
      │    ├── PermissionError
      │    ├── ProcessLookupError
      │    └── TimeoutError
      ├── ReferenceError
      ├── RuntimeError
      │    ├── NotImplementedError
      │    └── RecursionError
      ├── StopAsyncIteration
      ├── StopIteration
      ├── SyntaxError
      │    └── IndentationError
      │         └── TabError
      ├── SystemError
      ├── TypeError
      ├── ValueError
      │    └── UnicodeError
      │         ├── UnicodeDecodeError
      │         ├── UnicodeEncodeError
      │         └── UnicodeTranslateError
      └── Warning
           ├── BytesWarning
           ├── DeprecationWarning
           ├── EncodingWarning
           ├── FutureWarning
           ├── ImportWarning
           ├── PendingDeprecationWarning
           ├── ResourceWarning
           ├── RuntimeWarning
           ├── SyntaxWarning
           ├── UnicodeWarning
           └── UserWarning

Notez que la plupart des classes de la hiérarchie héritent de Exception. Il s'agit également de la classe de base que vous devez utiliser dans les situations où vous devez créer une exception personnalisée.

Connaître les exceptions de base

Dans la hiérarchie des exceptions intégrée, vous trouverez quelques classes conçues pour être des classes de base. La classe BaseException est en haut. Ensuite, vous avez cinq sous-classes :

BaseExceptionGroup

Crée un groupe d'exceptions qui encapsule toute exception plutôt que uniquement celles qui héritent de Exception

GeneratorExit

Se produit lorsqu'un générateur ou une coroutine est fermé

KeyboardInterrupt

Se produit lorsque l'utilisateur appuie sur la combinaison de touches d'interruption, qui est normalement Ctrl<span>+C

SystemExit

Résultat de l'appel de la fonction sys.exit(), mais vous pouvez également la déclencher directement

Exception

Fournit une classe de base pour les exceptions définies par l'utilisateur, qui doivent être dérivées de cette classe

Comme vous pouvez le constater, toutes ces exceptions de base ont leur cas d'utilisation spécifique. Il est important de noter que vous devez utiliser Exception et non BaseException lorsque vous créez des exceptions personnalisées. C'est parce que BaseException doit être utilisé pour les exceptions qui ne devraient jamais être interceptées dans le code réel.

En pratique, lorsque vous détectez et déclenchez des exceptions, vous devez utiliser l’exception la plus spécifique pour le problème en question.

Par exemple, si vous disposez d'un morceau de code susceptible de générer une ValueError, vous devez alors gérer explicitement cette exception. Si vous utilisez Exception au lieu de ValueError, alors votre code interceptera Exception et toutes ses sous-classes, y compris ValueError. Si votre code finit par générer quelque chose de différent de ValueError, alors cette erreur sera mal gérée.

Apprendre à connaître les avertissements

Au bas de la hiérarchie des exceptions, vous trouverez des avertissements. Il s’agit de types particuliers d’exceptions qui dénotent quelque chose qui peut causer des problèmes dans un avenir proche. Les avertissements sont des exceptions liées aux catégories d'avertissements.

L'avertissement le plus courant que vous verrez lors de l'exécution de code Python est probablement DeprecationWarning. Cet avertissement apparaît lorsque vous utilisez des fonctionnalités obsolètes du langage. Par exemple, Python 3.12 a rendu obsolète les constantes calendar.January et calendar.February :

>>> # Python 3.12.2
>>> calendar.January
<stdin>:1: DeprecationWarning: The 'January' attribute is deprecated,
    use 'JANUARY' instead
1

Même si le code fonctionne car les constantes n'ont pas encore été supprimées, Python vous informe que la fonctionnalité est obsolète pour vous éviter d'avoir des problèmes à l'avenir. Comme le dit le message d'avertissement, vous devez maintenant utiliser calendar.JANUARY.

Erreurs de syntaxe

La première exception que vous verrez probablement en Python est l'exception SyntaxError. Même si Python utilise une classe d'exceptions pour ce type de problème, vous devez être clair sur le fait qu'elles représentent des erreurs plutôt que des exceptions. Vous ne les gérerez donc pas, mais vous les réparerez, comme vous l’avez déjà appris.

Vous constaterez également que Python définit quelques exceptions supplémentaires qui héritent de SyntaxError :

    IndentationError
    TabError

Ces exceptions peuvent être attendues lorsque vous commencez à apprendre Python, et elles peuvent vous dérouter. Heureusement, les éditeurs de code et les IDE modernes incluent des fonctionnalités qui détectent et suppriment souvent les conditions qui génèrent ces exceptions. Dans les sections suivantes, vous découvrirez ce groupe d’exceptions.

Erreur de syntaxe

Lorsque Python détecte une syntaxe invalide dans un morceau de code, il déclenche une exception SyntaxError. L'exception imprime un traçage avec des informations utiles que vous pouvez utiliser pour déboguer l'erreur et corriger votre code.

Il existe de nombreuses situations dans lesquelles vous pouvez vous retrouver avec une erreur de syntaxe dans votre code. Vous pouvez oublier une virgule ou une parenthèse fermante, utiliser à mauvais escient un opérateur ou un mot-clé, et bien plus encore.

Voici quelques exemples:

>>> numbers = [1, 2, 3 4]
  File "<stdin>", line 1
    numbers = [1, 2, 3 4]
                     ^^^
SyntaxError: invalid syntax. Perhaps you forgot a comma?

>>> 7 = 7
  File "<stdin>", line 1
    7 = 7
    ^
SyntaxError: cannot assign to literal here.
    Maybe you meant '==' instead of '='?

>>> class = "Economy"
  File "<stdin>", line 1
    class = "Economy"
          ^
SyntaxError: invalid syntax

Ce sont des erreurs de syntaxe courantes dans lesquelles vous pouvez parfois tomber. La bonne nouvelle est qu’avec les messages d’erreur améliorés fournis par Python de nos jours, vous pouvez rapidement localiser l’erreur et la corriger.

Erreur d'indentation

Contrairement à d’autres langages de programmation, l’indentation fait partie de la syntaxe Python. Pour délimiter un bloc de code en Python, vous utilisez l'indentation. Par conséquent, vous pouvez obtenir une erreur si l’indentation n’est pas correcte. Python utilise l'exception IndentationError pour signaler ce problème.

Vous pouvez rencontrer cette exception lorsque vous commencez à apprendre Python. Il peut également apparaître lorsque vous copiez et collez du code à partir d’endroits complexes. Heureusement, cette erreur n’est plus courante de nos jours, car les éditeurs de code modernes peuvent corriger automatiquement l’indentation du code.

Pour voir l'exception en action, considérons l'exemple suivant d'une fonction dont le bloc de code utilise une indentation non uniforme :

>>> def greet(name):
...     print(f"Hello, {name}!")
...   print("Welcome to Real Python!")
  File "<stdin>", line 3
    print("Welcome to Real Python!")
                                   ^
IndentationError: unindent does not match any outer indentation level

Dans cet exemple, les lignes en surbrillance ont des indentations différentes. La première ligne est en retrait de quatre espaces, tandis que la deuxième ligne est en retrait de deux espaces. Cette incompatibilité d'indentation déclenche une IndentationError. Encore une fois, comme il s’agit d’une erreur de syntaxe, Python la signale immédiatement. Vous pouvez résoudre rapidement le problème en corrigeant l'indentation.

TabErreur

Un autre problème possible pour ceux qui viennent d'autres langages de programmation est de mélanger les caractères de tabulation et d'espace lors de l'indentation du code Python. L'exception à ce problème est TabError, qui est une sous-classe de IndentationError. C’est donc une autre exception d’erreur de syntaxe.

Voici un exemple dans lequel Python lève une exception TabError :

def greet(name):
    print(f"Hello, {name}!")
        print("Welcome to Real Python!")

Dans cet exemple, la première ligne de greet() est indentée à l'aide d'un caractère de tabulation, tandis que la deuxième ligne est indentée à l'aide de huit espaces, qui est le nombre courant d'espaces qui remplacent un caractère de tabulation.

Pour essayer l'exemple, continuez et exécutez le fichier à partir de la ligne de commande :

$ python greeting.py
  File ".../greeting.py", line 3
    print("Welcome to Real Python!")
TabError: inconsistent use of tabs and spaces in indentation

Étant donné que l'indentation du code mélange des tabulations et des espaces, vous obtenez une erreur d'indentation TabError. Encore une fois, les exceptions TabError ne sont pas courantes de nos jours car les éditeurs de code modernes peuvent les corriger automatiquement. Donc, si votre éditeur est bien configuré pour le développement Python, vous ne verrez probablement pas cette exception en action.

Exceptions liées à l'importation

Parfois, lors de l'importation de packages, de modules et de leur contenu, votre code échoue avec une erreur vous indiquant que le fichier cible n'a pas été trouvé. En pratique, vous pouvez obtenir l'une des deux exceptions liées à l'importation :

    ModuleNotFoundError
    ImportError

Notez que l'exception ModuleNotFoundError est une sous-classe de ImportError avec une signification ou un objectif plus spécifique, comme vous l'apprendrez dans un instant.

Dans les sections suivantes, vous découvrirez ces deux exceptions et quand Python les déclenche. Ces connaissances vous aideront à résoudre le problème et à faire fonctionner votre code.

ModuleNotFoundError

Comme vous l'avez déjà appris, l'exception ModuleNotFoundError est une sous-classe de ImportError avec un objectif plus spécifique. Python déclenche cette exception lorsqu'il ne trouve pas le module à partir duquel vous souhaitez importer quelque chose :

>>> import non_existing
Traceback (most recent call last):
    ...
ModuleNotFoundError: No module named 'non_existing'

Lorsque Python ne trouve pas le module cible dans son chemin de recherche d'importation, il lève une exception ModuleNotFoundError. Pour résoudre ce problème, vous devez vous assurer que votre module est répertorié dans sys.path.

Parce que ModuleNotFoundError est une sous-classe de ImportError, lorsque vous utilisez explicitement ce dernier dans un bloc trysauf, vous Je vais attraper les deux exceptions. Donc, si votre intention est d'identifier les situations dans lesquelles le module cible n'est pas présent, alors vous devez être précis et utiliser ModuleNotFoundError.

Erreur d'importation

Python lève l'exception ImportError pour tous les problèmes liés à l'importation qui ne sont pas couverts par ModuleNotFoundError. Cela peut se produire pour deux raisons principales :

  1. Une instruction import module ne parvient pas à charger un module pour une raison non couverte par ModuleNotFoundError.
  2. Une instruction from module import name ne parvient pas à trouver name dans le module cible.

Maintenant, allez-y et exécutez l'instruction import suivante pour voir l'exception ImportError en action :

>>> from sys import non_existing
Traceback (most recent call last):
    ...
ImportError: cannot import name 'non_existing' from 'sys' (unknown location)

Dans cet exemple, vous essayez d'importer le nom non_existing depuis le module sys. Comme le nom n'existe pas dans ce module, vous obtenez une ImportError. Vous pouvez résoudre rapidement le problème en fournissant le nom de cible correct.

Dans le code du monde réel, vous pouvez profiter de ImportError ou de ModuleNotFoundError pour éventuellement charger différents modules ou bibliothèques qui fournissent une fonctionnalité donnée en fonction de la disponibilité de la bibliothèque.

Par exemple, disons que vous devez analyser un fichier TOML et lire son contenu. Dans ce cas, vous pouvez utiliser le module de bibliothèque standard tomllib si vous utilisez Python 3.11 ou version ultérieure. Sinon, vous devez utiliser la bibliothèque tierce tomli, qui est compatible avec tomllib.

Voici comment procéder :

try:
    import tomllib  # Python >= 3.11
except ModuleNotFoundError:
    import tomli as tomllib  # Python < 3.11

L'importation dans la clause try cible le module de bibliothèque standard tomllib. Si cette importation génère une exception parce que vous utilisez une version de Python inférieure à 3.11, alors la clause sauf importe la bibliothèque tierce tomli, que vous devez installer en tant que une dépendance externe de votre projet.

Exceptions d'erreur de recherche

Obtenir une IndexError lorsque vous effectuez des opérations d'indexation sur une séquence, ou une KeyError lorsque vous recherchez des clés dans des dictionnaires, sont également des problèmes courants en Python. Dans les sections suivantes, vous découvrirez ces deux exceptions et quand elles peuvent se produire dans votre code.

Erreur d'index

L'exception IndexError se produit lorsque vous essayez de récupérer une valeur d'une séquence à l'aide d'un index hors plage :

>>> colors = [
...     "red",
...     "orange",
...     "yellow",
...     "green",
...     "blue",
...     "indigo",
...     "violet",
... ]

>>> colors[10]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    colors[10]
    ~~~~~~^^^^
IndexError: list index out of range

Dans cet exemple, vous utilisez 10 comme index pour obtenir une valeur de votre liste colors. Étant donné que la liste ne contient que sept éléments, les indices valides vont de 0 à 6. Votre index cible est hors plage et Python vous renvoie une exception IndexError.

IndexError peut être une exception fréquente en Python, en particulier lorsque vous utilisez des index générés dynamiquement. Pour résoudre le problème, vous devez déterminer quel index votre code utilise pour récupérer une valeur de la séquence cible, puis ajuster la plage d'index pour corriger le problème.

Si vous ne pouvez pas contrôler la plage d'index, vous pouvez alors intercepter l'exception dans un bloc trysauf et prendre l'action de récupération appropriée.

Vous pouvez également déclencher l'exception IndexError dans votre propre code. Cette exception peut être appropriée lorsque vous créez des structures de données personnalisées de type séquence. Par exemple, disons que vous devez créer une pile de type séquence :

class Stack:
    def __init__(self, items=None):
        self.items = list(items) if items is not None else []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def __len__(self):
        return len(self.items)

    def __getitem__(self, index):
        try:
            return self.items[index]
        except IndexError:
            raise IndexError(
                f"your stack only has {len(self)} items!"
            ) from None

Dans cet exemple, vous définissez Stack et fournissez les méthodes .push() et .pop(). La première méthode ajoute un nouvel élément en haut de la pile, tandis que la seconde supprime et renvoie l'élément en haut de la pile.

Ensuite, vous disposez de la méthode spéciale .__len__() pour prendre en charge la fonction intégrée len(), qui vous donne le nombre d'éléments dans la pile.

Enfin, vous disposez de la méthode .__getitem__(). Cette méthode spéciale prend un index comme argument et renvoie l'élément à l'index d'entrée. Le bloc trysauf intercepte l'exception IndexError et la relance avec un message d'erreur plus spécifique. Vous ne vous fiez pas au message d'origine, ce qui rend votre code plus ciblé.

Erreur de clé

De même, lorsque vous essayez d'obtenir une clé inexistante à partir d'un dictionnaire, vous obtenez une exception KeyError. Ce problème est également courant en Python :

>>> fruits = {"apple": 0.40, "orange": 0.35, "banana": 0.25}

>>> fruits["grape"]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    fruits["grape"]
    ~~~~~~^^^^^^^^^
KeyError: 'grape'

Dans cet exemple, vous essayez d'obtenir la clé grape du dictionnaire fruits et obtenez une exception KeyError. Dans ce cas, le message d'erreur est assez court. Il affiche uniquement le nom de la clé manquante.

Encore une fois, l’utilisation de clés générées dynamiquement peut être à l’origine du problème. Ainsi, pour corriger le problème, vous devrez vérifier les clés générées et existantes dans votre code. Vous pouvez également utiliser la méthode .get() avec une valeur par défaut appropriée si c'est une solution adaptée à votre cas d'utilisation.

Enfin, vous pouvez également générer une KeyError dans votre code. Cela peut être utile lorsque vous devez créer une classe de type dictionnaire et fournir un message d'erreur plus descriptif à vos utilisateurs.

Erreurs de nom : NameError

L'exception NameError est également assez courante lorsque vous débutez avec Python. Cette exception se produit lorsque vous essayez d'utiliser un nom qui n'est pas présent dans votre espace de noms actuel. Ainsi, cette exception est étroitement liée aux étendues et aux espaces de noms.

Par exemple, disons que vous travaillez dans une session REPL. Vous avez importé le module sys pour l'utiliser dans votre tâche actuelle. Pour une raison quelconque, vous redémarrez la session interactive et essayez d'utiliser sys sans la réimporter :

>>> sys.path
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    sys.path
    ^^^
NameError: name 'sys' is not defined. Did you forget to import 'sys'?

Étant donné que vous avez redémarré la session interactive, tous les noms et modules que vous avez importés auparavant ont disparu. Désormais, lorsque vous essayez d'utiliser le module sys, vous obtenez un NameError.

Notez que si le nom inconnu se trouve après le point dans un nom qualifié, vous ne verrez pas de NameError. Dans ce cas, vous obtiendrez à la place une exception AttributeError, que vous découvrirez dans la section suivante.

Exceptions liées aux objets

Vous trouverez quelques exceptions qui peuvent survenir lorsque vous travaillez avec des classes, des objets et des types intégrés Python. Les plus courants sont les suivants :

    TypeError
    ValueError
    AttributeError

Dans les sections suivantes, vous apprendrez quand ces exceptions se produisent et comment les gérer dans votre code Python.

TypeErreur

Python déclenche une exception TypeError lorsque vous appliquez une opération ou une fonction à un objet qui ne prend pas en charge cette opération. Par exemple, envisagez d'appeler la fonction intégrée len() avec un nombre comme argument :

>>> len(42)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    len(42)
TypeError: object of type 'int' has no len()

Dans cet exemple, l'argument de len() est un nombre entier, qui ne prend pas en charge la fonction. Par conséquent, vous obtenez une erreur avec un message approprié.

En pratique, vous pouvez intercepter l'exception TypeError dans votre propre code lorsque vous devez éviter un problème lié au type. Vous pouvez également déclencher l'exception dans votre code pour indiquer un problème lié au type. Par exemple, supposons que vous souhaitiez écrire une fonction qui prend un itérable de nombres et renvoie une liste de valeurs au carré. Vous voulez vous assurer que la fonction n'accepte que les objets itérables.

Dans cette situation, vous pouvez avoir une fonction comme la suivante :

>>> def squared(numbers):
...     try:
...         iter(numbers)
...     except TypeError:
...         raise TypeError(
...             f"expected an iterable, got '{type(numbers).__name__}'"
...         ) from None
...     return [number**2 for number in numbers]
...

>>> squared([1, 2, 3, 4, 5])
[1, 4, 9, 16, 25]

>>> squared(42)
Traceback (most recent call last):
    ...
TypeError: expected an iterable, got 'int'

Dans cet exemple, votre fonction utilise la fonction intégrée iter() pour vérifier les données d'entrée. Cette fonction lève une exception TypeError si l'entrée fournie n'est pas itérable. Vous interceptez cette exception et la relancez, mais avec un message d'erreur personnalisé qui respecte l'objectif de votre code. Ensuite, vous construisez la liste des valeurs au carré à l’aide d’une compréhension de liste.

ValueErreur

De même, Python déclenche l'exception ValueError lorsqu'une opération ou une fonction obtient un argument qui a le bon type mais une valeur inappropriée :

>>> float("1")
1.0

>>> float("one")
Traceback (most recent call last):
    ...
ValueError: could not convert string to float: 'one'

Dans cet exemple, vous utilisez d'abord la fonction intégrée float() pour convertir une chaîne en nombre à virgule flottante. La chaîne d'entrée contient une valeur numérique, l'opération réussit donc.

Dans le deuxième exemple, vous appelez la même fonction en utilisant une chaîne qui ne représente pas un nombre valide et obtenez une exception ValueError. Notez que l'argument d'entrée est une chaîne, ce qui correspond au type d'argument correct. Cependant, la chaîne d’entrée n’est pas une valeur numérique valide, vous avez donc le bon type mais la mauvaise valeur.

Vous pouvez également utiliser ValueError dans votre code Python. Cette exception peut être appropriée dans différentes situations. Par exemple, disons que vous devez écrire une classe pour représenter les couleurs de l’arc-en-ciel. Cette classe a sept noms de couleurs autorisés, que vous pouvez représenter avec des chaînes :

COLORS = {
    "Red": {"Hex": "#FF0000", "RGB": (255, 0, 0)},
    "Orange": {"Hex": "#FF7F00", "RGB": (255, 127, 0)},
    "Yellow": {"Hex": "#FFFF00", "RGB": (255, 255, 0)},
    "Green": {"Hex": "#00FF00", "RGB": (0, 255, 0)},
    "Blue": {"Hex": "#0000FF", "RGB": (0, 0, 255)},
    "Indigo": {"Hex": "#4B0082", "RGB": (75, 0, 130)},
    "Violet": {"Hex": "#8B00FF", "RGB": (139, 0, 255)},
}

class RainbowColor:
    def __init__(self, name="Red"):
        name = name.title()
        if name not in COLORS:
            raise ValueError(f"{name} is not a valid rainbow color")
        self.name = name

    def as_hex(self):
        return COLORS[self.name]["Hex"]

    def as_rgb(self):
        return COLORS[self.name]["RGB"]

Dans cet exemple, vous définissez d'abord une constante COLORS qui contient les couleurs de l'arc-en-ciel et leur représentation Hex et RVB correspondante.

Ensuite, vous définissez la classe RainbowColor. Dans l'initialiseur de classe, vous utilisez une condition pour vous assurer que le nom de la couleur d'entrée est l'une des couleurs de l'arc-en-ciel. Si ce n'est pas le cas, alors vous déclenchez une ValueError car le nom de la couleur est du type correct mais a une valeur incorrecte. C'est une chaîne mais pas un nom de couleur valide.

Voici comment le cours fonctionne en pratique :

>>> from rainbow import RainbowColor

>>> color = RainbowColor("Gray")
Traceback (most recent call last):
    ...
ValueError: Gray is not a valid rainbow color

>>> color = RainbowColor("Blue")
>>> color.name
'Blue'
>>> color.as_rgb()
(0, 0, 255)

Dans cet extrait de code, lorsque vous essayez de transmettre un nom de couleur qui n'est pas autorisé, vous obtenez une ValueError. Si le nom de la couleur est valide, alors la classe fonctionne correctement.

Erreur d'attribut

Une autre exception courante que vous verrez lorsque vous travaillez avec des classes Python est AttributeError. Cette exception se produit lorsque l'objet spécifié ne définit pas l'attribut ou la méthode à laquelle vous essayez d'accéder.

Prenez, par exemple, votre RainbowClass de la section précédente. Cette classe a un attribut et deux méthodes, .name, .as_hex() et .as_rgb(). Si vous y accédez, vous obtiendrez un résultat en fonction de ce qu’ils font. Cependant, supposons que vous essayiez d'accéder à une méthode appelée .as_hsl(). Ce qui se passerait?

Voici la réponse :

>>> from rainbow import RainbowColor

>>> color = RainbowColor("Blue")

>>> color.as_hsl()
Traceback (most recent call last):
    ...
AttributeError: 'RainbowColor' object has no attribute 'as_hsl'

Parce que RainbowColor ne définit pas la méthode .as_hsl(), vous obtenez une AttributeError qui vous indique que la classe n'a pas d'attribut qui correspond à ce nom.

Cette erreur peut être courante dans les hiérarchies de classes complexes qui définissent des classes similaires avec des interfaces légèrement différentes. Dans cette situation, vous pouvez penser qu’une classe donnée définit une méthode ou un attribut donné, mais ce n’est peut-être pas le cas. Pour résoudre le problème, vous pouvez consulter la définition de la classe ou la documentation pour vous assurer que vous utilisez uniquement les attributs et les méthodes définis par la classe.

Vous pouvez ignorer l'exception AttributeError en utilisant la fonction intégrée hasattr() :

>>> if hasattr(color, "as_hsl"):
...     color.as_hsl()
... else:
...     print("Hmm, you can't call that.")
...
Hmm, you can't call that.

Dans cet exemple, vous utilisez hasattr() pour vérifier explicitement si l'objet color a un attribut appelé "as_hsl".

Vous pouvez également intercepter et déclencher l'exception AttributeError dans vos classes personnalisées. En pratique, cette exception peut être assez utile car Python vous encourage à utiliser un style de codage basé sur la gestion des exceptions appelé EAFP (plus facile de demander pardon que d'autoriser).

En bref, utiliser ce style signifie que vous décidez de gérer les erreurs et les exceptions lorsqu'elles se produisent. En revanche, avec le style LBYL (regardez avant de sauter), vous essayez d'éviter les erreurs et les exceptions avant qu'elles ne se produisent. C'est ce que vous avez fait dans l'exemple ci-dessus.

Ainsi, au lieu de vous fier à hasattr(), vous devriez écrire l'exemple ci-dessus comme suit :

>>> try:
...     color.as_hsl()
... except AttributeError:
...     print("Hmm, you can't call that.")
...
Hmm, you can't call that.

Ce style de codage est plus direct. Vous allez appeler la méthode souhaitée. Si cela échoue avec une AttributeError, alors vous effectuez des actions alternatives.

Enfin, vous obtiendrez également une exception AttributeError lorsque vous tenterez d'accéder à un objet qui n'est pas défini dans un module déjà importé :

>>> import sys

>>> sys.non_existing
Traceback (most recent call last):
    ...
AttributeError: module 'sys' has no attribute 'non_existing'

Notez que vous n’obtenez pas d’erreur d’importation dans cet exemple. Au lieu de cela, vous obtenez une exception AttributeError. C'est parce que les objets définis dans un module deviennent des attributs de ce module.

Exceptions d'erreur arithmétique

La classe d'exceptions ArithmeticError est la classe de base pour les exceptions intégrées liées à trois types différents d'erreurs arithmétiques :

    ZeroDivisionError
    FloatingPointError
    OverflowError

Parmi ces trois exceptions, la plus courante est ZeroDivisionError. Le FloatingPointError est défini comme une exception intégrée, mais Python ne l'utilise pas de nos jours. Dans les sections suivantes, vous découvrirez quand les exceptions ZeroDivisionError et OverflowError peuvent apparaître dans votre code.

Erreur de division zéro

Python déclenche l'exception ZeroDivisionError lorsque le diviseur d'une division ou l'opérande droit est nul. À titre d'exemple rapide, disons que vous devez écrire une fonction pour calculer la note moyenne d'un groupe d'étudiants. Dans ce cas, vous pouvez proposer la fonction suivante :

>>> def average_grade(grades):
...     return sum(grades) / len(grades)
...

>>> average_grade([4, 3, 3, 4, 5])
3.8

Cette fonction fonctionne correctement lorsque vous utilisez les données d'entrée appropriées. Mais que se passerait-il si vous appeliez la fonction avec un objet liste vide ? Voici la réponse :

>>> average_grade([])
Traceback (most recent call last):
    ...
ZeroDivisionError: division by zero

La liste d'entrée étant vide, la fonction len() renvoie 0. Cette valeur de retour produit une exception ZeroDivisionError lorsque la division réelle se produit.

Pour résoudre le problème, vous pouvez intercepter l'exception et présenter à l'utilisateur un message d'erreur descriptif :

>>> def average_grade(grades):
...     try:
...         return sum(grades) / len(grades)
...     except ZeroDivisionError:
...         raise ValueError(
...             "you should pass at least one grade to average_grade()"
...         ) from None
...

>>> average_grade([])
Traceback (most recent call last):
    ...
ValueError: you should pass at least one grade to average_grade()

Dans cette version mise à jour de average_grade(), vous détectez le ZeroDivisionError généré par une liste d'entrée vide et déclenchez un ValueError avec une erreur conviviale. message.

Enfin, l'exception ZeroDivisionError se produit également lorsque l'opérande de droite d'une opération modulo est nul :

>>> 42 % 0
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    42 % 0
    ~~~^~~
ZeroDivisionError: integer modulo by zero

L'opérateur modulo renvoie le reste de la division de deux nombres. Étant donné que la division des nombres est la première étape du calcul du reste, vous pouvez obtenir une ZeroDivisionError si l'opérande de droite est 0.

Erreur de débordement

Le OverflowError n'est pas si courant. Python lève cette exception lorsque le résultat d'une opération arithmétique est trop grand et qu'il n'y a aucun moyen de le représenter :

>>> 10.0**1000
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    10.0**1000
    ~~~~^^~~~~
OverflowError: (34, 'Result too large')

Dans cet exemple, le nombre résultant de l’opération d’alimentation est trop élevé et Python ne peut pas le représenter correctement. Ainsi, vous obtenez une exception OverflowError.

Exceptions liées aux itérations

Python possède quelques exceptions intégrées étroitement liées à l'itération. Le RecursionError est lié au code récursif et est ce que vous pouvez appeler une exception standard. Ensuite, vous avez l'exception StopIteration, que Python utilise en interne pour contrôler le flux d'exécution des boucles for. Cette exception a une contrepartie asynchrone appelée AsyncStopIteration, que Python utilise dans les boucles async for.

Dans les sections suivantes, vous découvrirez les exceptions RecursionError et StopIteration et comment Python les utilise.

Erreur de récursion

Une fonction qui s'appelle elle-même est appelée fonction récursive. Ces types de fonctions sont à la base d'une technique d'itération appelée récursion.

Considérez la fonction jouet suivante comme exemple de fonction récursive :

>>> def recurse():
...     recurse()
...

Cette fonction ne suit pas les règles de récursivité car elle n'a pas de cas de base qui arrête la répétition. Il n'y a qu'un cas récursif. Ainsi, si vous appelez la fonction, vous obtenez une exception RecursionError :

>>> recurse()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    recurse()
  File "<input>", line 2, in recurse
    recurse()
  File "<input>", line 2, in recurse
    recurse()
  File "<input>", line 2, in recurse
    recurse()
  [Previous line repeated 981 more times]
RecursionError: maximum recursion depth exceeded

Python déclenchera une exception RecursionError lorsque l'interpréteur détectera que la profondeur de récursion maximale est dépassée. Cela peut se produire lorsque votre scénario de base ne fonctionne pas correctement ou lorsque le nombre de récursions requis pour effectuer le calcul dépasse la limite de récursivité.

Vous pouvez utiliser la fonction sys.getrecursionlimit() pour vérifier votre profondeur de récursion actuelle :

>>> import sys

>>> sys.getrecursionlimit()
1000

Le résultat de l’appel de cette fonction est un nombre représentant le nombre de fois qu’une fonction récursive peut s’appeler en Python. Si cela est approprié pour votre cas d'utilisation, vous pouvez également définir une limite de récursion différente à l'aide de la fonction sys.setrecursionlimit().

Arrêter l'itération

Python lève une exception StopIteration lorsque vous appelez la fonction intégrée next() sur un itérateur épuisé. Son but est de signaler qu’il n’y a plus d’éléments dans l’itérateur. Ainsi, Python utilise cette exception en interne pour contrôler l’itération.

Prenons l'exemple de jouet suivant :

>>> numbers_iterator = iter([1, 2, 3])

>>> next(numbers_iterator)
1
>>> next(numbers_iterator)
2
>>> next(numbers_iterator)
3
>>> next(numbers_iterator)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    next(numbers_iterator)
StopIteration

Dans cet exemple, vous utilisez la fonction intégrée iter() pour créer un itérateur à partir d'une courte liste de nombres. Ensuite, vous appelez next() à plusieurs reprises pour consommer l'itérateur. Le quatrième appel à cette fonction lève une exception StopIteration pour signaler que l'itérateur n'a plus d'éléments à récupérer.

En interne, Python utilise cette exception pour terminer les boucles for. En d'autres termes, lorsqu'une boucle for itère sur un itérateur, la boucle s'arrête lorsqu'elle obtient l'exception StopIteration. Pour cette raison, il n’est pas courant de voir cette exception en action. Notez que la boucle for de Python appelle next() sous le capot pour effectuer l'itération.

En pratique, vous pouvez utiliser l'exception StopIteration dans votre code dans le cadre d'une implémentation du protocole itérateur. Ce protocole se compose de deux méthodes spéciales :

  1. .__iter__() est appelé pour initialiser l'itérateur. Il doit renvoyer un objet itérateur.
  2. .__next__() est appelé pour parcourir l'itérateur. Il doit renvoyer la valeur suivante dans le flux de données.

Lorsque vous fournissez ces méthodes dans une classe personnalisée, votre classe prend en charge l'itération. Pour signaler quand l'itération doit s'arrêter, vous devez déclencher l'exception StopIteration dans la méthode .__next__().

Pour illustrer comment utiliser StopIteration, disons que vous devez créer une classe personnalisée qui prend une liste de valeurs et crée un itérateur sur leurs carrés :

class SquareIterator:
    def __init__(self, values):
        self.values = values
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.values):
            raise StopIteration
        square = self.values[self.index] ** 2
        self.index += 1
        return square

Dans cette classe, l'initialiseur prend une liste de valeurs comme argument. Il définit également un index de référence, qui commence à 0. La méthode .__iter__() renvoie self, ce qui est une pratique courante lorsque vous utilisez le protocole itérateur comme dans cet exemple.

Dans la méthode .__next__(), vous vérifiez si l'index actuel est supérieur ou égal au nombre de valeurs dans la liste d'entrée. Si tel est le cas, alors vous déclenchez un StopIteration pour signaler que l'itérateur est épuisé. Ensuite, vous calculez le carré de la valeur actuelle et le renvoyez comme résultat de la méthode.

Vous pouvez désormais utiliser des instances de cette classe dans une boucle for :

>>> from squares import SquareIterator

>>> for value in SquareIterator([1, 2, 3]):
...     print(value)
...
1
4
9

Votre itérateur fonctionne bien. Il génère des valeurs carrées et prend en charge l'itération. Notez que vous ne voyez pas l'exception StopIteration se produire. Cependant, Python utilise l'exception en interne pour terminer la boucle lorsque l'itérateur est terminé.

Exceptions liées aux fichiers

Lors du traitement de fichiers dans votre code Python, vous pouvez être confronté à plusieurs problèmes différents. Votre code peut tenter de créer un fichier qui existe déjà, d'accéder à un fichier inexistant, d'exécuter une opération de fichier sur un répertoire plutôt que sur un fichier ou d'avoir des problèmes d'autorisation. Python possède des exceptions liées aux fichiers qui signalent ces situations.

Voici quelques-uns des plus populaires :

    FileExistsError
    FileNotFoundError
    PermissionError

Dans les sections suivantes, vous découvrirez ces exceptions intégrées et quand elles se produisent en Python.

FileExistsError

Lorsque vous essayez de créer un fichier ou un répertoire qui existe déjà, Python déclenche l'exception intégrée FileExistsError.

Pour voir cette exception en action, disons que vous devez créer un fichier dans votre répertoire de travail et y écrire du texte. Cependant, vous souhaitez éviter de remplacer le fichier s'il existe déjà. Dans ce cas, vous pouvez profiter du FileExistsError et écrire le code suivant :

try:
    with open("hello.txt", mode="x") as file:
        file.write("Hello, World!")
except FileExistsError:
    print("The file already exists")

Dans cet exemple, vous utilisez la fonction intégrée open() pour ouvrir un fichier appelé hello.txt. Le mode "x" signifie que vous souhaitez ouvrir le fichier pour une création exclusive, en échouant si le fichier existe déjà.

Maintenant, allez-y et exécutez ce fichier hello.py depuis votre ligne de commande :

$ python hello.py

Cette commande n’émettra aucune sortie. Cependant, si vous regardez votre répertoire de travail, vous verrez un nouveau fichier appelé hello.txt avec le texte "Hello, World!" dedans.

Si vous exécutez à nouveau la commande, le résultat sera différent :

$ python hello.py
The file already exists

Cette fois, comme le fichier hello.txt existe déjà, le code de création du fichier génère une exception FileExistsError et la clause sauf affiche une erreur. message à l'écran.

ErreurFileNotFound

Python déclenche l'exception FileNotFoundError lorsqu'un fichier ou un répertoire est demandé mais n'existe pas à l'emplacement cible. Par exemple, supposons que vous ayez accidentellement supprimé le fichier hello.txt que vous avez créé dans la section précédente. Lorsque vous essayez de lire le contenu du fichier, vous obtenez une erreur :

>>> with open("hello.txt", mode="r") as file:
...     print(file.read())
...
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    with open("hello.txt", mode="r") as file:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'hello.txt'

Dans cet extrait de code, vous essayez d'ouvrir le fichier hello.txt pour lire son contenu. Étant donné que vous avez supprimé le fichier avant cette opération, votre code échoue avec l'exception FileNotFoundError.

Pour résoudre ce problème, vous pouvez réécrire votre code comme dans l'exemple suivant :

>>> try:
...     with open("hello.txt", mode="r") as file:
...         print(file.read())
... except FileNotFoundError:
...     print("The file doesn't exist")
...
The file doesn't exist

Maintenant, vous utilisez un bloc trysauf pour gérer l'exception FileNotFoundError et empêcher votre code d'éclater.

Erreur d'autorisation

Un autre problème courant lors du traitement de fichiers survient lorsque vous essayez d'accéder à un fichier ou à un répertoire sans autorisations d'accès adéquates. Dans ce cas, Python déclenche l'exception PermissionError pour vous informer que vous ne disposez pas des autorisations requises.

Par exemple, disons que vous travaillez sur un système de type Unix, tel que Linux ou macOS. Vous souhaitez mettre à jour le contenu du fichier /etc/hosts. Dans les systèmes de type Unix, seul l'utilisateur root peut modifier ce fichier. Ainsi, si vous essayez d’effectuer votre tâche en utilisant un autre utilisateur, vous obtiendrez une erreur :

>>> with open("/etc/hosts", mode="a") as file:
...     print(file.write("##"))
...
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    with open("/etc/hosts", mode="a") as file:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^
PermissionError: [Errno 13] Permission denied: '/etc/host'

Cette erreur se produit car le fichier /etc/hosts est un fichier système. Vous avez besoin des autorisations root pour modifier ce fichier. Votre code tente d'ouvrir le fichier pour ajouter du contenu sans disposer des autorisations nécessaires, ce qui entraîne une exception PermissionError.

Exception liée aux classes abstraites : NotImplementedError

Parfois, vous souhaitez créer une classe de base personnalisée avec une interface prédéfinie à partir de laquelle vos utilisateurs peuvent dériver leurs propres classes. De cette façon, vous vous assurez que les sous-classes remplissent l’interface requise. En Python, vous pouvez le faire en utilisant ce qu’on appelle une classe de base abstraite (ABC).

Le module abc de la bibliothèque standard fournit la classe ABC et d'autres outils associés que vous pouvez utiliser pour définir des classes de base personnalisées qui définissent une interface spécifique. Notez que vous ne pouvez pas instancier directement les ABC. Ils sont destinés à être sous-classés.

Lorsque vous commencerez à créer votre ABC, vous constaterez que lorsque vous définissez des méthodes, il est courant de déclencher une exception NotImplementedError pour les méthodes que les sous-classes doivent implémenter mais qui ne constituent pas une exigence stricte.

À titre d'exemple, supposons que vous souhaitiez créer une hiérarchie de classes pour représenter différents oiseaux, comme un canard, un cygne, un pingouin, etc. Vous décidez que toutes les classes doivent avoir les méthodes .swim() et .fly(). Dans cette situation, vous pouvez commencer avec la classe de base suivante :

from abc import ABC

class Bird(ABC):
    def swim(self):
        raise NotImplementedError("must be implemented in subclasses")

    def fly(self):
        raise NotImplementedError("must be implemented in subclasses")

Dans cette classe Bird, vous héritez de abc.ABC, ce qui signifie que vous construisez une classe de base abstraite. Ensuite, vous définissez les méthodes .swim() et .fly(), qui déclenchent l'exception NotImplementedError avec un message d'erreur approprié.

Voici un exemple de la façon dont vous pouvez dériver des oiseaux concrets de la classe ci-dessus :

# ...

class Duck(Bird):
    def swim(self):
        print("The duck is swimming")

    def fly(self):
        print("The duck is flying")

class Penguin(Bird):
    def swim(self):
        print("The penguin is swimming")

Dans cet exemple, vous créez la classe Duck avec les méthodes .swim() et .fly(). Ensuite, vous créez la classe Penguin, qui n'a qu'une méthode .swim() car les pingouins ne peuvent pas voler. Vous pouvez utiliser la classe Duck normalement. En revanche, la classe Penguin se comportera différemment lorsque vous appellerez sa méthode .fly() :

>>> from birds import Duck

>>> donald = Duck()
>>> donald.swim()
The duck is swimming
>>> donald.fly()
The duck is flying

>>> skipper = Penguin()
>>> skipper.swim()
The penguin is swimming
>>> skipper.fly()
Traceback (most recent call last):
    ...
NotImplementedError: must be implemented in subclasses

Dans cet extrait de code, la classe Duck fonctionne comme prévu. Pendant ce temps, la classe Penguin génère une NotImplementedError lorsque vous appelez la méthode .fly() sur l'une de ses instances.

Erreurs d'assertion : AssertionError

Python possède une fonctionnalité appelée assertions que vous pouvez définir à l'aide de l'instruction assert. Les assertions vous permettent de définir des vérifications d'intégrité pendant le développement de votre code. Les assertions vous permettent de tester l'exactitude de votre code en vérifiant si certaines conditions spécifiques restent vraies. Cela s’avère utile lorsque vous testez et déboguez votre code.

L'instruction assert a la syntaxe suivante :

assert expression[, assertion_message]

La partie expression est une condition qui devrait être vraie sauf si vous avez un bug dans votre programme. Si la condition devient fausse, alors l'assertion lève une exception AssertionError et met fin à l'exécution de votre programme.

Les assertions sont utiles pour rédiger des cas de test. Vous pouvez les utiliser pour vérifier des hypothèses telles que les préconditions et les postconditions. Par exemple, vous pouvez tester si un argument est d’un type donné, vous pouvez tester la valeur de retour d’une fonction, etc. Ces vérifications peuvent vous aider à détecter les erreurs le plus rapidement possible lorsque vous développez un programme.

À titre d'exemple, disons que vous participez à un entretien de codage Python. Il vous est demandé d'implémenter une fonction qui relève le défi FizzBuzz, où vous renvoyez "fizz" pour les nombres divisibles par trois, "buzz" pour ceux divisibles par cinq, et "fizz buzz" pour ceux divisibles par trois et par cinq. Vous écrivez une fonction comme celle-ci :

def fizzbuzz(number):
    if number % 3 == 0:
        return "fizz"
    elif number % 5 == 0:
        return "buzz"
    elif number % 15 == 0:
        return "fizz buzz"
    else:
        return number

Cette fonction couvre apparemment tous les scénarios possibles. Cependant, vous décidez d'écrire quelques tests de base pour vous assurer que la fonction fonctionne correctement. Vous vous retrouvez avec le code suivant à la fin de votre fichier fizzbuzz.py :

# ...

if __name__ == "__main__":
    assert fizzbuzz(9) == "fizz"
    assert fizzbuzz(10) == "buzz"
    assert fizzbuzz(15) == "fizz buzz"
    assert fizzbuzz(7) == 7
    print("All tests pass")

Dans cet extrait de code, vous avez ajouté quelques assertions rapides pour vérifier si votre fonction renvoie la bonne chaîne avec différentes valeurs d'entrée. Maintenant, vous pouvez exécuter le test en exécutant le fichier depuis la ligne de commande :

$ python fizzbuzz.py
Traceback (most recent call last):
  File ".../fizzbuzz.py", line 26, in <module>
    assert fizzbuzz(15) == "fizz buzz"
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError

Ouah! Vous avez reçu une exception AssertionError, ce qui signifie que certains tests ont échoué. Lorsque vous examinez le traçage des exceptions, vous notez que l'assertion échoue lorsque vous appelez la fonction avec 15 comme argument. Ce nombre est divisible par 3 et par 5, donc l'ordre actuel de vos conditions est incorrect.

Vous devez déplacer la condition numéro % 15 == 0 en première position :

def fizzbuzz(number):
    if number % 15 == 0:
        return "fizz buzz"
    elif number % 3 == 0:
        return "fizz"
    elif number % 5 == 0:
        return "buzz"
    else:
        return number

# ...

Les conditions vérifient d'abord si le nombre saisi est divisible par 3 et 5. Maintenant, continuez et exécutez à nouveau le fichier :

$ python fizzbuzz.py
All tests pass

Super! Tous vos tests réussissent. La fonction fait son travail correctement. C'est la puissance de l'instruction assert.

Enfin, notez que vous ne devez pas déclencher explicitement l'exception AssertionError dans votre code. Au lieu de cela, vous devez laisser l'instruction assert déclencher cette exception lorsque la condition de l'assertion échoue. De plus, vous ne devez pas tenter de gérer les erreurs en écrivant du code qui intercepte l'exception AssertionError car les assertions peuvent être désactivées.

Exceptions de sortie d’interprète

Lors de l’écriture de code Python, vous rencontrerez des situations dans lesquelles vous devrez quitter ou mettre fin à une application ou à un programme en cours d’exécution.

Par exemple, si une erreur se produit pendant l'exécution d'une application, vous pouvez décider de quitter proprement l'application avec un état de sortie approprié, ce qui peut être utile dans les applications en ligne de commande. Un autre exemple est lorsque vous testez un code qui prend trop de temps à s’exécuter ou qui est bloqué d’une manière ou d’une autre. Dans ce cas, vous souhaitez un moyen rapide de mettre fin à l’exécution du code.

Python présente les exceptions suivantes qui traitent de ces situations :

    SystemExit
    KeyboardInterrupt

En général, votre code ne devrait pas détecter ou gérer ces exceptions. Cela peut vous empêcher de quitter votre programme, rendant impossible la fermeture à partir du code ou à l'aide du clavier.

Dans les sections suivantes, vous découvrirez quand ces exceptions peuvent se produire dans votre code Python. Vous écrirez également quelques exemples illustrant leur utilisation.

Sortie du système

Python déclenche l'exception SystemExit lorsque vous appelez la fonction sys.exit() dans votre code. Lorsque vous ne gérez pas l'exception, l'interpréteur Python se termine sans imprimer de trace d'exception :

>>> import sys
>>> sys.exit(0)
$

Dans cet exemple, vous appelez sys.exit() pour terminer l'interpréteur Python. Cet appel vous ramène à votre session de terminal.

En pratique, vous pouvez utiliser SystemExit directement lorsque vous devez terminer une application. Par exemple, supposons que vous souhaitiez créer une application CLI (interface de ligne de commande) minimale qui imite la fonctionnalité de base de la commande Unix ls, qui répertorie le contenu d'un répertoire donné.

Dans cette situation, vous pouvez écrire un script comme celui-ci :

import sys
from pathlib import Path

if (args_count := len(sys.argv)) > 2:
    print(f"One argument expected, got {args_count - 1}")
    raise SystemExit(2)
elif args_count < 2:
    print("You must specify the target directory")
    raise SystemExit(2)

target_dir = Path(sys.argv[1])

if not target_dir.is_dir():
    print("The target directory doesn't exist")
    raise SystemExit(1)

for entry in target_dir.iterdir():
    print(entry.name)

Ce programme traite les arguments fournis sur la ligne de commande, qui sont automatiquement stockés dans la variable sys.argv. Le premier élément de sys.argv est le nom du programme. Le deuxième élément doit être le répertoire cible.

L'application ne doit accepter qu'un seul répertoire cible, donc la variable args_count doit être 2 au maximum. Si l'application obtient plusieurs répertoires cibles, vous imprimez un message d'erreur et déclenchez une exception SystemExit avec un état de sortie de 2. Cela indique que l'application s'est fermée après un échec.

La branche elif vérifie si l'utilisateur a fourni un répertoire cible. Si ce n'est pas le cas, vous imprimez un message d'erreur à l'utilisateur et quittez l'application, en déclenchant une exception SystemExit.

Après avoir vérifié le contenu de sys.argv, vous créez un objet pathlib.Path pour stocker le chemin d'accès à votre répertoire cible. Si ce répertoire n'existe pas, vous en informez l'utilisateur et quittez à nouveau l'application en utilisant l'exception SystemExit. Cette fois, l'état de sortie est 1 pour signaler que l'application a rencontré une erreur lors de son exécution. Enfin, la boucle for répertorie le contenu du répertoire, une entrée par ligne.

Sur un système de type Unix, tel que Linux et macOS, vous pouvez exécuter la commande suivante pour vérifier le fonctionnement du script :

$ python ls.py
You must specify the target directory
$ echo $?
2

$ python ls.py /home /etc
One argument expected, got 2
$ echo $?
2

$ python ls.py .
hello.py
birds.py
grades.py
greeting.py
fizzbuzz.py
ls.py
mean.py
stack.py
square.py
rainbow.py
$ echo $?
0

Lorsque vous exécutez le script sans argument, vous recevez un message vous indiquant que vous devez fournir un répertoire cible. Lorsque vous utilisez la commande echo pour vérifier le code de sortie, représenté par $?, vous verrez qu'il s'agit de 2. Dans le deuxième exemple, vous fournissez deux répertoires cibles. Encore une fois, l'application échoue avec un message d'erreur. La commande echo renvoie également 2.

Enfin, vous exécutez le script avec le répertoire de travail actuel comme argument. Dans ce cas, vous obtenez la liste des fichiers qui se trouvent dans ce répertoire. Lorsque vous exécutez echo, vous obtenez un statut de sortie de 0, ce qui signale que l'exécution de l'application a réussi.

Interruption du clavier

L'exception KeyboardInterupt est quelque peu différente des autres exceptions. Python déclenche cette exception lorsque l'utilisateur appuie intentionnellement sur la combinaison de touches Ctrl<span>+C lors de l'exécution d'un programme. Cette action interrompt un programme en cours d'exécution.

Par exemple, disons que vous travaillez sur un morceau de code qui implique une boucle. Par erreur, le code tombe dans une boucle infinie. Dans cette situation, vous avez besoin d’un moyen rapide pour mettre fin à l’exécution du code. C'est alors que la combinaison de touches Ctrl<span>+C s'avère utile.

Considérez la boucle de jouet suivante :

>>> while True:
...     print("Hello")
...
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Traceback (most recent call last):
    ...
KeyboardInterrupt

Cette boucle est intentionnellement écrite pour être infinie. En pratique, vous pouvez avoir de vraies boucles qui se déroulent en itérations infinies à cause d'une erreur logique. Lorsque vous rencontrez ce problème, vous pouvez appuyer sur les touches Ctrl<span>+C pour mettre fin à l'exécution du code. En conséquence, Python déclenchera une exception KeyboardInterrupt.

Enfin, il est important de noter que vous ne devez pas intercepter cette exception dans votre code car cela pourrait empêcher l’interpréteur de se fermer.

Groupes d'exceptions

Dans Python 3.11 et versions ultérieures, vous disposerez des classes ExceptionGroup et BaseExceptionGroup. Vous pouvez les utiliser lorsque vous devez déclencher plusieurs exceptions non liées en même temps. La différence entre ces classes est que BaseExceptionGroup étend BaseException, tandis que ExceptionGroup étend Exception.

Vous pouvez accéder à ces exceptions lorsqu'un programme asynchrone comporte plusieurs tâches simultanées qui pourraient échouer en même temps. Mais en général, vous déclencherez un ExceptionGroup avec parcimonie.

Vous pouvez gérer et générer des groupes d’exceptions selon vos besoins. Voici comment intercepter un groupe d’exceptions avec la syntaxe sauf* associée :

>>> try:
...     raise ExceptionGroup(
...         "several exceptions",
...         [
...             ValueError("invalid value"),
...             TypeError("invalid type"),
...             KeyError("missing key"),
...         ],
...     )
... except* ValueError:
...     print("Handling ValueError")
... except* TypeError:
...     print("Handling TypeError")
... except* KeyError:
...     print("Handling KeyError")
...
Handling ValueError
Handling TypeError
Handling KeyError

La syntaxe sauf* vous permet d'intercepter des exceptions individuelles sur un groupe d'exceptions. De cette façon, vous pouvez gérer les exceptions individuelles de manière spécifique.

Conclusion

Vous avez découvert les exceptions intégrées de Python, qui offrent un moyen rapide et efficace de gérer les erreurs et les situations exceptionnelles dans votre code. Vous connaissez désormais les exceptions intégrées les plus couramment utilisées, quand elles apparaissent et comment les utiliser dans votre code de gestion des exceptions. Vous avez également appris que vous pouvez déclencher ces exceptions dans votre code.

Dans ce didacticiel, vous avez :

  • J'ai appris ce que sont les erreurs et les exceptions en Python
  • Compris comment Python organise les exceptions intégrées dans une hiérarchie de classes
  • Explorer les exceptions intégrées les plus couramment utilisées
  • J'ai appris à gérer et déclencher des exceptions intégrées dans votre code.

Ces connaissances vous aideront à déboguer efficacement votre code car chaque exception a une signification et un cas d'utilisation spécifiques. Vous serez également en mesure de gérer et de déclencher efficacement des exceptions intégrées dans votre code, ce qui constitue une grande compétence pour un développeur Python.