Module itertools de Python
Maîtrisez le module itertools de Python : itérateurs infinis, combinatoires, regroupement, chaînage et filtrage — avec des exemples concrets et exécutables.
Le module itertools de Python est une boîte à outils de la bibliothèque standard proposant des blocs de construction rapides et économes en mémoire pour travailler avec les itérateurs. Chaque fonction d'itertools retourne un itérateur — il produit les valeurs à la demande plutôt que de construire une liste en mémoire — ce qui rend le module idéal pour les grands ensembles de données, les séquences infinies et les pipelines de données composables.
Ce chapitre couvre les trois catégories de fonctions d'itertools : les itérateurs infinis (count, cycle, repeat), les itérateurs combinatoires (product, permutations, combinations, combinations_with_replacement) et les itérateurs terminaux (chain, islice, groupby, compress, filterfalse, takewhile, dropwhile, starmap, zip_longest, accumulate, pairwise).
Aucune installation n'est nécessaire — itertools est livré avec chaque installation de Python 3 :
import itertoolsPourquoi itertools ?
Considérons la lecture des 10 premiers multiples d'un nombre. Sans itertools, il faut une liste ou un compteur manuel. Avec itertools.count et itertools.islice, l'intention est immédiatement claire et la consommation mémoire reste constante :
import itertools
multiples = itertools.islice(itertools.count(0, 7), 10)
print(list(multiples))
# [0, 7, 14, 21, 28, 35, 42, 49, 56, 63]La philosophie d'itertools : construire un petit élément correct, puis le composer avec d'autres. Enchaîner deux fonctions itertools est plus rapide et moins sujet aux erreurs qu'écrire la boucle équivalente à la main.
Itérateurs infinis
Ces itérateurs produisent des valeurs indéfiniment. Associez-les toujours à islice, à un for … break ou à un autre mécanisme de terminaison pour éviter une boucle infinie.
count(start=0, step=1)
count produit une séquence de nombres régulièrement espacés. C'est essentiellement un range sans borne supérieure, avec prise en charge des flottants et des pas négatifs.
import itertools
# Integer counter
for n in itertools.islice(itertools.count(10), 5):
print(n, end=' ')
# 10 11 12 13 14
print()
# Float step
for n in itertools.islice(itertools.count(0.0, 0.5), 5):
print(n, end=' ')
# 0.0 0.5 1.0 1.5 2.0
print()
# Countdown
for n in itertools.islice(itertools.count(100, -10), 5):
print(n, end=' ')
# 100 90 80 70 60count est utile lorsque vous devez numéroter les éléments d'un itérable sans connaître leur nombre à l'avance — l'idiome enumerate mais avec un début et un pas personnalisés.
cycle(iterable)
cycle répète indéfiniment les éléments de n'importe quel itérable.
import itertools
colours = itertools.cycle(['red', 'green', 'blue'])
for i, colour in enumerate(colours):
if i == 7:
break
print(colour, end=' ')
# red green blue red green blue redUtilisation pratique — attribuer des éléments à des équipes en tournoi à la ronde :
import itertools
teams = itertools.cycle(['Alpha', 'Beta', 'Gamma'])
players = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve']
assignments = {player: team for player, team in zip(players, teams)}
print(assignments)
# {'Alice': 'Alpha', 'Bob': 'Beta', 'Carol': 'Gamma', 'Dave': 'Alpha', 'Eve': 'Beta'}repeat(object, times=None)
repeat produit le même object times fois (ou indéfiniment si times est omis).
import itertools
# Finite repeat
print(list(itertools.repeat('hello', 3)))
# ['hello', 'hello', 'hello']
# Used as a fixed argument supplier in map()
squares = list(map(pow, range(1, 6), itertools.repeat(2)))
print(squares)
# [1, 4, 9, 16, 25]Le motif map(pow, range(1, 6), repeat(2)) est un idiome courant pour fournir un second argument constant à une fonction à deux arguments.
Itérateurs combinatoires
Ces itérateurs produisent toutes les combinaisons, permutations ou produits croisés d'un itérable d'entrée. Ils sont indispensables pour les recherches par force brute, la génération de cas de test et les problèmes de combinatoire.
product(*iterables, repeat=1)
product calcule le produit cartésien — toutes les combinaisons ordonnées où un élément est tiré de chaque itérable. Il est équivalent à des boucles for imbriquées.
import itertools
suits = ['Hearts', 'Diamonds']
ranks = ['A', 'K', 'Q']
deck = list(itertools.product(suits, ranks))
print(deck)
# [('Hearts', 'A'), ('Hearts', 'K'), ('Hearts', 'Q'),
# ('Diamonds', 'A'), ('Diamonds', 'K'), ('Diamonds', 'Q')]Utilisez repeat pour calculer le produit d'un itérable avec lui-même plusieurs fois :
import itertools
# All 2-digit binary numbers
binary_pairs = list(itertools.product([0, 1], repeat=2))
print(binary_pairs)
# [(0, 0), (0, 1), (1, 0), (1, 1)]Attention : product matérialise les itérables d'entrée en mémoire (pour permettre plusieurs passes), donc ne passez pas d'immenses itérateurs en entrée.
permutations(iterable, r=None)
permutations produit tous les arrangements ordonnés de r éléments tirés de l'entrée. Lorsque r est omis, tous les éléments sont utilisés.
import itertools
# All orderings of 3 letters
perms = list(itertools.permutations('ABC'))
print(perms)
# [('A', 'B', 'C'), ('A', 'C', 'B'), ('B', 'A', 'C'),
# ('B', 'C', 'A'), ('C', 'A', 'B'), ('C', 'B', 'A')]
print(len(perms)) # 6 (3! = 6)
# 2-element permutations
perms2 = list(itertools.permutations('ABC', 2))
print(perms2)
# [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
print(len(perms2)) # 6 (3 * 2 = 6)L'ordre compte dans les permutations — ('A', 'B') et ('B', 'A') sont des résultats distincts.
combinations(iterable, r)
combinations produit toutes les sélections non ordonnées de r éléments. Contrairement à permutations, l'ordre n'a pas d'importance — chaque sous-ensemble n'apparaît qu'une seule fois.
import itertools
# All 2-element subsets of [1, 2, 3, 4]
combos = list(itertools.combinations([1, 2, 3, 4], 2))
print(combos)
# [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
print(len(combos)) # 6 (C(4,2) = 6)Un cas d'utilisation courant — vérifier toutes les paires d'éléments pour une propriété :
import itertools
words = ['bat', 'tab', 'cat', 'tac']
anagram_pairs = [
(a, b) for a, b in itertools.combinations(words, 2)
if sorted(a) == sorted(b)
]
print(anagram_pairs)
# [('bat', 'tab'), ('cat', 'tac')]combinations_with_replacement(iterable, r)
Comme combinations, mais permet à chaque élément d'apparaître plus d'une fois dans une sélection.
import itertools
# All 2-element combinations with repetition from [1, 2, 3]
combos = list(itertools.combinations_with_replacement([1, 2, 3], 2))
print(combos)
# [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]Cela est utile pour générer tous les lancers de dés possibles, les séquences de jets de pièces ou les choix de caractères pour un mot de passe.
Fonctions combinatoires en un coup d'œil
| Fonction | L'ordre compte ? | Répétitions autorisées ? | Nombre (n=4, r=2) |
|---|---|---|---|
product | Oui | Oui | n^r = 16 |
permutations | Oui | Non | n!/(n-r)! = 12 |
combinations | Non | Non | C(n,r) = 6 |
combinations_with_replacement | Non | Oui | C(n+r-1,r) = 10 |
Itérateurs terminaux
Les itérateurs terminaux traitent une entrée finie et s'arrêtent lorsque cette entrée est épuisée.
chain(*iterables)
chain traite plusieurs itérables comme une seule séquence continue sans construire une nouvelle liste.
import itertools
a = [1, 2, 3]
b = (4, 5)
c = range(6, 9)
combined = list(itertools.chain(a, b, c))
print(combined)
# [1, 2, 3, 4, 5, 6, 7, 8]chain.from_iterable accepte un seul itérable d'itérables — utile lorsque vous ne connaissez pas le nombre de séquences à l'avance :
import itertools
nested = [[1, 2], [3, 4], [5, 6]]
flat = list(itertools.chain.from_iterable(nested))
print(flat)
# [1, 2, 3, 4, 5, 6]C'est une alternative rapide et économe en mémoire à [item for sublist in nested for item in sublist].
islice(iterable, stop) / islice(iterable, start, stop, step=1)
islice découpe n'importe quel itérateur — y compris les infinis — sans le matérialiser. Les arguments reflètent la notation slice de Python mais n'acceptent que des entiers non négatifs.
import itertools
# First 5 elements
print(list(itertools.islice(range(100), 5)))
# [0, 1, 2, 3, 4]
# Elements 10–14 (start inclusive, stop exclusive)
print(list(itertools.islice(range(100), 10, 15)))
# [10, 11, 12, 13, 14]
# Every other element from position 0 to 10
print(list(itertools.islice(range(20), 0, 10, 2)))
# [0, 2, 4, 6, 8]islice ne prend pas en charge les indices négatifs ni les pas négatifs (contrairement au découpage de liste classique).
groupby(iterable, key=None)
groupby regroupe les éléments consécutifs qui partagent la même valeur de clé. Il retourne des paires (key, group_iterator).
import itertools
data = [
('fruit', 'apple'),
('fruit', 'banana'),
('veggie', 'carrot'),
('veggie', 'broccoli'),
('fruit', 'cherry'),
]
for category, group in itertools.groupby(data, key=lambda x: x[0]):
items = [item[1] for item in group]
print(f'{category}: {items}')
# fruit: ['apple', 'banana']
# veggie: ['carrot', 'broccoli']
# fruit: ['cherry']Point critique : groupby ne regroupe que les éléments consécutifs égaux. Si vos données ne sont pas triées au préalable par la clé, des éléments similaires situés à des positions différentes forment des groupes séparés (comme illustré ci-dessus — 'cherry' démarre un nouveau groupe 'fruit' au lieu de rejoindre le premier). Triez toujours par la clé avant d'appeler groupby :
import itertools
data = [
('fruit', 'apple'),
('veggie', 'carrot'),
('fruit', 'banana'),
('veggie', 'broccoli'),
('fruit', 'cherry'),
]
# Sort first, then group
sorted_data = sorted(data, key=lambda x: x[0])
for category, group in itertools.groupby(sorted_data, key=lambda x: x[0]):
items = [item[1] for item in group]
print(f'{category}: {items}')
# fruit: ['apple', 'banana', 'cherry']
# veggie: ['carrot', 'broccoli']Notez également que l'itérateur de groupe devient invalide dès que vous passez à la clé suivante — consommez chaque groupe avant d'appeler next() sur l'itérateur externe.
compress(data, selectors)
compress filtre data en ne conservant que les éléments dont la valeur selector correspondante est vraie.
import itertools
names = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve']
active = [True, False, True, True, False]
result = list(itertools.compress(names, active))
print(result)
# ['Alice', 'Carol', 'Dave']compress est équivalent à [d for d, s in zip(data, selectors) if s] mais est plus rapide et évite la liste intermédiaire.
filterfalse(predicate, iterable)
filterfalse est le complément du filter intégré — il produit les éléments pour lesquels le prédicat retourne False.
import itertools
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Keep only odd numbers (those that fail the even test)
odds = list(itertools.filterfalse(lambda x: x % 2 == 0, numbers))
print(odds)
# [1, 3, 5, 7, 9]takewhile(predicate, iterable)
takewhile produit des éléments tant que le prédicat est True, puis s'arrête immédiatement — même si des éléments ultérieurs satisferaient le prédicat.
import itertools
data = [2, 4, 6, 3, 8, 10]
# Stop as soon as an odd number appears
evens_from_start = list(itertools.takewhile(lambda x: x % 2 == 0, data))
print(evens_from_start)
# [2, 4, 6]dropwhile(predicate, iterable)
dropwhile est le miroir de takewhile : il ignore les éléments tant que le prédicat est True, puis produit tous les éléments restants (y compris ceux où le prédicat serait à nouveau True).
import itertools
data = [2, 4, 6, 3, 8, 10]
# Drop leading even numbers, yield everything from the first odd onward
result = list(itertools.dropwhile(lambda x: x % 2 == 0, data))
print(result)
# [3, 8, 10]takewhile et dropwhile sont utiles pour traiter des fichiers journaux ou des flux où l'on souhaite ignorer une section d'en-tête ou s'arrêter à une ligne sentinelle.
starmap(function, iterable)
starmap applique une fonction à chaque élément d'un itérable, en décompressant l'élément comme arguments positionnels. C'est l'équivalent de map mais pour les itérables de tuples.
import itertools
pairs = [(2, 3), (4, 2), (10, 3)]
results = list(itertools.starmap(pow, pairs))
print(results)
# [8, 16, 1000]Comparez avec map(pow, [2, 4, 10], [3, 2, 3]) — starmap fonctionne lorsque vos arguments sont déjà regroupés sous forme de tuples.
zip_longest(*iterables, fillvalue=None)
Le zip intégré s'arrête à l'itérable le plus court. zip_longest complète les itérables plus courts avec fillvalue afin que tous les itérables soient entièrement consommés.
import itertools
a = [1, 2, 3]
b = ['a', 'b', 'c', 'd', 'e']
print(list(zip(a, b)))
# [(1, 'a'), (2, 'b'), (3, 'c')] — b's 'd' and 'e' are lost
print(list(itertools.zip_longest(a, b, fillvalue=0)))
# [(1, 'a'), (2, 'b'), (3, 'c'), (0, 'd'), (0, 'e')]accumulate(iterable, func=operator.add, *, initial=None)
accumulate calcule des totaux cumulés (ou toute autre agrégation progressive). Par défaut il additionne, mais vous pouvez passer n'importe quelle fonction à deux arguments.
import itertools
import operator
numbers = [1, 2, 3, 4, 5]
# Running sum (default)
print(list(itertools.accumulate(numbers)))
# [1, 3, 6, 10, 15]
# Running product
print(list(itertools.accumulate(numbers, operator.mul)))
# [1, 2, 6, 24, 120]
# Running maximum
data = [3, 1, 4, 1, 5, 9, 2, 6]
print(list(itertools.accumulate(data, max)))
# [3, 3, 4, 4, 5, 9, 9, 9]Le paramètre initial (Python 3.8+) préfixe une valeur de départ avant le premier élément :
import itertools
print(list(itertools.accumulate([1, 2, 3], initial=100)))
# [100, 101, 103, 106]pairwise(iterable)
pairwise (Python 3.10+) produit des paires consécutives se chevauchant à partir de l'itérable.
import itertools
data = [1, 2, 3, 4, 5]
print(list(itertools.pairwise(data)))
# [(1, 2), (2, 3), (3, 4), (4, 5)]Cela est utile pour calculer les différences entre des valeurs consécutives, ou pour une logique de fenêtre glissante où la taille de la fenêtre est exactement 2 :
import itertools
prices = [10.0, 12.5, 11.0, 13.5, 15.0]
changes = [b - a for a, b in itertools.pairwise(prices)]
print(changes)
# [2.5, -1.5, 2.5, 1.5]Avant Python 3.10, l'équivalent était zip(data, data[1:]) (fonctionne pour les séquences) ou une approche manuelle basée sur tee (fonctionne pour les itérateurs quelconques).
Composer des pipelines avec itertools
La vraie puissance d'itertools se manifeste lorsque vous combinez des fonctions. Comme chaque fonction retourne un itérateur, vous pouvez les enchaîner sans aucune liste intermédiaire.
Exemple : les 3 mots les plus fréquents dans un texte
import itertools
import operator
text = "the quick brown fox jumps over the lazy dog the fox"
words = text.split()
# Sort words so groupby can collect identical words together
sorted_words = sorted(words)
# Count each word using groupby
word_counts = (
(key, sum(1 for _ in group))
for key, group in itertools.groupby(sorted_words)
)
# Sort by count descending, take the top 3
top3 = list(itertools.islice(
sorted(word_counts, key=operator.itemgetter(1), reverse=True),
3
))
print(top3)
# [('the', 3), ('fox', 2), ('brown', 1)]Exemple : diviser un itérable en morceaux de taille fixe
import itertools
def batched(iterable, n):
"""Yield successive n-sized tuples from iterable."""
it = iter(iterable)
while chunk := tuple(itertools.islice(it, n)):
yield chunk
data = range(10)
for batch in batched(data, 3):
print(batch)
# (0, 1, 2)
# (3, 4, 5)
# (6, 7, 8)
# (9,)Python 3.12 embarque itertools.batched en natif, vous pouvez donc remplacer l'utilitaire ci-dessus par itertools.batched(data, 3) sur les versions modernes de Python.
Référence rapide
| Catégorie | Fonction | Ce qu'elle fait |
|---|---|---|
| Infini | count(start, step) | Nombres régulièrement espacés à l'infini |
| Infini | cycle(iterable) | Répéter les éléments de l'itérable à l'infini |
| Infini | repeat(obj, n) | Produire obj exactement n fois (ou à l'infini) |
| Combinatoire | product(*its, repeat) | Produit cartésien |
| Combinatoire | permutations(it, r) | Arrangements ordonnés, sans répétition |
| Combinatoire | combinations(it, r) | Sous-ensembles non ordonnés, sans répétition |
| Combinatoire | combinations_with_replacement(it, r) | Sous-ensembles non ordonnés, répétitions autorisées |
| Terminal | chain(*its) | Concaténer des itérables |
| Terminal | chain.from_iterable(it) | Aplatir un niveau d'imbrication |
| Terminal | islice(it, stop) | Découper un itérateur |
| Terminal | groupby(it, key) | Regrouper les éléments consécutifs de même clé |
| Terminal | compress(data, sel) | Filtrer par masque booléen |
| Terminal | filterfalse(pred, it) | Garder les éléments où le prédicat est False |
| Terminal | takewhile(pred, it) | Produire tant que le prédicat est True, puis s'arrêter |
| Terminal | dropwhile(pred, it) | Ignorer tant que le prédicat est True, puis produire |
| Terminal | starmap(func, it) | Map avec décompression des arguments |
| Terminal | zip_longest(*its, fill) | Zip en complétant les itérables plus courts |
| Terminal | accumulate(it, func) | Agrégation progressive |
| Terminal | pairwise(it) | Paires consécutives se chevauchant (3.10+) |
Pour les concepts d'évaluation paresseuse qui sous-tendent itertools, voir Python Generators et Python Iterators. Pour les utilitaires de style fonctionnel qui complètent itertools, voir Python Lambda Functions et le Python collections Module.