Une fois que les compréhensions de listes font leur effet, vous n'écrirez plus jamais de boucle for qui ajoute à une liste.
Ce n'est pas juste du sucre syntaxique — elles signalent clairement l'intention et s'exécutent plus vite que la boucle équivalente dans CPython
grâce à l'optimisation du bytecode. Selon le
tutoriel officiel Python,
les compréhensions offrent un moyen concis de créer des listes à partir de séquences ou d'autres itérables existants.
Cet article construit d'abord le modèle mental, puis couvre tous les vrais patrons : filtrage, imbrication,
compréhensions de dicts et d'ensembles, et le seul cas où vous devriez utiliser une boucle for ordinaire à la place.
Le patron de base
L'anatomie d'une compréhension de liste est [expression for item in iterable].
Trois parties : l'expression de sortie (ce que devient chaque élément), la variable de boucle,
et l'itérable à parcourir. Commencez par une boucle familière et réduisez-la.
# Plain for loop — building a list of file sizes in KB
file_sizes_bytes = [1024, 204800, 51200, 3145728, 8192]
sizes_kb = []
for size in file_sizes_bytes:
sizes_kb.append(size / 1024)
# print(sizes_kb) → [1.0, 200.0, 50.0, 3072.0, 8.0]
# Same result as a list comprehension — one line, same meaning
sizes_kb = [size / 1024 for size in file_sizes_bytes]La compréhension se lit presque comme de l'anglais : « donnez-moi size / 1024 pour chaque size
dans file_sizes_bytes ». Cette clarté est le vrai avantage — un lecteur n'a pas à tracer un appel
append pour comprendre ce que vous construisez.
# Another common pattern: deriving one list from another
usernames = ["alice", "bob", "carol"]
# Build a list of display names
display_names = [name.capitalize() for name in usernames]
# ['Alice', 'Bob', 'Carol']
# Or extract a single field from a list of dicts
users = [
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "viewer"},
{"id": 3, "name": "Carol", "role": "editor"},
]
names = [user["name"] for user in users]
# ['Alice', 'Bob', 'Carol']Filtrage avec if
Ajoutez une condition à la fin et seuls les éléments qui passent sont inclus dans la liste de sortie :
[expression for item in iterable if condition].
C'est le patron qui remplace une combinaison for + if + append.
# Loop version — extract only active users
active_users = []
for user in users:
if user["active"]:
active_users.append(user["name"])
# Comprehension version — identical result
users = [
{"name": "Alice", "active": True},
{"name": "Bob", "active": False},
{"name": "Carol", "active": True},
{"name": "Dave", "active": False},
]
active_users = [user["name"] for user in users if user["active"]]
# ['Alice', 'Carol']# Filtering a raw CSV row — drop blanks and whitespace-only values
raw_row = ["[email protected]", "", " ", "editor", " "]
clean_row = [field.strip() for field in raw_row if field.strip()]
# ['[email protected]', 'editor']
# Filtering a list of log levels
log_lines = [
"INFO server started",
"DEBUG loading config",
"ERROR database timeout",
"DEBUG query took 450ms",
"ERROR disk space low",
]
errors = [line for line in log_lines if line.startswith("ERROR")]
# ['ERROR database timeout', 'ERROR disk space low']Travailler avec les chaînes
Le traitement de chaînes est l'endroit où les compréhensions font leurs preuves. La combinaison des riches méthodes de chaînes de Python et de la syntaxe de compréhension maintient les pipelines de transformation lisibles sans variables intermédiaires.
# Strip whitespace from tags coming out of a form field
raw_tags = [" python ", "data science", " machine-learning ", "API"]
tags = [tag.strip().lower() for tag in raw_tags]
# ['python', 'data science', 'machine-learning', 'api']
# Normalise email addresses from a signup CSV
raw_emails = ["[email protected]", " [email protected] ", "[email protected]"]
emails = [e.strip().lower() for e in raw_emails]
# ['[email protected]', '[email protected]', '[email protected]']
# Extract file extensions from a list of uploaded filenames
filenames = ["report.pdf", "avatar.PNG", "data.CSV", "archive.tar.gz", "notes.txt"]
extensions = [name.rsplit(".", 1)[-1].lower() for name in filenames if "." in name]
# ['pdf', 'png', 'csv', 'gz', 'txt']" [email protected] " passe une vérification .lower() sur le contenu mais porte encore
des espaces de début qui casseront une recherche en base de données ou une correspondance de clé JSON.Compréhensions de dicts et d'ensembles
La même idée s'étend aux dicts et aux ensembles. Une
compréhension de dict
utilise des accolades avec une paire key: value : {key: value for item in iterable}.
Une compréhension d'ensemble supprime les deux-points et produit une collection dédupliquée :
{expression for item in iterable}.
# Invert a dict — swap keys and values
permissions = {"alice": "admin", "bob": "viewer", "carol": "editor"}
by_role = {role: name for name, role in permissions.items()}
# {'admin': 'alice', 'viewer': 'bob', 'editor': 'carol'}
# Build a fast lookup dict from a list of user records
users = [
{"id": 101, "name": "Alice", "active": True},
{"id": 102, "name": "Bob", "active": False},
{"id": 103, "name": "Carol", "active": True},
]
# O(1) lookups by ID — much faster than scanning the list every time
user_by_id = {user["id"]: user for user in users}
# {101: {...}, 102: {...}, 103: {...}}
# Access a user directly
user_by_id[102]["name"] # 'Bob'# Set comprehension — deduplicate a list of file extensions
uploads = ["report.pdf", "data.csv", "summary.pdf", "export.CSV", "notes.txt"]
unique_extensions = {name.rsplit(".", 1)[-1].lower() for name in uploads if "." in name}
# {'pdf', 'csv', 'txt'} — order not guaranteedUne mise en garde avec les ensembles : l'ordre n'est pas préservé. Si vous devez dédupliquer une liste tout en
conservant l'ordre original, une compréhension d'ensemble est le mauvais outil — utilisez
list(dict.fromkeys(items)) à la place, qui exploite le comportement d'insertion ordonnée des
dicts depuis Python 3.7+.
Compréhensions imbriquées
Vous pouvez imbriquer des compréhensions pour itérer sur des structures imbriquées. Le cas d'utilisation le plus courant est l'aplatissement d'une liste de listes — une matrice d'un analyseur CSV, des pages de réponse API fragmentées, ou des résultats de requête groupés.
# Flatten a 2D list (e.g. paginated API results)
pages = [
[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}],
[{"id": 3, "name": "Carol"}],
[{"id": 4, "name": "Dave"}, {"id": 5, "name": "Eve"}],
]
all_users = [user for page in pages for user in page]
# [{'id': 1, ...}, {'id': 2, ...}, {'id': 3, ...}, {'id': 4, ...}, {'id': 5, ...}]
# The order mirrors what nested for loops would produce:
# for page in pages:
# for user in page:
# all_users.append(user)Lisez les compréhensions imbriquées de gauche à droite — la boucle externe vient en premier, la boucle interne en second.
Cela correspond à l'ordre des boucles for imbriquées.
# Two levels — fine and readable
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [cell for row in matrix for cell in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Three levels — stop here. Use a plain loop or a helper function.
# This is the point where readability dies:
cube = [[[1,2],[3,4]],[[5,6],[7,8]]]
# Don't do this:
flat3 = [val for layer in cube for row in layer for val in row]
# Do this instead:
flat3 = []
for layer in cube:
for row in layer:
flat3.extend(row)(user["id"] for user in users). Elles s'évaluent paresseusement — les éléments sont produits un par un
plutôt que de construire la liste complète en mémoire. Utilisez-les quand vous n'avez besoin d'itérer qu'une fois, ou quand vous
passez le résultat directement à sum(), max(), any() ou des fonctions similaires.
Voir la référence des expressions génératrices pour tous les détails.Le ternaire if/else dans une compréhension
Quand vous devez transformer des éléments en l'une de deux valeurs — plutôt que de les supprimer — utilisez une expression ternaire en position de sortie. La position compte : le ternaire appartient au début, pas après l'itérable.
# Correct: ternary is the output expression
users = [
{"name": "Alice", "active": True},
{"name": "Bob", "active": False},
{"name": "Carol", "active": True},
]
status = ["active" if user["active"] else "inactive" for user in users]
# ['active', 'inactive', 'active']
# Normalise a config value — replace None with a default
raw_config = ["/var/log", None, "/tmp", None, "/etc/app"]
paths = [path if path is not None else "/var/log/default" for path in raw_config]
# ['/var/log', '/var/log/default', '/tmp', '/var/log/default', '/etc/app']# Common mistake — putting the ternary after the iterable (SyntaxError)
# status = ["active" for user in users if user["active"] else "inactive"] # WRONG
# The trailing "if" is a filter — it drops non-matching items entirely.
# The ternary ("if ... else ...") in the output expression transforms all items.
# They serve different purposes. You can combine them:
# Keep only users, but show their status
status = ["active" if user["active"] else "inactive"
for user in users
if user["name"] != "Bob"]
# ['active', 'active'] — Bob was filtered out entirelyQuand utiliser une boucle for à la place
Les compréhensions de listes servent à construire des listes. Si ce que vous faites vraiment c'est exécuter
des effets secondaires, utilisez une boucle for — pas une compréhension. Cela va au-delà du style : une compréhension
qui rejette son résultat gaspille de la mémoire en construisant une liste que personne n'utilise, et cache l'intention au lecteur.
# Bad — comprehension for side effects (writing to a file, printing, calling an API)
[print(f"Processing user {user['name']}") for user in users] # don't do this
[requests.post("/api/notify", json=user) for user in users] # definitely don't do this
# Good — plain for loop makes the intent obvious
for user in users:
print(f"Processing user {user['name']}")
for user in users:
requests.post("/api/notify", json=user)# Bad — comprehension with logic complex enough to need comments or multiple steps
result = [
user["name"].strip().lower()
if user.get("active") and user.get("email_verified")
else user["name"].strip().lower() + " (unverified)"
for user in users
if user.get("role") in ("admin", "editor") and user.get("last_login") is not None
]
# Good — break it out when the logic is this involved
result = []
for user in users:
if user.get("role") not in ("admin", "editor"):
continue
if user.get("last_login") is None:
continue
name = user["name"].strip().lower()
if not (user.get("active") and user.get("email_verified")):
name += " (unverified)"
result.append(name)- Utilisez une compréhension quand vous construisez une nouvelle liste à partir d'un itérable existant avec une expression claire et un filtre optionnel.
- Utilisez une boucle for quand vous exécutez des effets secondaires — E/S, appels réseau, impression, mutation d'état externe.
- Utilisez une boucle for quand la logique de transformation nécessite plusieurs lignes, des variables intermédiaires, ou des commentaires pour être comprise.
- Utilisez une expression génératrice quand vous n'avez besoin d'itérer qu'une fois ou de passer directement à
sum(),any(),max()— pas de liste nécessaire en mémoire.
Note sur les performances
Les compréhensions de listes sont significativement plus rapides qu'une boucle for + append équivalente
dans CPython. La raison est le bytecode : une compréhension compile vers un opcode LIST_APPEND dédié
qui évite la recherche d'attribut sur list.append à chaque itération.
Le wiki des conseils de performance Python
couvre cela, et l'écart est généralement de 10 à 40% pour les charges de travail Python pures selon la taille de la liste.
import timeit
data = list(range(100_000))
# for + append
def with_loop():
result = []
for x in data:
result.append(x * 2)
return result
# list comprehension
def with_comprehension():
return [x * 2 for x in data]
# generator expression — no list built at all
def with_generator():
return sum(x * 2 for x in data)
# Typical results on CPython 3.12:
# with_loop(): ~7.2 ms
# with_comprehension(): ~4.8 ms (~33% faster)
# with_generator(): ~4.1 ms (and uses O(1) memory vs O(n))Si vous n'avez pas besoin d'une liste matérialisée — vous passez le résultat à sum(),
any(), max(), ou l'itérez une fois — utilisez une
expression génératrice
à la place. Elle utilise une mémoire constante quelle que soit la taille d'entrée, ce qui compte lors du traitement de grandes exportations CSV
ou de charges utiles JSON dans une boucle serrée.
Conclusion
Les compréhensions de listes sont l'une de ces fonctionnalités Python qui semblent maladroites pendant environ une semaine et deviennent ensuite impossibles à s'en passer. Le modèle mental est simple : expression de sortie, variable de boucle, itérable, filtre optionnel. Respectez cela et vous écrirez du Python idiomatique et lisible. Quand la logique devient complexe, c'est le signal pour revenir à une boucle ordinaire — pas un échec, juste le bon outil pour le travail.
Si vous travaillez avec des données JSON en Python — transformer des réponses API en listes de valeurs, extraire des champs d'enregistrements, construire des dicts de recherche — les outils sur ce site s'associent bien à ce que vous venez d'apprendre. Essayez le Formateur JSON pour inspecter et mettre en forme joliment les charges utiles JSON avant d'écrire la compréhension qui les traite, ou le Formateur CSV pour valider les données CSV avant de les analyser en une liste de lignes. Pour la référence complète du langage, la documentation Python sur les compréhensions de listes et PEP 202 (la proposition originale) valent la peine d'être lus.