Python enumerate, zip, map et filter
Apprenez à utiliser enumerate, zip, map et filter en Python, avec des exemples clairs et des modèles pratiques pour un code d'itération plus lisible.
Python fournit quatre fonctions intégrées — enumerate, zip, map et filter — qui remplacent les boucles classiques par une expression unique et lisible. Chacune retourne un itérateur (paresseux, économe en mémoire) et se compose naturellement avec les boucles for et les compréhensions de listes.
Ce chapitre couvre :
enumerate()— boucler avec un index automatiquezip()— itérer sur deux séquences ou plus en parallèlemap()— appliquer une fonction à chaque élémentfilter()— ne conserver que les éléments qui satisfont un test- Combiner ces quatre fonctions dans des cas réels
- Quand préférer une compréhension de liste à la place
enumerate() — index pendant la boucle
Lorsque vous avez besoin à la fois de la position et de la valeur dans une boucle, l'approche naïve utilise un compteur manuel :
colors = ["red", "green", "blue"]
i = 0
for color in colors:
print(i, color)
i += 1enumerate() fait cela automatiquement. Elle enveloppe n'importe quel itérable et produit des paires (index, valeur) :
colors = ["red", "green", "blue"]
for i, color in enumerate(colors):
print(i, color)Résultat :
0 red
1 green
2 blueCommencer à partir d'un index différent
Passez un argument start pour commencer le comptage à partir de n'importe quel entier :
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")Résultat :
1. apple
2. banana
3. cherryC'est utile lors de la construction de listes numérotées pour l'affichage, où une numérotation commençant à 0 paraît étrange au lecteur.
enumerate() avec une condition
Puisque enumerate() est simplement un itérateur ordinaire, vous pouvez ajouter des tests if dans le corps de la boucle :
scores = [72, 91, 55, 88, 44]
for rank, score in enumerate(scores, start=1):
if score >= 80:
print(f"Rank {rank}: score {score} — PASS")Résultat :
Rank 2: score 91 — PASS
Rank 4: score 88 — PASSObtenir uniquement l'index sans enumerate()
Si vous n'avez besoin que d'un index sans autre chose, range(len(seq)) fonctionne — mais dès que vous avez également besoin de la valeur, utilisez enumerate() pour éviter d'écrire seq[i] à répétition.
zip() — itérer sur plusieurs séquences en parallèle
zip() combine deux itérables ou plus en un seul itérateur de tuples, associant les éléments aux positions correspondantes :
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")Résultat :
Alice: 95
Bob: 87
Charlie: 92zip() avec trois itérables ou plus
first = ["a", "b", "c"]
second = [1, 2, 3]
third = [True, False, True]
for x, y, z in zip(first, second, third):
print(x, y, z)Résultat :
a 1 True
b 2 False
c 3 TrueSéquences de longueurs inégales
zip() s'arrête à l'itérable le plus court et abandonne silencieusement les éléments restants des séquences plus longues :
short = [1, 2]
long = [10, 20, 30, 40]
print(list(zip(short, long)))Résultat :
[(1, 10), (2, 20)]Les valeurs 30 et 40 de long sont ignorées. Si vous avez besoin de conserver tous les éléments, utilisez itertools.zip_longest de la bibliothèque standard :
from itertools import zip_longest
short = [1, 2]
long = [10, 20, 30, 40]
print(list(zip_longest(short, long, fillvalue=0)))Résultat :
[(1, 10), (2, 20), (0, 30), (0, 40)]Convertir un résultat zippé en liste ou en dictionnaire
zip() retourne un itérateur paresseux. Enveloppez-le dans list() pour matérialiser toutes les paires à la fois, ou passez-le à dict() pour construire un mapping à partir de deux séquences parallèles :
keys = ["name", "age", "city"]
values = ["Alice", 30, "Paris"]
record = dict(zip(keys, values))
print(record)Résultat :
{'name': 'Alice', 'age': 30, 'city': 'Paris'}Dézipper avec zip(*...)
L'astuce de décompression * « inverse » une séquence de tuples en séquences séparées :
pairs = [(1, "a"), (2, "b"), (3, "c")]
numbers, letters = zip(*pairs)
print(numbers) # (1, 2, 3)
print(letters) # ('a', 'b', 'c')map() — appliquer une fonction à chaque élément
map(function, iterable) applique function à chaque élément et retourne un itérateur paresseux de résultats. Elle remplace une boucle for qui construit une nouvelle liste en transformant chaque élément.
numbers = [1, 2, 3, 4, 5]
squares = map(lambda x: x ** 2, numbers)
print(list(squares))Résultat :
[1, 4, 9, 16, 25]map() avec une fonction nommée
Vous pouvez passer n'importe quel callable — une fonction intégrée, une fonction définie avec def, ou une lambda :
words = [" hello ", " world ", " python "]
stripped = list(map(str.strip, words))
print(stripped)Résultat :
['hello', 'world', 'python']Ici str.strip est passé en référence (sans parenthèses). map() appelle str.strip(word) pour chaque élément.
map() sur plusieurs itérables
Passez des itérables supplémentaires pour mapper une fonction qui prend deux arguments ou plus. map() parcourt alors tous les itérables en parallèle (comme zip(), il s'arrête au plus court) :
a = [1, 2, 3]
b = [10, 20, 30]
totals = list(map(lambda x, y: x + y, a, b))
print(totals)Résultat :
[11, 22, 33]map() vs compréhension de liste
Les deux approches sont valides. La règle générale :
| Situation | Préférer |
|---|---|
| Appliquer une fonction nommée existante | map() — plus court, pas besoin de lambda |
| Écrire une expression en ligne | Compréhension de liste — plus lisible |
| Plusieurs entrées en parallèle | map() avec plusieurs itérables |
Formes équivalentes :
# map() with a named function
result = list(map(str.upper, ["a", "b", "c"]))
# list comprehension — same result
result = [s.upper() for s in ["a", "b", "c"]]filter() — ne conserver que les éléments correspondant à une condition
filter(function, iterable) conserve les éléments pour lesquels function retourne une valeur vraie. Elle retourne un itérateur paresseux.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens))Résultat :
[2, 4, 6, 8, 10]filter() avec une fonction nommée
def is_positive(n):
return n > 0
values = [-3, -1, 0, 4, 7, -2]
positives = list(filter(is_positive, values))
print(positives)Résultat :
[4, 7]filter(None, iterable) — supprimer les valeurs fausses
Passer None comme fonction ne conserve que les éléments vrais. C'est un moyen rapide de supprimer les chaînes vides, les zéros et les valeurs None :
mixed = [0, "hello", "", None, 42, False, "world"]
truthy = list(filter(None, mixed))
print(truthy)Résultat :
['hello', 42, 'world']filter() vs compréhension de liste
filter() et une compréhension de liste conditionnelle font le même travail :
numbers = range(1, 11)
# filter()
evens_f = list(filter(lambda x: x % 2 == 0, numbers))
# list comprehension — equivalent
evens_c = [x for x in numbers if x % 2 == 0]
print(evens_f) # [2, 4, 6, 8, 10]
print(evens_c) # [2, 4, 6, 8, 10]Préférez une compréhension de liste lorsque la condition est écrite en ligne ; préférez filter() lorsque vous disposez déjà d'une fonction prédicat bien nommée.
Combiner enumerate, zip, map et filter
Ces quatre fonctions se composent naturellement. Voici un exemple réaliste : vous avez deux listes parallèles de noms d'élèves et de notes brutes ; vous souhaitez afficher un rapport numéroté qui n'inclut que les élèves ayant réussi (note >= 60), avec les notes converties en lettres.
def letter_grade(score):
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
elif score >= 60:
return "D"
return "F"
names = ["Alice", "Bob", "Carol", "Dave", "Eve"]
scores = [85, 52, 91, 67, 48]
# Step 1: pair names with scores
pairs = zip(names, scores)
# Step 2: keep only passing students
passing = filter(lambda pair: pair[1] >= 60, pairs)
# Step 3: convert scores to letter grades
graded = map(lambda pair: (pair[0], letter_grade(pair[1])), passing)
# Step 4: number the results
for rank, (name, grade) in enumerate(graded, start=1):
print(f"{rank}. {name}: {grade}")Résultat :
1. Alice: B
2. Carol: A
3. Dave: DChaque étape produit un itérateur ; rien n'est matérialisé dans une liste jusqu'à ce que la boucle for le consomme. Cela maintient une utilisation mémoire constante quelle que soit la taille de l'entrée.
Pièges courants
Les itérateurs sont épuisés après un seul passage
map(), filter() et zip() retournent tous des itérateurs, pas des listes. Une fois qu'un itérateur est consommé, il est vide :
squares = map(lambda x: x ** 2, [1, 2, 3])
print(list(squares)) # [1, 4, 9]
print(list(squares)) # [] — already exhaustedEnveloppez dans list() dès le départ si vous avez besoin d'itérer plus d'une fois.
zip() abandonne silencieusement les éléments en fin de séquence
Lorsque les séquences d'entrée ont des longueurs différentes, zip() supprime les éléments supplémentaires sans avertissement. Vérifiez toujours les longueurs explicitement si cela est important, ou utilisez itertools.zip_longest.
enumerate() ne modifie pas l'itérable
enumerate() enveloppe l'itérable d'origine ; il ne le copie pas. Modifier la liste sous-jacente pendant l'itération peut produire des résultats inattendus — la même précaution qui s'applique aux boucles for ordinaires.
Référence rapide
| Fonction | Signature | Ce qu'elle retourne |
|---|---|---|
enumerate | enumerate(iterable, start=0) | Itérateur de tuples (index, value) |
zip | zip(*iterables) | Itérateur de tuples, un élément de chaque itérable par tuple |
map | map(function, *iterables) | Itérateur de function appliqué à chaque élément |
filter | filter(function, iterable) | Itérateur d'éléments où function retourne True |
Les quatre sont paresseuses : elles produisent les valeurs à la demande plutôt que de construire une liste complète à l'avance.