Exceptions and assertions
Reading time10 minEn bref
Résumé de l’article
Dans ce cours, nous introduisons les exceptions, qui indiquent des erreurs détectées lors de l’exécution du programme.
Nous détaillons également les assertions, qui permettent de faire certaines vérifications à un moment donné du programme.
Elles sont très pratiques pour le développement, car elles permettent de déclencher des erreurs lorsqu’un problème survient.
Points clés
-
Les exceptions sont utilisées pour gérer les erreurs qui peuvent survenir lors de l’exécution du programme, telles que des entrées utilisateur invalides ou des fichiers manquants.
-
Les exceptions peuvent être interceptées et gérées par le programme.
-
Le bloc
try
-except
est utilisé pour intercepter 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 tous les cas. -
Les assertions sont faites pour valider et appliquer des 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 impose des contraintes, conduisant à un logiciel plus robuste et fiable.
-
Les assertions peuvent être activées/désactivées facilement en production.
Contenu de l’article
1 — Exceptions
1.1 — Qu’est-ce qu’une exception ?
Les exceptions sont utilisées pour gérer des erreurs qui peuvent survenir même si le code est parfaitement correct.
Voici quelques exemples de tels scénarios :
- Un utilisateur saisit des données invalides.
- Un fichier est manquant.
- Une connexion réseau est perdue.
Une exception est un événement qui survient 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, lorsqu’une exception n’est pas gérée par les programmes, cela entraîne 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
/**
* To run this code, you need to have Java installed on your computer, then:
* - Create a file named `Main.java` in a directory of your choice.
* - Copy this code in the file.
* - Open a terminal in the directory where the file is located.
* - Run the command `javac Main.java` to compile the code.
* - Run the command `java -ea Main` to execute the compiled code.
* Note: '-ea' is an option to enable assertions in Java.
*/
public class Main {
/**
* This function returns the integer division of a by b.
* Beware of division by 0!
*
* @param a The numerator.
* @param b The divisor.
* @return The integer result of dividing a by b.
*/
public static int euclideanDivision(int a, int b){
// No check for division by 0, the program will crash if b is 0
return a / b;
}
public static void main(String[] args) {
System.out.println(euclideanDivision(10, 0));
}
}
L’exécution du code précédent avec b = 0
déclenchera une ZeroDivisionError
en Python (ArithmeticException
en Java).
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
Exception in thread "main" java.lang.ArithmeticException: / by zero
at org.step1.Main.euclideanDivision(Mai.java:30)
at org.step1.Main.main(Mai.java:34)
La dernière ligne du message d’erreur indique ce qui s’est passé.
Les exceptions existent sous différents types, et le type est affiché dans le message (ZeroDivisionError
et ArithmeticException
).
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 exceptions en créant une nouvelle classe d’exception (qui doit dériver de la classe Exception
), mais nous ne décrirons pas cela dans cette leçon.
1.2 — Gestion des exceptions
1.2.1 — Le bloc try-except
Il est possible d’écrire des programmes qui gèrent certaines exceptions, c’est-à-dire qui détectent quand une exception survient, et la capturent pour faire quelque chose et empêcher le programme de planter.
Pour cela, on peut utiliser un bloc try
-except
pour intercepter 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
(l’instruction ou les instructions entre les mots-cléstry
etexcept
) est exécutée. - Voici ce qui peut se passer ensuite :
- Si aucune exception ne survient, la clause
except
est ignorée, et l’exécution de la clausetry
est terminée. - Si une exception survient pendant l’exécution de la clause
try
(par exemple, à la ligne 42), le reste de la clause (lignes 43 et suivantes) 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, puis l’exécution continue après le bloctry
-except
. - Si une exception survient 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 survient, la clause
L’exemple suivant demande à l’utilisateur une entrée jusqu’à ce qu’un entier valide soit 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 sous forme d’un tuple entre parenthèses, par exemple :
except (RuntimeError, TypeError, NameError):
pass
L’instruction pass
est utilisée comme un espace réservé pour du code futur.
Lorsque l’instruction pass
est exécutée, rien ne se passe (c’est une commande pour “ne rien faire”), mais cela évite d’obtenir une erreur lorsque du code vide n’est pas autorisé.
Le code vide n’est pas autorisé dans les boucles, définitions de fonctions, définitions de classes, ou dans les instructions if
.
Comme toutes les exceptions Python dérivent d’une classe de base appelée Exception
, vous pouvez intercepter toute erreur qui survient pendant le bloc try en écrivant :
except Exception:
pass
Ou, de manière équivalente :
except:
pass
Enfin, si vous souhaitez 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 destinée à définir des actions de nettoyage qui doivent être exécutées dans tous les cas.
Si une clause finally
est présente, la clause finally
s’exécutera en 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
s’exécute que l’instruction try
produise ou non une exception.
1.3 — Lever des exceptions
Les exceptions sont levées lorsqu’une erreur survient.
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
/**
* To run this code, you need to have Java installed on your computer, then:
* - Create a file named `Main.java` in a directory of your choice.
* - Copy this code in the file.
* - Open a terminal in the directory where the file is located.
* - Run the command `javac Main.java` to compile the code.
* - Run the command `java -ea Main` to execute the compiled code.
* Note: '-ea' is an option to enable assertions in Java.
*/
public class Main {
/**
* This function returns the integer division of a by b.
* Beware of division by 0!
*
* @param a The numerator.
* @param b The divisor.
* @return The integer result of dividing a by b.
* @throws ArithmeticException If b is 0.
*/
public static int euclideanDivision(int a, int b) throws ArithmeticException {
// Raise an exception with a custom error message
if (b == 0) {
throw new ArithmeticException("Division by zero. Please provide a non-zero divisor.");
}
// Perform a division that cannot be by 0
return a / b;
}
public static void main(String[] args) {
System.out.println(euclideanDivision(10, 0));
}
}
Dans l’exemple ci-dessus, la fonction euclidean_division
lève une ZeroDivisionError
en Python (et une ArithmeticException
en Java) 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 plus loin et définir notre propre classe étendant Exception
et la lever, au lieu de simplement personnaliser le message d’erreur.
Avec cela, nous pourrions ajouter des propriétés à l’objet exception créé et les utiliser dans le bloc except
.
2 — Assertions
Les assertions sont un outil crucial dans le développement logiciel utilisé pour valider les hypothèses faites dans le code.
Ce sont des types spécifiques d’exceptions, faites pour le développement.
Les assertions vérifient si des conditions spécifiques sont vraies pendant l’exécution, aidant à détecter tôt les bugs et erreurs logiques.
Lorsqu’une assertion échoue, elle fournit un retour immédiat, aidant à un débogage rapide.
En imposant des contraintes et des comportements attendus, les assertions contribuent à créer un 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 conforme aux attentes à un point donné du code.
Si l’assertion échoue, une exception est levée, et le programme s’arrête.
Ainsi, le mécanisme d’assertion est utilisé pour vérifier des conditions pendant l’exécution du code.
La plupart du temps, cela ne nécessite aucune dépendance supplémentaire.
En Python (comme en Java), une assertion est ajoutée en utilisant le mot réservé assert
, suivi de la condition à vérifier, et d’un message optionnel à afficher si l’assertion échoue : assert condition, message
.
Considérons la fonction euclidean_division
vue précédemment.
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
/**
* To run this code, you need to have Java installed on your computer, then:
* - Create a file named `Main.java` in a directory of your choice.
* - Copy this code in the file.
* - Open a terminal in the directory where the file is located.
* - Run the command `javac Main.java` to compile the code.
* - Run the command `java -ea Main` to execute the compiled code.
* Note: '-ea' is an option to enable assertions in Java.
*/
public class Main {
/**
* This function returns the integer division of a by b.
* Beware of division by 0!
*
* @param a The numerator.
* @param b The divisor.
* @return The integer result of dividing a by b.
*/
public static int euclideanDivision(int a, int 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 des 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 son exécution.
Cependant, les assertions sont uniquement utilisées en phase de développement et sont rendues silencieuses en production.
Ainsi, en production, la vérification que b != 0
ci-dessus n’est pas effectuée.
On suppose donc que le programme ne devrait jamais appeler la fonction avec ce cas problématique.
Cela est fait pour accélérer le code et éviter des vérifications inutiles.
Cependant, si vous pensez que l’erreur peut encore se produire en production, une exception peut être plus adaptée.
C’est particulièrement le cas lorsque vous interagissez avec un utilisateur qui pourrait définir b = 0
.
Comme les assertions peuvent être désactivées, vous devez faire attention aux points suivants :
-
Les assertions ne doivent pas avoir d’effets de bord (elles ne doivent pas modifier l’état du programme, mais seulement le vérifier) – Par exemple, vous ne devez pas utiliser une assertion pour ouvrir un fichier, mettre à jour une structure de données, etc.
-
Les assertions ne doivent 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 devez pas utiliser d’assertions pour vérifier si un fichier existe.
-
Les assertions ne remplacent pas la gestion des erreurs, la validation des entrées, les tests, la documentation, etc.
-
Lors de l’utilisation d’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, puis vérifiez la variable dans l’assertion, le calcul sera quand même effectué même si les assertions sont désactivées.
Pour de telles vérifications complexes, il peut donc être judicieux de définir des fonctions, et de les appeler dans l’assertion.
Par exemple, dans le code suivant, la fonctionis_sorted
est appelée dans l’assertion :
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
On dirait que cette section est 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 pourrons l’ajouter rapidement.
Sinon, cela nous aidera à améliorer le cours pour l’année prochaine !
Pour aller au-delà
On dirait que cette section est 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 pourrons l’ajouter rapidement.
Sinon, cela nous aidera à améliorer le cours pour l’année prochaine !