Définition
En écrivant du code, on tient sans cesse des choses pour acquises : cette liste n'est jamais vide ici, ce compteur ne descend pas sous zéro. Rien de cela n'est écrit noir sur blanc, et c'est ce qui coûte cher. Le jour où l'une de ces suppositions devient fausse, le programme ne s'arrête pas là où elle a été trahie : il casse trois fonctions plus loin, sur un message qui ne désigne plus le coupable.
assert répond à cet ennui. Cette instruction vérifie qu'une condition est vraie à un point précis du programme. Si elle l'est, rien ne se passe. Sinon, Python lève une AssertionError et tout s'arrête, à l'endroit exact où la supposition a cessé d'être vraie.
Voici la forme la plus courante : une hypothèse posée en tête de fonction.
def moyenne(notes):
# Une moyenne n'a aucun sens sur une liste vide.
assert len(notes) > 0, "la liste de notes est vide"
return sum(notes) / len(notes)
moyenne([])
# AssertionError: la liste de notes est videDeux parties composent la forme complète : la condition à vérifier, puis un message facultatif placé après une virgule. Ce message a des airs d'ornement, il n'en est pas un : sans lui, le traceback affiche la ligne fautive et rien de plus, à charge pour le lecteur de deviner ce qui était attendu.
Ce n'est pas un contrôle de saisie
Le malentendu le plus coûteux consiste à valider des données extérieures avec une assertion. La tentation se comprend : assert age >= 18 tient sur une ligne, là où la version correcte en demande trois.
L'interdit tient au sort réservé à ce mot-clé en mode optimisé, quand l'interpréteur est lancé avec l'option -O. Les lignes concernées ne sont pas ignorées à l'exécution : elles sont retirées à la compilation, comme si personne ne les avait jamais écrites.
Un contrôle de droits écrit sous forme d'assertion laisse passer en production exactement ce qu'il était censé bloquer. Aucune erreur, aucune trace dans les logs : le code paraît protégé, il ne l'est plus.
Une seule question tranche : d'où vient la donnée vérifiée ? Le tableau met chaque situation courante en face de l'écriture qui convient.
| Situation | Écriture juste |
|---|---|
| Une saisie utilisateur douteuse | Un if puis un raise de ValueError |
| Un appel réseau qui peut échouer | Un bloc try autour de l'appel |
| Un droit d'accès à contrôler | Une exception explicite, jamais une assertion |
| Une hypothèse interne au code | Une assertion, à sa juste place |
Si la condition peut devenir fausse à cause du monde extérieur, ce n'est donc pas une assertion. Une donnée venue d'un formulaire, d'un fichier ou d'une API en fait partie, quelle que soit la confiance placée dans sa source.
Le piège des parenthèses
Ce mot-clé n'est pas une fonction, et les parenthèses ne l'entourent donc pas. L'habitude prise avec print() pousse pourtant à en ajouter, et Python accepte la ligne sans broncher.
Écrire assert (condition, "message") revient à lui passer un argument unique : un tuple de deux éléments. Or un tuple non vide compte toujours comme vrai. L'assertion réussit alors systématiquement, quelle que soit la condition réelle.
# Silencieux : le tuple est toujours vrai,
# la condition n'est jamais évaluée.
assert (total > 0, "total négatif")
# Correct : aucune parenthèse autour de l'ensemble.
assert total > 0, "total négatif"Les versions récentes de Python signalent cette écriture par un avertissement, encore faut-il le lire. C'est le genre de faute qui laisse croire qu'une batterie de vérifications tourne, alors qu'aucune d'entre elles ne mord.
Où il est à sa place
Les tests automatisés sont son terrain naturel. Une fonction de test se résume souvent à trois gestes : préparer des données, appeler le code, poser une assertion sur le résultat. Les bibliothèques de test les plus répandues en Python reposent entièrement sur ce mot-clé.
pytest réécrit les assertions au chargement des fichiers de test. C'est ce qui explique qu'un échec affiche les deux valeurs comparées au lieu d'une AssertionError nue.
Hors des tests, le deuxième usage est l'invariant interne, cette hypothèse qu'un développeur pose sans jamais l'écrire : cette liste est déjà triée, ce compteur reste positif. Une assertion la transforme en vérification pour le prix d'une ligne, lisible par qui reprendra le code dans six mois.
Le troisième usage est la documentation qui s'exécute. Une assertion en tête de fonction annonce ce qu'elle attend, comme une docstring, à une différence près : une docstring peut s'éloigner du code sans que personne s'en aperçoive, l'assertion proteste dès qu'on la contredit.
Questions fréquentes
Faut-il rattraper une AssertionError ?
Presque jamais, et la raison tient à ce qu'elle signale. Une assertion qui échoue ne décrit pas une situation prévue : elle annonce un défaut dans le code lui-même. La rattraper revient à masquer un bug au lieu de le corriger. Si le cas était prévisible, c'est une exception adaptée qu'il fallait lever.
Comment désactiver les assertions en production ?
En lançant l'interpréteur avec l'option -O, qui retire toutes les assertions du code compilé et met la variable __debug__ à faux. Le gain de vitesse reste minuscule, mais l'existence même de cette option fixe la règle : leur disparition ne doit rien changer au comportement du programme.
Quelle différence avec un test conditionnel suivi d'une levée d'exception ?
L'intention, avant tout. Un if suivi d'un raise traite un cas que le programme prévoit et sait gérer : une saisie vide, un fichier absent. Une assertion déclare une hypothèse qui ne doit jamais être fausse. Le premier reste actif en production, la seconde s'adresse au développement et peut disparaître sans conséquence.