Définition
Savoir si une valeur se trouve quelque part est l'une des questions les plus banales du métier. Ce pseudo est-il déjà pris ? Cette adresse figure-t-elle sur la liste noire ? Dans beaucoup de langages, y répondre demande une boucle, un drapeau et une sortie anticipée : cinq lignes pour une question qui n'en vaut qu'une.
Python a fait le choix inverse et réserve un mot-clé à cette question. in la pose directement, et il ne renvoie qu'une réponse vraie ou fausse, jamais une position ni l'élément trouvé. C'est ce qui en fait une condition utilisable telle quelle derrière un if, sans variable intermédiaire.
langages = ["python", "javascript", "go"]
if "python" in langages:
print("Présent")Lue à voix haute, la ligne donne presque une phrase française. Cette lisibilité explique que in remplace presque toujours la boucle de recherche écrite à la main, avec ses erreurs d'indice et ses break oubliés.
Deux rôles pour un même mot
Une gêne arrive vite, dès la première boucle écrite. Le même mot-clé revient dans la boucle for, où il ne teste rien du tout : il sépare la variable de la séquence à parcourir, comme le ferait une ponctuation. Les deux emplois partagent quatre lettres, et rien d'autre.
# Test d'appartenance : la réponse vaut vrai ou faux
"go" in langages
# Boucle : chaque élément passe à son tour dans la variable
for langage in langages:
print(langage)Le repère est facile à tenir : regardez ce qui précède. Derrière un if, ou partout où une valeur vraie ou fausse est attendue, in teste une appartenance. Entre un for et le deux-points, il déclare une boucle.
La conséquence est concrète. Un test d'appartenance produit une valeur de type bool, que l'on peut stocker ou combiner avec d'autres conditions. La ligne d'un for, elle, ne produit rien du tout.
Ce qu'il regarde selon le type
Sur quoi porte exactement la recherche ? La question ne change jamais, mais l'endroit où Python va la poser dépend du type de la collection. À gauche du tableau, ce que l'on interroge ; à droite, ce que la réponse concerne vraiment.
| Collection | Ce que in teste |
|---|---|
| liste et tuple | La présence d'un élément, comparé par égalité |
| chaîne | La présence d'un morceau entier, pas seulement d'un caractère |
| dictionnaire | La présence d'une clé, jamais d'une valeur |
| ensemble | La présence d'un élément, retrouvé par son empreinte |
| range | L'appartenance à l'intervalle, calculée sans le parcourir |
La ligne du dictionnaire est celle qui surprend le plus, et elle mérite qu'on s'y arrête. "prix" in article interroge les clés, jamais les valeurs. Pour chercher de l'autre côté, il faut le dire explicitement, avec in article.values().
Ce choix rend un service précis : il permet de savoir si une clé existe avant d'y toucher, donc de distinguer une clé absente d'une clé bien présente mais valant None. Sans ce test, l'accès direct lèverait une KeyError.
Sur un range, in ne parcourt rien : Python calcule si la valeur tombe dans l'intervalle et sur le bon pas. 999_999 in range(10_000_000) répond donc instantanément, là où la liste équivalente demanderait un long moment et beaucoup de mémoire.
Le coût du test
Ce calcul plutôt que ce parcours mène au vrai sujet : deux tests in écrits exactement pareil ne coûtent pas la même chose.
Sur une liste ou un tuple, Python compare les éléments un par un, jusqu'à trouver ou jusqu'au bout. Le test ralentit donc à mesure que la collection s'allonge, et le pire cas est celui où la valeur est absente : il faut alors tout parcourir avant de conclure. Sur un ensemble ou un dictionnaire, la réponse tombe immédiatement, quelle que soit la taille.
interdits = ["a@exemple.fr", "b@exemple.fr"] # liste : reparcourue à chaque tour
interdits = {"a@exemple.fr", "b@exemple.fr"} # ensemble : réponse immédiate
for client in clients:
if client.email in interdits:
continueD'où le réflexe à prendre : dès qu'un même test revient à l'intérieur d'une boucle, convertissez la collection en ensemble une seule fois, avant d'y entrer. La ligne ne change que de ponctuation, le temps d'exécution change d'ordre de grandeur.
not in, et la confusion avec is
Reste le cas inverse : vérifier qu'une valeur est absente. Il se traite avec not in, un opérateur à part entière et non une négation appliquée après coup. email not in interdits se lit dans le sens de la lecture, et c'est la forme retenue.
Une dernière confusion mérite d'être levée, celle avec is. in cherche une valeur à l'intérieur d'une collection, is demande si deux noms désignent le même objet en mémoire. Le premier travaille sur le contenu, le second sur l'identité.
Écrire if valeur is liste à la place du test d'appartenance ne lève aucune erreur. La ligne est valide, elle répond simplement faux pour toujours, sans rien signaler. Une condition qui ne se déclenche jamais se repère beaucoup plus tard qu'un programme qui plante.
Questions fréquentes
Comment vérifier qu'une valeur est absente ?
Avec not in, écrit d'un seul bloc : if email not in interdits:. La forme not (email in interdits) fonctionne aussi, mais elle oblige à lire l'expression intérieure avant de comprendre la négation.
Pourquoi le test échoue-t-il sur un dictionnaire ?
Parce qu'un dictionnaire répond sur ses clés, et non sur ses valeurs. Pour chercher parmi les valeurs, passez par in mon_dico.values() ; pour un couple complet, par in mon_dico.items(). Ces trois écritures répondent à trois questions différentes.
Peut-on utiliser in sur ses propres objets ?
Oui, et c'est prévu pour. Une classe qui définit la méthode magique __contains__ décide elle-même de la réponse, et Python l'appelle plutôt que de parcourir quoi que ce soit. Sans cette méthode, il se rabat sur l'itérable sous-jacent, ce qui coûte un parcours complet à chaque test.