Exceptions et assertions
Temps de lecture10 minEn bref
Résumé de l’article
Dans ce cours, nous introduisons les exceptions, qui indiquent des erreurs détectées pendant l’exécution du programme.
Nous détaillons également les assertions, qui permettent de faire des vérifications à un certain point du programme. Elles sont très pratiques pour le développement, car elles permettent de lever des erreurs quand quelque chose ne va pas.
Points clés à retenir
-
Les exceptions sont des erreurs qui surviennent pendant l’exécution du programme, comme des saisies utilisateur invalides ou des fichiers manquants.
-
Les exceptions peuvent être capturées et gérées par le programme.
-
Le bloc
try
-except
est utilisé pour capturer et gérer les exceptions en Python. -
La clause
finally
est utilisée pour définir des actions de nettoyage qui doivent être exécutées dans toutes les circonstances. -
La clause
raise
permet de lever (i.e. générer une exception). -
Les assertions sont faites pour valider et appliquer les conditions attendues dans le code pendant l’exécution.
-
Les assertions échouées fournissent un retour immédiat, aidant les développeurs à identifier et corriger rapidement les problèmes.
-
L’utilisation cohérente des assertions applique des contraintes, conduisant à un logiciel plus robuste et fiable.
-
Les assertions peuvent être facilement activées/désactivées pour la production.
Contenu de l’article
1 — Exceptions
1.1 — Qu’est-ce qu’une exception ?
Un code peut être syntaxiquement et sémantiquement correct mais voir son exécution abortée brutalement à cause d’une erreur dite d’exécution. Voici quelques exemples de tels scénarios :
- Un utilisateur saisit des données invalides.
- Un fichier est manquant ou inaccessible.
- Une connexion réseau est perdue.
Les exceptions sont utilisées pour traiter ces erreurs.
Une exception est un événement qui se produit pendant l’exécution d’un programme, qui perturbe le flux normal des instructions. Mais les exceptions ne sont pas nécessairement fatales, elles peuvent être gérées par le programme.
Cependant, quand une exception n’est pas gérée par les programmes, cela résulte en un message d’erreur comme celui montré ci-dessous :
def euclidean_division (a: int, b: int) -> int:
"""
This function returns the integer division of a by b.
Beware of division by 0!
In:
* a: The numerator.
* b: The divisor.
Out:
* The integer division of a by b.
"""
# No check for division by 0, the program will crash if b is 0
return a // b
Exécuter le code précédent avec b = 0
lèvera une ZeroDivisionError
en Python.
Traceback (most recent call last):
File "functions.py", line 16, in <module>
euclidean_division(10, 0)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "functions.py", line 14, in euclidean_division
return a // b
~~^^~~
ZeroDivisionError: integer division or modulo by zero
La dernière ligne du message d’erreur indique ce qui s’est passé.
Les exceptions viennent en différents types, et le type est affiché comme partie du message (ZeroDivisionError
).
La chaîne affichée comme type d’exception est le nom de l’exception intégrée qui s’est produite.
Les programmes peuvent définir leurs propres types d’exception en créant une nouvelle classe d’exception (qui devrait être dérivée de la classe Exception
), mais nous nous focaliserons sur les types existants dans cette session.
1.2 — Gestion des exceptions
1.2.1 — Le bloc try-except
Il est possible d’écrire des programmes qui gèrent des exceptions sélectionnées, c’est-à-dire, qui détectent quand une exception se produit, et la capturent pour faire quelque chose et empêcher le programme de planter.
Pour ce faire, nous pouvons utiliser un bloc try
-except
pour capturer et gérer l’exception.
La syntaxe du bloc try
-except
est la suivante :
# The code that has a chance to raise an exception should be written within the try block
try:
<do something>
# The code to execute in case of an exception of specified type should be written in the except block
except <exception type>:
<handle the error>
L’instruction try fonctionne comme suit :
- D’abord, la clause
try
(la ou les instruction(s) entre les mots-cléstry
etexcept
) est exécutée. - Voici ce qui peut arriver ensuite :
- Si aucune exception ne se produit, la clause
except
est ignorée, et l’exécution de la clausetry
est terminée. - Si une exception se produit pendant l’exécution de la clause
try
(par exemple, à la ligne 42), le reste de la clause (lignes 43 et au-delà) est ignoré. Ensuite, si le type de l’exception levée correspond à l’exception nommée après le mot-cléexcept
(<exception type>
dans le code ci-dessus), la clauseexcept
est exécutée, et puis l’exécution continue après le bloctry
-except
. - Si une exception se produit qui ne correspond pas à l’exception nommée dans la clause except, elle est transmise aux instructions
try
externes (s’il y en a). Si aucun gestionnaire n’est trouvé, c’est une exception non gérée et l’exécution s’arrête avec un message d’erreur.
- Si aucune exception ne se produit, la clause
L’exemple suivant demande à l’utilisateur une saisie jusqu’à ce qu’un entier valide ait été saisi, mais permet à l’utilisateur d’interrompre le programme (en utilisant Control-C).
# Infinite loop
while True:
# Start the block that may raise an exception
try:
# Here, int() can raise a ValueError if the provided string cannot be converted to an integer
x = int(input("Please enter a number: "))
# If we can reach this point, it means that no exception was raised
# We abort the infinite loop
break
# In case of a ValueError, we make a print and loop again
except ValueError:
print("Oops! That was no valid number. Try again...")
Une instruction try
peut avoir plus d’une clause except
, pour spécifier des gestionnaires pour différentes exceptions.
Au plus un gestionnaire sera exécuté.
Une clause except
peut nommer plusieurs exceptions comme un tuple entre parenthèses, par exemple :
except (RuntimeError, TypeError, NameError):
pass
L’instruction pass
est utilisée comme espace réservé pour du code futur.
Quand l’instruction pass
est exécutée, rien ne se passe (c’est une commande pour “ne rien faire”), mais vous évitez d’obtenir une erreur quand du code vide n’est pas autorisé.
Le code vide n’est pas autorisé dans les boucles, les définitions de fonctions, les définitions de classes, ou dans les instructions if
.
Comme toutes les exceptions Python sont dérivées d’une classe de base appelée Exception
, vous pouvez capturer toute erreur qui se produit pendant le bloc try en écrivant :
except Exception:
pass
Ou, de manière équivalente :
except:
pass
Enfin, si vous voulez plus d’informations sur l’exception que vous avez capturée, vous pouvez utiliser la bibliothèque traceback
, et obtenir l’exception dans une variable (e
dans le code ci-dessous) :
# Needed import
import traceback
# Raise and catch an exception
try:
x = 1 / 0
except Exception as e:
# Print a few info
print(f"Type of the exception: {type(e).__name__}")
print(f"Error message of the exception: {str(e)}")
# Show complete traceback (which function was called, etc.)
print("Traceback:")
traceback.print_exc()
1.2.2 — Définir des actions de nettoyage
L’instruction try a une autre clause optionnelle qui est destinée à définir des actions de nettoyage qui doivent être exécutées dans toutes les circonstances.
Si une clause finally
est présente, la clause finally
s’exécutera comme dernière tâche avant que l’instruction try
ne se termine.
Par exemple :
try:
while True:
print("Program is running")
except KeyboardInterrupt:
print("Oh! you pressed CTRL + C.")
print("Program interrupted.")
finally:
print("This was an important code, ran at the end.")
Program is running
Program is running
...
Program is running
Program is running
Prog^Cis running
Oh! you pressed CTRL + C.
Program interrupted.
This was an important code, ran at the end.
La clause finally
est exécutée qu’une exception soit levée ou non dans la clause try
.
1.3 — Lever des exceptions
Les exceptions sont levées quand une erreur se produit.
Mais il est aussi possible pour un programmeur de forcer une exception à se produire avec le mot-clé raise
.
L’argument de raise
indique l’exception à lever.
Cela doit être soit une instance d’exception soit une classe d’exception.
Par exemple :
def euclidean_division (a: int, b: int) -> int:
"""
This function returns the integer division of a by b.
Beware of division by 0!
In:
* a: The numerator.
* b: The divisor.
Out:
* The integer division of a by b.
Exceptions:
* ZeroDivisionError: If b is 0.
"""
# Raise an exception with a custom error message
if b == 0:
raise ZeroDivisionError("Division by zero. Please provide a non-zero divisor.")
# Perform a division that cannot be by 0
return a // b
Dans l’exemple ci-dessus, la fonction euclidean_division
lève une ZeroDivisionError
en Python si le diviseur b
est 0.
Ici, le même type d’erreur est levé mais le message diffère.
Cela montre principalement comment lever une exception.
Nous pourrions aller un peu plus loin et définir notre propre classe étendant Exception
et la lever, au lieu de juste personnaliser le message d’erreur.
Avec cela, nous pouvons ajouter des propriétés à l’objet exception créé et les utiliser dans le bloc except
.
class MyDivisionError(Exception):
_message = "Division by zero is not allowed."
"""Custom exception for division errors."""
def __init__(self, message=_message):
self.message = message
super().__init__(self.message)
def euclidean_division (a: int, b: int) -> int:
"""
This function returns the integer division of a by b.
Beware of division by 0!
In:
* a: The numerator.
* b: The divisor.
Out:
* The integer division of a by b.
Exceptions:
* ZeroDivisionError: If b is 0.
"""
# Raise an exception with a custom error message
if b == 0:
raise MyDivisionError()
# Perform a division that cannot be by 0
return a // b
if __name__ == "__main__":
# Test cases
try:
print(euclidean_division(10, 2)) # Should print 5
print(euclidean_division(10, 0)) # Should raise MyDivisionError
except MyDivisionError as e:
print(e)
2 — Assertions
Les assertions sont un outil crucial en développement logiciel utilisé pour valider les hypothèses faites dans le code. Elles sont des types spécifiques d’exceptions, faites à des fins de développement. Les assertions vérifient si des conditions spécifiques sont vraies pendant l’exécution, aidant à détecter les bugs et erreurs logiques au plus tôt.
Quand une assertion échoue, elle fournit un retour immédiat, aidant au débogage rapide. En appliquant des contraintes et des comportements attendus, les assertions contribuent à créer du code plus fiable et maintenable.
2.1 — Qu’est-ce qu’une assertion ?
Les assertions nous donnent un moyen de rendre nos hypothèses explicites dans notre code. Elles sont utilisées pour vérifier que l’état du programme est comme attendu à un point donné dans le code. Si l’assertion échoue, une exception est levée, et le programme s’arrête. Donc, le mécanisme d’assertion est utilisé pour vérifier les conditions pendant l’exécution du code.
La plupart du temps, cela ne nécessite aucune dépendance supplémentaire.
En Python, une assertion est ajoutée en utilisant le mot réservé assert
, suivi par la condition à vérifier, et un message optionnel à afficher si l’assertion échoue : assert condition, message
.
Considérons la euclidean_division
vue plus tôt.
Dans ce code, nous avons une hypothèse que b != 0
.
Par conséquent, une assertion peut être plus adaptée qu’une exception :
def euclidean_division (a: int, b: int) -> int:
"""
This function returns the integer division of a by b.
Beware of division by 0!
In:
* a: The numerator.
* b: The divisor.
Out:
* The integer division of a by b.
"""
# This assertion will fail when a division by 0 is attempted
assert b != 0, "Division by zero"
# If we pass all assertions, we are fine
return a // b
Les assertions peuvent être utilisées pour enrichir le code en vérifiant les invariants de code et ainsi être vues comme une forme de documentation : elles peuvent décrire l’état que le code s’attend à trouver avant de s’exécuter.
Cependant, les assertions sont seulement utilisées dans la phase de développement et sont rendues silencieuses en production.
Par conséquent, en production, la vérification que b != 0
ci-dessus n’est pas faite.
Il est ainsi supposé que le programme ne devrait jamais appeler la fonction avec ce cas problématique.
Ceci est fait pour accélérer le code et éviter les vérifications inutiles.
Cependant, si vous sentez que l’erreur peut encore arriver pendant la production, une exception peut encore être plus adaptée.
C’est particulièrement le cas quand vous interagissez avec un utilisateur qui pourrait définir b = 0
.
Comme les assertions peuvent être désactivées, vous devriez faire attention aux points suivants :
-
Les assertions ne devraient pas avoir d’effets de bord (elles ne devraient pas modifier l’état du programme, mais seulement le vérifier) – Par exemple, vous ne devriez pas utiliser d’assertion pour ouvrir un fichier, mettre à jour une structure de données, etc.
-
Les assertions ne devraient pas être utilisées pour vérifier des conditions qui peuvent être causées par des facteurs externes (par exemple, saisie utilisateur, réseau, etc.) – Par exemple, vous ne devriez pas utiliser d’assertions pour vérifier si un fichier existe.
-
Les assertions ne sont pas un substitut à la gestion d’erreurs, la validation d’entrée, les tests, la documentation, etc.
-
Quand vous utilisez des assertions qui nécessitent un temps d’exécution assez élevé, assurez-vous que les calculs sont faits dans la condition
assert
. En effet, si vous calculez le résultat avant, le stockez dans une variable, et puis vérifiez la variable dans l’assert
, le calcul sera encore fait lors de la désactivation des assertions. Pour de telles vérifications complexes, il peut ainsi être une bonne idée de définir des fonctions, et les appeler dans l’assert
. Par exemple, dans le code suivant, la fonctionis_sorted
est appelée dans l’assert
:
def binary_search (data: list[int], search_value: int) -> int :
"""
This function searches for a value in a sorted list using the binary search algorithm.
In:
* data: The list to search in.
* search_value: The value to search for.
Out:
* The index of the value in the list if it is found, -1 otherwise.
"""
assert len(data) > 0, "The list is empty"
assert is_sorted(data), "The list is not sorted"
Pour aller plus loin
Vous pouvez consulter ces deux ressources pour distinguer les propriétés attendues d’un code en mode développement et production :
Pour aller au-delà
Il semble que cette section soit vide !
Y a-t-il quelque chose que vous auriez aimé voir ici ? Faites-le nous savoir sur le serveur Discord ! Peut-être que nous pouvons l’ajouter rapidement. Sinon, cela nous aidera à améliorer le cours pour l’année prochaine !