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 try
… sauf
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.
Remarque : Dans le code ci-dessus, vous remarquerez à quel point le message d'erreur suggère une solution possible pour corriger le code. Depuis la version 3.10, les développeurs principaux de Python ont déployé beaucoup d'efforts pour améliorer les messages d'erreur afin de les rendre plus conviviaux et utiles pour le débogage.
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 try
… sauf
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.
Remarque : Pour apprendre les bases de la gestion des exceptions en Python, consultez le didacticiel Python Exceptions : An Introduction.
En Python, vous pouvez gérer les exceptions à l'aide de l'instruction try
… sauf
, 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 try
… sauf
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.
Remarque : Pour approfondir la manière de déclencher des exceptions dans Python, consultez le didacticiel Python's raise : Effectively Raising Exceptions in Your Code.
À 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 :
- Exceptions de classe de base
- 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 moduleModuleNotFoundError
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 directementException
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.
Remarque : Les avertissements étant un type d'exception avec des cas d'utilisation spécifiques et une documentation dédiée, vous ne les aborderez pas en détail dans ce didacticiel. Vous pouvez consulter la page de contrôle des avertissements dans la documentation de Python pour une présentation complète des 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.
Remarque : Pour en savoir plus sur les erreurs de syntaxe, consultez le didacticiel Syntaxe invalide en Python : raisons courantes d'une erreur de syntaxe.
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.
Remarque : Le Guide de style pour le code Python (PEP 8) indique explicitement :
Les espaces sont la méthode d'indentation préférée. Les tabulations doivent être utilisées uniquement pour rester cohérentes avec le code déjà mis en retrait avec des tabulations. Python interdit le mélange de tabulations et d'espaces pour l'indentation. (Source)
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.
Remarque : Si vous n'utilisez pas huit espaces dans la deuxième ligne, vous obtiendrez une IndentationError
au lieu d'une TabError
.
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
.
Remarque : La meilleure façon de garantir que votre module est disponible dans le chemin de Python est de l'installer. Vous pouvez utiliser pip
et installer vos packages locaux de la même manière que vous installez les packages à partir de PyPI.
Parce que ModuleNotFoundError
est une sous-classe de ImportError
, lorsque vous utilisez explicitement ce dernier dans un bloc try
… sauf
, 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 :
- Une instruction
import module
ne parvient pas à charger un module pour une raison non couverte parModuleNotFoundError
. - Une instruction
from module import name
ne parvient pas à trouvername
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.
Remarque : Pour approfondir le fonctionnement des importations en Python, consultez le didacticiel Importation Python : techniques et astuces avancées.
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.
Remarque : Dans la plupart des situations, vous n'utiliserez pas d'index dans les boucles Python. En raison de cette pratique, le problème d'erreur d'index est moins courant dans les boucles Python que dans d'autres langages de programmation où les boucles reposent sur des indices.
Si vous ne pouvez pas contrôler la plage d'index, vous pouvez alors intercepter l'exception dans un bloc try
… sauf
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 try
… sauf
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
.
Remarque : L'exception AsyncStopIteration
a la même signification sémantique que l'exception StopIteration
mais pour les boucles asynchrones. Cette exception ne sera donc pas abordée dans ce tutoriel.
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.
Remarque : Pour en savoir plus sur la récursivité, consultez le didacticiel Récursion en Python : introduction.
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é.
Remarque : Une limite de récursivité est nécessaire car chaque appel de fonction occupe de la mémoire sur la pile d'appels de votre système, c'est donc un moyen de contrôler l'utilisation de la mémoire.
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 :
.__iter__()
est appelé pour initialiser l'itérateur. Il doit renvoyer un objet itérateur..__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__()
.
Remarque : Pour en savoir plus sur les itérateurs en Python, consultez le didacticiel Itérateurs et itérables en Python : Exécuter des itérations efficaces.
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 try
… sauf
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é.
Remarque : Vous pouvez également importer abstractmethod
depuis abc
et décorer .swim()
et .fly( )
avec @abstractmethod
. Python générera une erreur si vous instanciez une classe avec une méthode abstraite.
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.
Remarque : Pour approfondir l'instruction assert
, consultez le didacticiel assert de Python : Déboguer et tester votre code comme un pro.
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.
Remarque : Vous ne devez pas vous fier aux assertions pour vérifier les hypothèses dans le code de production, car les assertions sont désactivées lorsque votre code s'exécute en mode optimisé à l'aide du -O de Python.
ou -OO
en ligne de commande.
À 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.
Remarque : L'arrêt soudain d'un programme avec un SystemExit
ou un KeyboardInterrupt
peut provoquer des conditions indésirables indésirables dans votre système, telles que des fichiers temporaires restants et un réseau ouvert. ou des connexions à une base de données.
Vous pouvez utiliser le module atexit
de la bibliothèque standard pour gérer la façon dont votre code répond à cette pratique. Ce module vous permet d'enregistrer et de désenregistrer des fonctions de nettoyage qui s'exécuteront automatiquement à la fin normale de l'interpréteur Python.
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
.
Remarque : Pour en savoir plus sur les groupes d'exceptions, consultez le didacticiel Python 3.11 Preview : Tâches et groupes d'exceptions.
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.