Skip to content

Python itertools : Guide Complet des Blocs de Construction d'Itérateurs

Updated on

Écrire des boucles est une seconde nature pour tout développeur Python. Mais en y regardant de plus près, on retrouve toujours les mêmes schémas dans la plupart des bases de code : aplatir des listes imbriquées, générer des combinaisons, grouper des données triées, découper des itérateurs, accumuler des totaux partiels. Chacun est typiquement résolu avec une boucle for écrite à la main, des variables temporaires et de la jonglerie d'indices. Le code fonctionne, mais il est verbeux, sujet aux erreurs et lent lorsque les jeux de données grossissent.

Ces problèmes s'accumulent. Une boucle imbriquée sur deux listes produit un produit cartésien, mais l'écrire manuellement obscurcit l'intention. Générer toutes les combinaisons de longueur 3 d'une collection nécessite une gestion minutieuse des indices. Grouper des éléments consécutifs par une clé nécessite une variable d'état facile à mal gérer. Chacun de ces schémas a été résolu, testé et optimisé dans le module itertools de Python -- une boîte à outils de la bibliothèque standard qui transforme des constructions de boucles multilignes en appels de fonctions uniques et lisibles produisant des itérateurs efficaces en mémoire.

Ce guide couvre chaque fonction majeure d'itertools avec des exemples pratiques et exécutables. À la fin, vous aurez une référence pour remplacer les schémas de boucles verbeux par des pipelines d'itérateurs propres et performants.

📚

Importer itertools

Le module itertools fait partie de la bibliothèque standard de Python. Aucune installation n'est nécessaire.

import itertools
 
# Ou importer des fonctions spécifiques
from itertools import chain, combinations, groupby, islice

Itérateurs Infinis

Ces fonctions produisent des itérateurs qui ne se terminent jamais d'eux-mêmes. Vous devez utiliser islice(), takewhile() ou un autre mécanisme d'arrêt pour éviter les boucles infinies.

count() -- Séquences Arithmétiques

count(start, step) génère des valeurs uniformément espacées à partir de start, avec un incrément de step (par défaut 1).

from itertools import count, islice
 
# Compter à partir de 10, pas de 2
counter = count(10, 2)
print(list(islice(counter, 6)))
# [10, 12, 14, 16, 18, 20]
 
# Séquences à virgule flottante
floats = count(0.0, 0.25)
print(list(islice(floats, 5)))
# [0.0, 0.25, 0.5, 0.75, 1.0]
 
# Étiqueter les lignes avec un index
names = ["Alice", "Bob", "Charlie"]
for idx, name in zip(count(1), names):
    print(f"{idx}. {name}")
# 1. Alice
# 2. Bob
# 3. Charlie

cycle() -- Répéter une Séquence Indéfiniment

cycle(iterable) sauvegarde une copie de l'itérable et renvoie ses éléments de façon répétée.

from itertools import cycle, islice
 
colors = cycle(["red", "green", "blue"])
print(list(islice(colors, 7)))
# ['red', 'green', 'blue', 'red', 'green', 'blue', 'red']
 
# Affectation en tourniquet (round-robin)
tasks = ["task_a", "task_b", "task_c", "task_d", "task_e"]
workers = cycle(["Worker1", "Worker2", "Worker3"])
assignments = {task: worker for task, worker in zip(tasks, workers)}
print(assignments)
# {'task_a': 'Worker1', 'task_b': 'Worker2', 'task_c': 'Worker3',
#  'task_d': 'Worker1', 'task_e': 'Worker2'}

repeat() -- Renvoyer la Même Valeur

repeat(value, times) renvoie value soit indéfiniment, soit un nombre fixe de fois.

from itertools import repeat
 
# Répétitions fixes
print(list(repeat("hello", 3)))
# ['hello', 'hello', 'hello']
 
# Utiliser repeat comme argument constant dans map()
import operator
bases = [2, 3, 4, 5]
squared = list(map(operator.pow, bases, repeat(2)))
print(squared)
# [4, 9, 16, 25]

Itérateurs Finis : Découpage et Filtrage

Ces fonctions consomment un ou plusieurs itérables en entrée et produisent une sortie finie.

chain() -- Concaténer Plusieurs Itérables

chain(*iterables) renvoie les éléments du premier itérable jusqu'à épuisement, puis du suivant, et ainsi de suite. chain.from_iterable() accepte un unique itérable d'itérables.

from itertools import chain
 
# Chaîner plusieurs listes
a = [1, 2, 3]
b = [4, 5, 6]
c = [7, 8, 9]
print(list(chain(a, b, c)))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
 
# Aplatir un niveau d'imbrication
nested = [[1, 2], [3, 4], [5, 6]]
print(list(chain.from_iterable(nested)))
# [1, 2, 3, 4, 5, 6]
 
# Combiner un générateur avec une liste
def evens():
    yield 2; yield 4; yield 6
 
print(list(chain(evens(), [8, 10])))
# [2, 4, 6, 8, 10]

compress() -- Filtrer par Sélecteurs

compress(data, selectors) renvoie les éléments de data où l'élément correspondant dans selectors est vrai.

from itertools import compress
 
data = ["A", "B", "C", "D", "E"]
selectors = [1, 0, 1, 0, 1]
print(list(compress(data, selectors)))
# ['A', 'C', 'E']
 
# Avec des conditions booléennes
values = [10, 25, 3, 42, 7, 18]
mask = [v > 15 for v in values]
print(list(compress(values, mask)))
# [25, 42, 18]

islice() -- Découper N'importe Quel Itérateur

islice(iterable, stop) ou islice(iterable, start, stop, step) fonctionne comme la notation de tranche mais sur n'importe quel itérateur, sans construire une liste au préalable.

from itertools import islice, count
 
# 5 premiers éléments d'un compteur infini
print(list(islice(count(100), 5)))
# [100, 101, 102, 103, 104]
 
# Éléments de 3 à 8
print(list(islice(range(20), 3, 9)))
# [3, 4, 5, 6, 7, 8]
 
# Un élément sur 3 parmi les 15 premiers
print(list(islice(range(100), 0, 15, 3)))
# [0, 3, 6, 9, 12]
 
# Lire les 5 premières lignes d'un itérateur de fichier
# lines = list(islice(open("data.txt"), 5))

takewhile() et dropwhile() -- Découpage Conditionnel

takewhile(predicate, iterable) renvoie des éléments tant que le prédicat est vrai, puis s'arrête. dropwhile(predicate, iterable) ignore les éléments tant que le prédicat est vrai, puis renvoie le reste.

from itertools import takewhile, dropwhile
 
data = [1, 3, 5, 7, 2, 4, 6, 8]
 
# Prendre les éléments tant qu'ils sont inférieurs à 6
print(list(takewhile(lambda x: x < 6, data)))
# [1, 3, 5]
 
# Ignorer les éléments tant qu'ils sont inférieurs à 6
print(list(dropwhile(lambda x: x < 6, data)))
# [7, 2, 4, 6, 8]

Notez que takewhile s'arrête au premier False ; il ne reprend pas si des éléments ultérieurs satisfont à nouveau le prédicat.

starmap() -- Appliquer une Fonction à des Arguments Pré-groupés

starmap(function, iterable) applique une fonction en utilisant des tuples d'arguments de l'itérable. C'est l'équivalent de function(*args) pour chaque args dans l'itérable.

from itertools import starmap
import operator
 
pairs = [(2, 5), (3, 4), (10, 3)]
print(list(starmap(operator.mul, pairs)))
# [10, 12, 30]
 
# Calculer l'hypoténuse pour plusieurs triangles
import math
triangles = [(3, 4), (5, 12), (8, 15)]
print(list(starmap(math.hypot, triangles)))
# [5.0, 13.0, 17.0]

filterfalse() -- Filtre Inverse

filterfalse(predicate, iterable) renvoie les éléments pour lesquels le prédicat retourne False. C'est le complément de la fonction intégrée filter().

from itertools import filterfalse
 
numbers = range(10)
# Garder les nombres impairs (où "est_pair" est faux)
odd = list(filterfalse(lambda x: x % 2 == 0, numbers))
print(odd)
# [1, 3, 5, 7, 9]

Itérateurs Combinatoires

Ces fonctions produisent tous les arrangements possibles des éléments d'entrée. Elles sont essentielles pour la recherche par force brute, les tests et les calculs mathématiques.

product() -- Produit Cartésien

itertools.product(*iterables, repeat=1) remplace les boucles for imbriquées sur plusieurs séquences.

from itertools import product
 
# Deux listes
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
print(list(product(colors, sizes)))
# [('red', 'S'), ('red', 'M'), ('red', 'L'),
#  ('blue', 'S'), ('blue', 'M'), ('blue', 'L')]
 
# Équivalent à :
# [(c, s) for c in colors for s in sizes]
 
# Lancers de dés : deux dés à six faces
dice = range(1, 7)
all_rolls = list(product(dice, repeat=2))
print(f"Combinaisons totales : {len(all_rolls)}")
# Combinaisons totales : 36
print(all_rolls[:5])
# [(1, 1), (1, 2), (1, 3), (1, 4), (1, 5)]

permutations() -- Tous les Ordres

permutations(iterable, r) génère tous les ordres possibles de longueur r à partir de l'entrée. L'ordre compte, donc (A, B) et (B, A) sont tous deux inclus.

from itertools import permutations
 
# Tous les arrangements de 2 lettres de 'ABC'
print(list(permutations("ABC", 2)))
# [('A', 'B'), ('A', 'C'), ('B', 'A'),
#  ('B', 'C'), ('C', 'A'), ('C', 'B')]
 
# Permutations complètes (tous les éléments)
print(list(permutations([1, 2, 3])))
# [(1, 2, 3), (1, 3, 2), (2, 1, 3),
#  (2, 3, 1), (3, 1, 2), (3, 2, 1)]
 
# Nombre de permutations : n! / (n-r)!
import math
n, r = 5, 3
print(f"P({n},{r}) = {math.perm(n, r)}")
# P(5,3) = 60

combinations() -- Sous-ensembles Uniques

combinations(iterable, r) génère tous les sous-ensembles uniques de longueur r. L'ordre ne compte pas, donc (A, B) est inclus mais (B, A) ne l'est pas.

from itertools import combinations
 
# Toutes les combinaisons de 2 éléments de [1, 2, 3, 4]
print(list(combinations([1, 2, 3, 4], 2)))
# [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
 
# Choisir 3 garnitures dans un menu
toppings = ["cheese", "pepperoni", "mushrooms", "onions", "peppers"]
combos = list(combinations(toppings, 3))
print(f"Nombre de combinaisons à 3 garnitures : {len(combos)}")
# Nombre de combinaisons à 3 garnitures : 10
for c in combos[:3]:
    print(c)
# ('cheese', 'pepperoni', 'mushrooms')
# ('cheese', 'pepperoni', 'onions')
# ('cheese', 'pepperoni', 'peppers')

combinations_with_replacement() -- Sous-ensembles avec Répétition

Les éléments peuvent être sélectionnés plus d'une fois.

from itertools import combinations_with_replacement
 
# Dénominations de pièces : choisir 3 pièces
coins = [1, 5, 10, 25]
selections = list(combinations_with_replacement(coins, 3))
print(f"Sélections totales : {len(selections)}")
# Sélections totales : 35
print(selections[:5])
# [(1, 1, 1), (1, 1, 5), (1, 1, 10), (1, 1, 25), (1, 5, 5)]

Grouper des Données avec groupby()

itertools.groupby(iterable, key=None) groupe les éléments consécutifs partageant la même clé. L'entrée doit être triée par la clé au préalable, sinon des clés identiques à des positions non adjacentes formeront des groupes séparés.

from itertools import groupby
 
# Grouper les éléments consécutifs identiques
data = "AAABBBCCAAB"
for key, group in groupby(data):
    print(f"{key}: {list(group)}")
# A: ['A', 'A', 'A']
# B: ['B', 'B', 'B']
# C: ['C', 'C']
# A: ['A', 'A']
# B: ['B']

Exemple Pratique : Grouper des Enregistrements par Catégorie

from itertools import groupby
from operator import itemgetter
 
sales = [
    {"product": "Widget", "category": "Hardware", "revenue": 150},
    {"product": "Gadget", "category": "Hardware", "revenue": 300},
    {"product": "App Pro", "category": "Software", "revenue": 500},
    {"product": "Cloud X", "category": "Software", "revenue": 200},
    {"product": "Cable",   "category": "Hardware", "revenue": 50},
]
 
# Trier par catégorie d'abord -- groupby nécessite une entrée triée
sales.sort(key=itemgetter("category"))
 
for category, items in groupby(sales, key=itemgetter("category")):
    item_list = list(items)
    total = sum(item["revenue"] for item in item_list)
    print(f"{category} : {len(item_list)} produits, ${total} de revenus")
# Hardware : 3 produits, $500 de revenus
# Software : 2 produits, $700 de revenus

Grouper des Nombres par Propriété

from itertools import groupby
 
numbers = sorted(range(1, 16), key=lambda x: x % 3)
for remainder, group in groupby(numbers, key=lambda x: x % 3):
    print(f"Reste {remainder} : {list(group)}")
# Reste 0 : [3, 6, 9, 12, 15]
# Reste 1 : [1, 4, 7, 10, 13]
# Reste 2 : [2, 5, 8, 11, 14]

Totaux Cumulés avec accumulate()

itertools.accumulate(iterable, func=operator.add, initial=None) produit des résultats cumulés (partiels). Par défaut, il calcule une somme cumulée, mais toute fonction binaire peut être fournie.

from itertools import accumulate
import operator
 
# Somme cumulée
data = [1, 2, 3, 4, 5]
print(list(accumulate(data)))
# [1, 3, 6, 10, 15]
 
# Produit cumulé
print(list(accumulate(data, operator.mul)))
# [1, 2, 6, 24, 120]
 
# Maximum cumulé
temps = [72, 68, 75, 71, 78, 74, 80]
print(list(accumulate(temps, max)))
# [72, 72, 75, 75, 78, 78, 80]
 
# Avec une valeur initiale
print(list(accumulate(data, operator.add, initial=100)))
# [100, 101, 103, 106, 110, 115]

Pratique : Solde Cumulé

from itertools import accumulate
 
transactions = [1000, -200, -150, 500, -300, -100, 250]
balances = list(accumulate(transactions))
print("Transactions :", transactions)
print("Soldes :      ", balances)
# Transactions : [1000, -200, -150, 500, -300, -100, 250]
# Soldes :       [1000, 800, 650, 1150, 850, 750, 1000]

Référence Complète des Fonctions

FonctionCatégorieDescriptionSortie Exemple
count(start, step)InfiniSéquence arithmétique10, 12, 14, 16, ...
cycle(iterable)InfiniRépète l'itérable sans finA, B, C, A, B, C, ...
repeat(val, n)InfiniMême valeur n fois (ou indéfiniment)5, 5, 5, 5
chain(*iterables)FiniConcaténer les itérables[1,2] + [3,4] -> 1,2,3,4
compress(data, sel)FiniFiltrer par sélecteurs booléensABCDE, 10101 -> A,C,E
islice(iter, start, stop, step)FiniDécouper tout itérateurComme list[start:stop:step]
takewhile(pred, iter)FiniRenvoyer tant que prédicat vraiS'arrête au premier faux
dropwhile(pred, iter)FiniIgnorer tant que prédicat vraiCommence au premier faux
filterfalse(pred, iter)FiniRenvoyer où prédicat est fauxInverse de filter()
starmap(func, iter)FiniAppliquer fonction aux tuples d'argsfunc(*args) pour chaque
accumulate(iter, func)FiniTotaux cumulés[1,3,6,10,15]
groupby(iter, key)FiniGrouper consécutifs par cléNécessite entrée triée
product(*iters)CombinatoireProduit cartésienRemplace boucles imbriquées
permutations(iter, r)CombinatoireTous les ordres de longueur rL'ordre compte
combinations(iter, r)CombinatoireTous les sous-ensembles de longueur rL'ordre ne compte pas
combinations_with_replacement(iter, r)CombinatoireSous-ensembles avec répétitionÉléments répétables

Performance : itertools vs Boucles Manuelles

Les fonctions d'itertools sont implémentées en C, ce qui les rend significativement plus rapides et plus efficaces en mémoire que les boucles Python équivalentes. Elles produisent des itérateurs (évaluation paresseuse), ce qui signifie qu'elles renvoient un élément à la fois au lieu de construire des listes entières en mémoire.

import time
from itertools import chain
 
# Benchmark : aplatir une liste de 1000 sous-listes de 1000 éléments chacune
nested = [list(range(1000)) for _ in range(1000)]
 
# Approche manuelle
start = time.perf_counter()
result_manual = []
for sublist in nested:
    result_manual.extend(sublist)
manual_time = time.perf_counter() - start
 
# Approche itertools
start = time.perf_counter()
result_itertools = list(chain.from_iterable(nested))
itertools_time = time.perf_counter() - start
 
print(f"Extend manuel : {manual_time:.4f}s")
print(f"chain.from_iterable : {itertools_time:.4f}s")
print(f"Accélération : {manual_time / itertools_time:.2f}x")
# Sortie typique :
# Extend manuel : 0.0180s
# chain.from_iterable : 0.0120s
# Accélération : 1.50x

Efficacité Mémoire

import sys
from itertools import islice, count
 
# Une liste d'1 million d'entiers
big_list = list(range(1_000_000))
print(f"Mémoire liste : {sys.getsizeof(big_list):,} octets")
# Mémoire liste : 8,000,056 octets
 
# Un itérateur sur la même plage (mémoire négligeable)
big_iter = islice(count(), 1_000_000)
print(f"Mémoire itérateur : {sys.getsizeof(big_iter)} octets")
# Mémoire itérateur : 72 octets

Les itérateurs traitent un élément à la fois. Lors du chaînage de plusieurs transformations, aucune liste intermédiaire n'est créée.

Recettes Courantes

La documentation d'itertools inclut plusieurs "recettes" -- des schémas courants construits à partir des primitives du module. Voici les plus utiles.

Aplatir des Listes Imbriquées

from itertools import chain
 
def flatten(nested_list):
    """Aplatir un niveau d'imbrication."""
    return list(chain.from_iterable(nested_list))
 
data = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
print(flatten(data))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Fenêtre Glissante

from itertools import islice
from collections import deque
 
def sliding_window(iterable, n):
    """Renvoyer une fenêtre glissante de taille n sur l'itérable."""
    iterator = iter(iterable)
    window = deque(islice(iterator, n), maxlen=n)
    if len(window) == n:
        yield tuple(window)
    for item in iterator:
        window.append(item)
        yield tuple(window)
 
data = [1, 2, 3, 4, 5, 6, 7]
for window in sliding_window(data, 3):
    print(window)
# (1, 2, 3)
# (2, 3, 4)
# (3, 4, 5)
# (4, 5, 6)
# (5, 6, 7)

Note : Python 3.12+ inclut itertools.pairwise() pour les fenêtres de taille 2, et itertools.batched() pour les blocs non chevauchants.

Découper un Itérable en Blocs

from itertools import islice
 
def chunked(iterable, size):
    """Diviser un itérable en blocs de taille fixe."""
    iterator = iter(iterable)
    while True:
        chunk = list(islice(iterator, size))
        if not chunk:
            break
        yield chunk
 
data = list(range(1, 12))
for chunk in chunked(data, 3):
    print(chunk)
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
# [10, 11]

En Python 3.12+, utilisez itertools.batched(iterable, n) pour le même résultat avec une fonction intégrée.

Itération par Paires

from itertools import pairwise  # Python 3.10+
 
data = [10, 20, 30, 40, 50]
for a, b in pairwise(data):
    print(f"{a} -> {b}, différence = {b - a}")
# 10 -> 20, différence = 10
# 20 -> 30, différence = 10
# 30 -> 40, différence = 10
# 40 -> 50, différence = 10

Round-Robin depuis Plusieurs Itérables

from itertools import cycle, islice
 
def roundrobin(*iterables):
    """Renvoyer les éléments de chaque itérable à tour de rôle."""
    iterators = [iter(it) for it in iterables]
    active = len(iterators)
    nexts = cycle(iter(it).__next__ for it in iterables)
    # Approche plus simple :
    pending = len(iterables)
    iters = cycle(iter(it) for it in iterables)
    # Utiliser la recette de la documentation :
    result = []
    iterators = list(map(iter, iterables))
    while iterators:
        next_iterators = []
        for it in iterators:
            try:
                result.append(next(it))
                next_iterators.append(it)
            except StopIteration:
                pass
        iterators = next_iterators
    return result
 
print(roundrobin("ABC", "D", "EF"))
# ['A', 'D', 'E', 'B', 'F', 'C']

Éléments Uniques en Préservant l'Ordre

from itertools import filterfalse
 
def unique_everseen(iterable):
    """Renvoyer les éléments uniques en préservant l'ordre de première apparition."""
    seen = set()
    for element in filterfalse(seen.__contains__, iterable):
        seen.add(element)
        yield element
 
data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print(list(unique_everseen(data)))
# [3, 1, 4, 5, 9, 2, 6]

Chaîner des Fonctions itertools

La vraie puissance d'itertools apparaît lorsque vous composez plusieurs fonctions en un pipeline. Comme chaque fonction renvoie un itérateur, il n'y a pas de gaspillage de mémoire entre les étapes.

from itertools import chain, compress, accumulate, islice
 
# Pipeline : aplatir -> filtrer -> somme cumulée -> prendre les 5 premiers
nested = [[10, 20, 30], [5, 15, 25], [40, 50]]
mask = [True, False, True, True, False, True, True, False]
 
flat = chain.from_iterable(nested)    # 10, 20, 30, 5, 15, 25, 40, 50
filtered = compress(flat, mask)        # 10, 30, 5, 25, 40
running = accumulate(filtered)         # 10, 40, 45, 70, 110
first_four = list(islice(running, 4))
 
print(first_four)
# [10, 40, 45, 70]

Aucune liste intermédiaire n'a été créée à aucune étape. Chaque valeur traverse le pipeline un élément à la fois.

Expérimenter avec itertools dans Jupyter

Les pipelines d'itérateurs peuvent être difficiles à déboguer car les itérateurs sont consommés au premier passage. Un environnement de notebook interactif facilite l'inspection des résultats intermédiaires, le test des cas limites et la visualisation du flux de données à travers chaque étape. RunCell (opens in a new tab) fournit un environnement Jupyter alimenté par l'IA qui est bien adapté à ce type d'exploration -- vous pouvez parcourir pas à pas les sorties d'itérateurs, obtenir des explications assistées par l'IA lorsqu'un pipeline se comporte de manière inattendue, et prototyper rapidement des recettes avant de les intégrer en code de production.

FAQ

Qu'est-ce qu'itertools en Python ?

itertools est un module de la bibliothèque standard qui fournit une collection de fonctions rapides et efficaces en mémoire pour créer et travailler avec des itérateurs. Il inclut des outils pour les séquences infinies (count, cycle, repeat), les schémas d'itération finis (chain, islice, groupby) et la combinatoire (product, permutations, combinations). Toutes les fonctions renvoient des itérateurs, ce qui signifie qu'elles génèrent les valeurs de manière paresseuse sans construire des listes complètes en mémoire.

Quelle est la différence entre itertools.combinations et itertools.permutations ?

combinations(iterable, r) génère tous les sous-ensembles uniques de longueur r où l'ordre ne compte pas -- (A, B) et (B, A) sont considérés comme identiques et seul (A, B) est renvoyé. permutations(iterable, r) génère tous les ordres de longueur r où l'ordre compte -- (A, B) et (B, A) sont tous deux renvoyés. Pour n éléments en choisissant r, combinations produit n! / (r!(n-r)!) résultats tandis que permutations produit n! / (n-r)! résultats.

Comment aplatir une liste imbriquée avec itertools ?

Utilisez itertools.chain.from_iterable() pour aplatir un niveau d'imbrication. Par exemple, list(chain.from_iterable([[1,2],[3,4],[5,6]])) renvoie [1, 2, 3, 4, 5, 6]. Pour les structures profondément imbriquées, vous avez besoin d'une approche récursive car chain.from_iterable ne supprime qu'un seul niveau.

Pourquoi itertools.groupby nécessite-t-il une entrée triée ?

groupby() groupe les éléments consécutifs partageant la même clé. Il ne parcourt pas l'intégralité de l'itérable pour trouver tous les éléments correspondants. Si vos données contiennent [A, A, B, A], groupby produit trois groupes : A, B, A. Pour obtenir un seul groupe par clé, triez les données par la fonction clé avant de les passer à groupby.

itertools est-il plus rapide que les boucles Python classiques ?

Oui. Les fonctions d'itertools sont implémentées en C dans la bibliothèque standard de CPython, ce qui les rend plus rapides que les boucles Python équivalentes écrites à la main. Elles utilisent également l'évaluation paresseuse (produisant un élément à la fois), ce qui réduit la consommation mémoire. Pour les grands jeux de données, la combinaison de l'exécution en C et de l'absence de listes intermédiaires peut apporter des améliorations significatives de performance.

Conclusion

Le module itertools de Python remplace des dizaines de schémas de boucles courants par des appels de fonctions uniques qui sont plus rapides, plus lisibles et plus efficaces en mémoire. Les itérateurs infinis (count, cycle, repeat) gèrent les séquences sans fin naturelle. Les itérateurs finis (chain, islice, groupby, accumulate, compress, takewhile, dropwhile) couvrent le filtrage, le découpage et l'agrégation. Les fonctions combinatoires (product, permutations, combinations, combinations_with_replacement) éliminent les boucles imbriquées pour la recherche exhaustive.

Le principe clé est la composition. Comme chaque fonction d'itertools renvoie un itérateur, vous pouvez diriger la sortie de l'une directement vers une autre sans stockage intermédiaire. Cela permet de traiter des jeux de données qui ne tiennent pas en mémoire, un élément à la fois, avec un code propre et déclaratif.

📚