La compréhension de liste en Python : construire une liste en une ligne

Construire une liste en une expression au lieu de trois lignes de boucle : la syntaxe, les deux if à ne pas confondre, et quand s'en passer.
6 min de lecture
Believemy logo

Définition

Fabriquer une liste à partir d'une autre est l'un des gestes les plus fréquents en Python, et l'un des plus bavards : il faut une liste vide, une boucle qui parcourt la source, un ajout à chaque tour. Trois lignes de plomberie pour une seule idée.

Une compréhension de liste supprime cette plomberie. Elle construit une liste en une seule expression, à partir d'un itérable existant, en énonçant ce que la liste d'arrivée contient plutôt que les gestes qui la remplissent.

PYTHON
# La boucle décrit les gestes, en trois temps
carres = []
for n in range(10):
    carres.append(n * n)

# La compréhension énonce le résultat, en une expression
carres = [n * n for n in range(10)]

# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

La seconde écriture se lit presque telle quelle : pour chaque n pris dans range(10), placer n * n dans la liste. Plus de liste à moitié remplie pendant le parcours, plus d'append qu'on peut oublier dans une branche.

Le résultat est une liste complète, construite en mémoire au moment où la ligne s'exécute. Une compréhension ne remet donc aucun calcul à plus tard, et c'est ce qui la sépare du générateur, auquel elle ressemble pourtant.


La forme complète

Une compréhension se décompose en trois morceaux, dont un seul est facultatif. Le tableau les prend dans l'ordre où vous les écrivez, qui ne change jamais.

MorceauRôleExemple
ExpressionCe qui atterrit dans la listen * n
ParcoursLa source des valeursfor n in nombres
FiltreÉcarte ce qui ne doit pas y entrerif n % 2 == 0

L'expression est le seul endroit où la valeur change de forme, et le parcours accepte n'importe quel iterable : une chaîne, les clés d'un dictionnaire, un fichier lu ligne à ligne.

Le filtre, lui, écarte des éléments : la liste obtenue devient plus courte que la source, parfois vide. Vous ne pouvez donc plus faire correspondre ses positions à celles du départ.


Les deux if à ne pas confondre

Le mot if peut apparaître à deux endroits, et il n'y fait pas le même travail. Rien dans le mot n'indique lequel des deux vous écrivez, d'où l'erreur la plus répandue sur le sujet.

PYTHON
# Le if après le parcours filtre : la liste rétrécit
pairs = [n for n in nombres if n % 2 == 0]

# Le if avant le parcours choisit : la liste garde sa longueur
etiquettes = ["pair" if n % 2 == 0 else "impair" for n in nombres]

Dans la première ligne, le if vient après le parcours : c'est un filtre. Il n'accepte aucun else, puisqu'un élément écarté n'a pas de remplaçant à proposer. Sur dix nombres dont quatre pairs, elle en compte quatre.

Dans la seconde, le if appartient à l'expression, avant le for : c'est un choix ternaire, qui doit produire une valeur et exige donc son else. Rien n'est écarté : sur dix nombres, elle en compte dix.

La position suffit à trancher : après le parcours, on trie ; avant le parcours, on transforme.

Attention

Un if sans else placé avant le for ne filtre rien : il lève une SyntaxError. La ligne [n if n % 2 == 0 for n in nombres] ne démarre même pas, et le message ne dira pas que le filtre est au mauvais endroit.


Les compréhensions voisines

Cette grammaire ne sert pas qu'aux listes : en changeant la ponctuation qui l'entoure, elle construit trois autres objets.

ÉcritureCe qu'elle produit
[x for x in source]Une liste, dans l'ordre du parcours
{x for x in source}Un set, donc sans doublons
{x: calcul(x) for x in source}Un dict, clé et valeur séparées par deux points
(x for x in source)Un générateur, qui calcule au fil de la lecture

La dernière ligne n'est pas une variante décorative. Sur un journal de plusieurs millions de lignes, la version entre crochets charge tout en mémoire avant de rendre la main, quand la version entre parenthèses livre les valeurs une par une. Passer de sum([...]) à sum(...) donne le même total sans la mémoire.

Bon à savoir

Quand le générateur est le seul argument d'une fonction, ses parenthèses se confondent avec celles de l'appel : sum(n * n for n in nombres) suffit, inutile de les doubler.


Quand la boucle reste préférable

Une compréhension tire sa valeur de ce qu'elle se lit d'un seul coup d'oeil. Dès qu'elle empile deux parcours et un filtre, cette qualité disparaît, et avec elle la seule bonne raison de l'écrire.

Le second garde-fou concerne les effets de bord. Une compréhension est faite pour produire une valeur, jamais pour agir.

PYTHON
# À ne jamais écrire : la liste construite ne sert à rien
[enregistrer(ligne) for ligne in lignes]

# La boucle dit exactement ce qu'elle fait
for ligne in lignes:
    enregistrer(ligne)

La première version fabrique une liste de None aussi longue que le fichier, puisque enregistrer ne renvoie rien, puis la jette. Elle coûte de la mémoire pour rien et ment sur l'intention.

Le même raisonnement départage la compréhension et map, qui réclame une fonction à appeler, souvent anonyme et écrite exprès. La compréhension, elle, montre la transformation en clair.


Questions fréquentes

Question

Une compréhension est-elle plus rapide qu'une boucle ?

Un peu, oui. La construction est confiée à une instruction dédiée de l'interpréteur, sans appel d'append à chaque tour, soit couramment un tiers de temps en moins. C'est trop peu pour trancher : la lisibilité passe avant.

Question

Peut-on imbriquer plusieurs parcours dans la même compréhension ?

Oui, et ils se lisent de gauche à droite, dans l'ordre où on les écrirait en boucles imbriquées : le premier for est la boucle extérieure. Au-delà de deux niveaux, la boucle classique redevient plus honnête.

Question

Pourquoi la variable de parcours n'existe-t-elle plus après la ligne ?

Parce qu'une compréhension possède sa propre portée depuis Python 3 : la variable naît et meurt dans les crochets, et un n défini plus haut ne sera jamais écrasé. Une boucle for ordinaire, elle, laisse la sienne accessible après son bloc, une des différences travaillées dans la formation Python.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.