L’instruction d’assertion en Python : assert
- 2021-09-14
- Publié par : Christophe DELEUZE
- Catégorie : Python
Les assertions sont un outil très pratique qui aide à détecter automatiquement les erreurs dans vos programmes Python afin de les rendre plus fiables et plus faciles à déboguer.
Que sont les assertions et à quoi servent-elles ?
L’instruction assert de Python est une aide au débogage qui teste une condition. Si la condition est vraie, cela ne fait rien et votre programme continue simplement à s’exécuter. Mais si la condition d’assertion est fausse, elle lève une exception AssertionError avec un message d’erreur facultatif.
L’utilisation appropriée des assertions est d’informer les développeurs d’erreurs irrécupérables dans un programme. Elles ne sont pas destinées à signaler les conditions d’erreur attendues telles qu’un « fichier introuvable », où un utilisateur peut prendre des mesures correctives pour pallier le problème avant de réessayer d’appeler le programme.
Une autre façon de voir les choses est de dire que les assertions sont des autocontrôles internes pour votre programme. Elles fonctionnent en déclarant certaines conditions comme impossibles dans votre code. Si l’une de ces conditions n’est pas remplie, cela signifie qu’il y a un bogue (bug) dans le programme.
Si votre programme est exempt de bogues, ces conditions ne se produiront jamais. Mais si l’un d’entre eux se produit, alors le programme plantera en vous révélant quelle assertion a généré une erreur, ce qui vous permettra de comprendre exactement quelle condition « impossible » a été déclenchée. Cela rend beaucoup plus facile la recherche et la correction des bogues (bugs) dans vos programmes.
Pour résumer : l’instruction assert de Python est une aide au débogage (debug). Elle n’est pas un mécanisme de gestion des erreurs d’exécution. L’objectif de l’utilisation des assertions est de permettre aux développeurs de trouver plus rapidement la cause probable d’un bogue. Une erreur d’assertion ne devrait jamais être déclenchée à moins qu’il n’y ait un bogue dans votre programme.
Exemple d'Assertion en Python
Commençons par un exemple simple afin que vous puissiez voir où les assertions peuvent être utiles. J’ai essayé de donner à mon exemple un semblant de problème qui pourrait nous arriver dans le monde réel et que vous pourriez rencontrer dans l’un de vos programmes.
Supposons que vous construisiez une boutique en ligne avec Python. Vous travaillez à ajouter une fonctionnalité de coupon de réduction au système et éventuellement à écrire la fonction appliquer_reduction(prix, reduction) suivante :
def appliquer_reduction(prix, reduction):
prix_reduit = prix - ( prix * reduction )
assert 0 <= prix_reduit <= prix
return prix_reduit
Avez-vous remarqué la déclaration assert dans la fonction ? Sa présence garantira que, quoi qu’il en soit, les prix réduits ne peuvent pas être inférieurs à 0 et ils ne peuvent pas être supérieurs au prix d’origine du produit.
Assurons-nous que cela fonctionne réellement comme prévu si nous appelons cette fonction pour appliquer une remise valide :
>>> # Appliquer une réduction de 25% sur un prix de 100 € -> 75 €
>>> appliquer_reduction(100.00, 0.25)
75.0
D’accord, cela a bien fonctionné. Essayons maintenant d’appliquer des remises non valides :
# Appliquer une remise de 200 %
>>> appliquer_reduction(100.00, 2.0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in appliquer_reduction
AssertionError
# Appliquer une remise de -30 %
>>> appliquer_reduction(100.00, -0.30)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in appliquer_reduction
AssertionError
Comme vous pouvez le voir, essayer d’appliquer une remise non valide lève une exception AssertionError qui signale la ligne avec la condition d’assertion violée. Si jamais nous rencontrons l’une de ces erreurs lors du test de notre boutique en ligne, il sera facile de découvrir ce qui s’est passé en examinant les logs.
C’est le pouvoir des assertions, en un mot.
La syntaxe d'assert
C’est toujours une bonne idée d’étudier comment une fonctionnalité de langage est réellement implémentée dans Python avant de commencer à l’utiliser. Jetons donc un coup d’œil rapide à la syntaxe de l’instruction assert selon la documentation Python :
assert_stmt ::= "assert" expression1 ["," expression2]
expression1 correspond à la condition que nous testons, et l’optionnel expression2 est un message d’erreur qui ne s’affichera que si l’assertion échoue.
Au moment de l’exécution, l’interpréteur Python transforme chaque instruction assert en ce qui suit :
if __debug__:
if not expression1:
raise AssertionError(expression2)
Vous pouvez utiliser expression2 pour transmettre un message d’erreur facultatif qui sera affiché avec l’erreur AssertionError dans le traceback. Cela peut simplifier encore plus le débogage. Par exemple, j’ai déjà vu du code comme celui-ci :
if condition == 'x':
do_x()
elif condition == 'y':
do_y()
else:
assert False, ("Cela ne devrait jamais arriver, mais cela arrive de temps en temps."
"Nous essayons actuellement de comprendre pourquoi."
"Envoyez un e-mail à pythoniste.fr si vous rencontrez ce message.")
Est-ce que c’est moche ? Carrément ! Mais c’est certainement une technique valable et utile si vous êtes confronté à un problème de type heisenbug (bogue difficile à localiser parce qu’il ne se manifeste pas lorsque des outils de détection sont utilisés pour le rechercher) dans l’une de vos applications.
Pièges courants avec l'utilisation d'assertions en Python
Avant de continuer, il y a deux mises en garde importantes que j’aimerais vous communiquer concernant l’utilisation d’assertions en Python.
La première concerne l’introduction de risques de sécurité et de bogues dans vos applications, et la seconde concerne une bizarrerie de syntaxe qui facilite l’écriture d’assertions inutiles.
Mise en garde n°1 – N'utilisez pas d'assertions pour la validation des données
Les assertions peuvent être désactivées globalement dans l’interpréteur Python. Ne comptez donc pas sur l’exécution des expressions d’assertion pour la validation ou le traitement des données.
Les assertions peuvent être globalement désactivées en ligne de commande et aussi via une variable d’environnement dans CPython.
Voici un exemple en utilisant le flag -O (Optimize) qui désactive toutes les assertions :
python -Oc "assert False"
Sans le flag -O cela donne :
python -c "assert False"
Traceback (most recent call last):
File "<string>", line 1, in <module>
AssertionError
La désactivation des assertions transforme toute instruction d’assertion en une opération nulle : les assertions sont simplement compilées et ne seront pas évaluées, ce qui signifie qu’aucune des expressions conditionnelles ne sera exécutée.
Il s’agit d’une décision de conception intentionnelle utilisée de la même manière par de nombreux autres langages de programmation. Comme effet secondaire, il devient extrêmement dangereux d’utiliser des instructions assert comme moyen rapide et facile de valider des données d’entrée.
Si votre programme utilise des assertions pour vérifier si un argument de fonction contient une valeur inattendue, cela peut rapidement se retourner contre vous et entraîner des bogues ou des failles de sécurité.
Jetons un coup d’œil à un exemple simple en reprenant notre application de boutique en ligne avec Python. Quelque part dans votre code d’application, il existe une fonction pour supprimer un produit à la demande d’un utilisateur :
def supprimer_produit(produit_id, utilisateur):
assert utilisateur.is_admin(), 'Doit avoir des privilèges d'administrateur pour supprimer'
assert boutique.produit_exist(produit_id), 'Id produit inconnu'
boutique.trouver_produit(produit_id).delete()
Regardez attentivement cette fonction. Que se passe-t-il si les assertions sont désactivées ?
Il y aura deux problèmes sérieux dans cet exemple de fonction à trois lignes, causés par l’utilisation incorrecte des instructions assert :
- La vérification des privilèges d’administrateur avec une instruction
assertest dangereuse. Si les assertions sont désactivées dans l’interpréteur Python, cela se transforme en une opération nulle. Par conséquent, tout utilisateur peut désormais supprimer des produits. La vérification des privilèges ne s’exécute même pas. Cela introduit probablement un problème de sécurité et ouvre la porte aux attaquants pour détruire ou endommager gravement les données de la boutique en ligne de votre client ou de votre entreprise. Pas bon ; - La vérification
produit_exist()est ignorée lorsque les assertions sont désactivées. À partir de là,trouver_produit()peut dorénavant être appelé avec des identifiants de produit invalides, ce qui pourrait entraîner des bogues plus graves selon la façon dont notre programme est écrit. Dans le pire des cas, cela pourrait être un moyen pour quelqu’un de lancer des attaques par déni de service contre notre magasin. Par exemple, si l’application du magasin se bloque lorsque nous tentons de supprimer un produit inconnu, il est possible qu’un attaquant le bombarde de demandes de suppression non valides et provoque une panne.
Comment pouvons-nous éviter ces problèmes ? La réponse est de ne pas utiliser d’assertions pour valider les données. Au lieu de cela, nous pourrions effectuer notre validation avec des instructions if régulières et lever des exceptions de validation si nécessaire comme dans l’exemple ci-dessous :
def supprimer_produit(produit_id, utilisateur):
if not utilisateur.is_admin():
raise AuthError('Doit avoir des privilèges d'administrateur pour supprimer')
if not boutique.produit_exist(produit_id):
raise ValueError('Id produit inconnu')
boutique.trouver_produit(produit_id).delete()
Cet exemple mis à jour présente également l’avantage qu’au lieu de lever des exceptions AssertionError non spécifiques, il soulève désormais des exceptions sémantiquement correctes comme ValueError ou AuthError (que nous devrons définir nous-mêmes).
Mise en garde n° 2 – Des assertions qui n'échouent jamais
Il est facile d’écrire accidentellement des déclarations d’assertion Python qui sont toujours évaluées à True.
Par exemple, lorsque vous passez un tuple comme premier argument dans une instruction assert, l’assertion est toujours évaluée comme vraie et n’échoue donc jamais. Ainsi, cette assertion n’échouera jamais :
assert(1 == 2, 'Fail !')
Il est facile d’écrire accidentellement des déclarations d’assertion Python qui sont toujours évaluées à True.
Par exemple, lorsque vous passez un tuple comme premier argument dans une instruction assert, l’assertion est toujours évaluée comme vraie et n’échoue donc jamais. Ainsi, cette assertion n’échouera jamais :
assert (
counter == 10,
'Tous les éléments ont été comptés.'
)
Lors de la première inspection, ce cas de test semble tout à fait correct. Cependant, il ne détectera jamais un résultat incorrect, car il sera toujours évalué à True, quel que soit l’état de la variable de counter.
Donc soyez vigilant avec l’utilisation de l’instruction assert parce que c’est assez facile de se tirer une balle dans le pied avec ça. Heureusement, il existe des contre-mesures que vous pouvez appliquer pour éviter que cette bizarrerie de syntaxe ne cause des problèmes, vérifier que vous avez bien activé les messages de Warning ! Si oui, vous obtiendrez cette information explicite :
>>> assert(1 == 2, 'Fail !')
:1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
Le mot de la fin
Malgré ces mises en garde, je pense que les assertions de Python sont un outil de débogage puissant qui est souvent sous-utilisé par les développeurs Python.
Comprendre comment fonctionnent les assertions et quand les appliquer peut vous aider à écrire des programmes Python plus faciles à gérer et à déboguer. C’est une grande compétence à apprendre qui aidera à amener votre Python au niveau supérieur et fera de vous un bon Pythoniste.
Évidemment, pour aller plus loin avec l’instruction assert, la prochaine étape serait d’étudier l’incontournable pytest qui est un indispensable dans l’univers des tests. D’ailleurs, cette librairie fera l’objet d’un article dédié.
D’ici là, je vous remercie de m’avoir lu et n’hésitez pas à partager, commenter et me poser vos questions.
Bonjour,
Merci d’avoir partagé cet article
Bonjour,
Je vous remercie d’avoir partager cet article , vous êtes d’une grande utilité !!!
Bonne semaine à vous .
Merci pour votre post je suis au senegale
article très utile et vraiment génial comme moi
Je pense que l’induction d’assertion dans un code conduis régulièrement a l’affection d’erreur dans un code. Je suggère donc très fortement leurs utilisations.
merci tu gères la fougère.
La page est sympa. À ce détail près ????
“pour palier au problème puis simplement réessayer d’appeler le programme.” => “pour palLier LE problème (…)”
(le verbe prend 2 ‘l’ et est transitif)
Merci de votre retour 🙂 C’est corrigé. Bonne année à vous.
Dommage que la source ne soit pas citée. Livre Python tricks the book par Dan Bader.