
itertools et functools sont deux modules de la bibliothèque standard qui outillent la programmation fonctionnelle en Python : composer des itérateurs paresseux et transformer des fonctions. Le premier fournit des briques pour chainer, grouper, combiner ou accumuler des séquences sans écrire de boucle verbeuse. Le second apporte la mémoïsation (lru_cache), l'application partielle (partial), la surcharge par type (singledispatch) et de quoi écrire des décorateurs propres (wraps). Ce guide montre quand chaque outil rend le code plus lisible et plus rapide, avec des exemples que vous pouvez rejouer.
Il s'adresse aux développeurs qui connaissent déjà les fonctions, les générateurs et les décorateurs, et qui veulent remplacer des boucles maison par des outils testés et optimisés en C. Les exemples ont été exécutés avec Python 3.12.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Chainer et découper des itérateurs avec
chainetislice. - Grouper, combiner et accumuler avec
groupby,productetaccumulate. - Réduire une séquence à une valeur avec
reduce. - Figer des arguments avec
partialet mémoïser aveclru_cache. - Écrire des décorateurs propres avec
wrapset surcharger par type avecsingledispatch.
itertools : composer des itérateurs paresseux
Section intitulée « itertools : composer des itérateurs paresseux »Le module itertools renvoie des itérateurs paresseux : ils produisent leurs valeurs une par une, à la demande, sans construire la liste complète en mémoire. C'est le même principe que les générateurs, appliqué à des opérations courantes. Sur un flux de logs ou un gros CSV, cette paresse évite de saturer la RAM. Pour inspecter le résultat dans les exemples, on le convertit en list(), mais en production on itère directement dessus.
Générer des flux avec count, cycle et repeat
Section intitulée « Générer des flux avec count, cycle et repeat »Ces trois fonctions produisent des itérateurs infinis ou répétitifs. count compte à partir d'une valeur avec un pas, cycle répète une séquence en boucle, repeat renvoie toujours la même valeur. Comme count et cycle ne s'arrêtent jamais, on les borne toujours avec islice ou une condition, sinon la boucle tourne à l'infini.
from itertools import count, cycle, repeat, islice
compteur = count(start=10, step=5)print(list(islice(compteur, 4))) # borne à 4 valeurs
couleurs = cycle(["rouge", "vert", "bleu"])print([next(couleurs) for _ in range(5)])
print(list(repeat("ok", 3)))[10, 15, 20, 25]['rouge', 'vert', 'bleu', 'rouge', 'vert']['ok', 'ok', 'ok']count sert par exemple à numéroter des éléments, cycle à distribuer un travail entre serveurs à tour de rôle (round-robin), et repeat à fournir une valeur constante à map ou zip.
Concaténer et découper avec chain et islice
Section intitulée « Concaténer et découper avec chain et islice »chain enchaîne plusieurs itérables comme s'ils n'en formaient qu'un, sans créer de liste intermédiaire. islice découpe un itérateur à la manière du slicing de liste, mais il fonctionne sur n'importe quel itérable, y compris infini ou non indexable. C'est le bon réflexe pour parcourir les 100 premières lignes d'un fichier sans tout charger.
from itertools import chain, islice
frontend = ["react", "vue"]backend = ["django", "fastapi"]print(list(chain(frontend, backend)))
# les 4 premiers carrés, sans matérialiser la liste infiniecarres = (n * n for n in count(1))print(list(islice(carres, 4)))['react', 'vue', 'django', 'fastapi'][1, 4, 9, 16]Regrouper des données avec groupby
Section intitulée « Regrouper des données avec groupby »groupby regroupe les éléments consécutifs qui partagent une même clé. Le piège classique : il ne groupe que les éléments adjacents, donc les données doivent être triées sur la clé au préalable, exactement comme la commande Unix sort | uniq. Sans tri, un même groupe apparaîtra plusieurs fois.
from itertools import groupby
serveurs = [ {"role": "web", "nom": "web-01"}, {"role": "web", "nom": "web-02"}, {"role": "db", "nom": "db-01"}, {"role": "cache", "nom": "cache-01"},]
serveurs.sort(key=lambda s: s["role"]) # indispensable avant groupbyfor role, groupe in groupby(serveurs, key=lambda s: s["role"]): noms = [s["nom"] for s in groupe] print(role, noms)cache ['cache-01']db ['db-01']web ['web-01', 'web-02']Notez que groupe est lui-même un itérateur paresseux : on le consomme dans la foulée avec une compréhension. Si vous devez le réutiliser, convertissez-le en liste immédiatement, car il se vide dès la première itération.
Combiner avec product, permutations et combinations
Section intitulée « Combiner avec product, permutations et combinations »Ces trois fonctions génèrent des combinaisons sans boucles imbriquées. product calcule le produit cartésien (toutes les paires possibles entre plusieurs ensembles), permutations produit les arrangements ordonnés, et combinations les groupes sans ordre ni répétition. C'est idéal pour construire une matrice de tests ou explorer un espace de paramètres.
from itertools import product, permutations, combinations
environnements = ["dev", "prod"]regions = ["eu-west-2", "cloudgouv-eu-west-1"]for env, region in product(environnements, regions): print(env, region)
print(list(permutations([1, 2, 3], 2)))print(list(combinations(["alice", "bob", "carol"], 2)))dev eu-west-2dev cloudgouv-eu-west-1prod eu-west-2prod cloudgouv-eu-west-1[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)][('alice', 'bob'), ('alice', 'carol'), ('bob', 'carol')]product remplace avantageusement deux for imbriqués. La distinction utile : permutations tient compte de l'ordre ((1, 2) diffère de (2, 1)), alors que combinations ignore l'ordre et ne retient chaque groupe qu'une fois.
Cumuler avec accumulate et aligner avec zip_longest
Section intitulée « Cumuler avec accumulate et aligner avec zip_longest »accumulate produit des totaux courants (somme cumulée par défaut, ou toute autre opération binaire). zip_longest assemble plusieurs séquences de longueurs différentes en comblant les trous avec une valeur de remplissage, là où le zip natif s'arrête au plus court et perd les éléments en trop.
from itertools import accumulate, zip_longest
ventes = [100, 250, 75, 300]print(list(accumulate(ventes))) # somme cumuléeprint(list(accumulate(ventes, max))) # maximum courant
noms = ["Alice", "Bob", "Carol"]scores = [15, 12]print(list(zip_longest(noms, scores, fillvalue=0)))[100, 350, 425, 725][100, 250, 250, 300][('Alice', 15), ('Bob', 12), ('Carol', 0)]accumulate sert à tracer une courbe cumulée de chiffre d'affaires ou un maximum glissant. zip_longest évite le bug silencieux du zip classique quand vos deux listes n'ont pas exactement la même taille.
functools : transformer et optimiser des fonctions
Section intitulée « functools : transformer et optimiser des fonctions »Là où itertools agit sur des données, functools agit sur les fonctions elles-mêmes. Il permet de les spécialiser, de mettre en cache leurs résultats, de préserver leurs métadonnées quand on les décore et de les surcharger selon le type reçu. Ces outils rendent le code plus déclaratif et suppriment beaucoup de code répétitif.
Réduire une séquence avec reduce
Section intitulée « Réduire une séquence avec reduce »reduce replie une séquence en une seule valeur en appliquant une fonction à deux arguments, de proche en proche. Beaucoup de cas se traitent plus lisiblement avec sum, max ou une compréhension : réservez reduce aux agrégations personnalisées qui n'ont pas d'équivalent natif, comme un produit ou une fusion de dictionnaires.
from functools import reduce
nombres = [2, 3, 4, 5]produit = reduce(lambda acc, x: acc * x, nombres)print(produit) # 2 * 3 * 4 * 5120L'accumulateur acc transporte le résultat partiel à chaque étape. Si la lisibilité souffre, une simple boucle for reste souvent préférable : reduce brille surtout quand l'opération d'agrégation est claire et associative.
Figer des arguments avec partial
Section intitulée « Figer des arguments avec partial »partial crée une nouvelle fonction en pré-remplissant certains arguments d'une fonction existante. On évite ainsi de réécrire des lambdas ou de répéter les mêmes paramètres. C'est très pratique pour spécialiser une fonction de configuration ou fournir un callback déjà paramétré.
from functools import partial
def connexion(hote, port, timeout): return f"{hote}:{port} (timeout={timeout}s)"
connexion_locale = partial(connexion, "localhost", timeout=5)print(connexion_locale(5432))print(connexion_locale(6379))localhost:5432 (timeout=5s)localhost:6379 (timeout=5s)La fonction connexion_locale a figé l'hôte et le timeout ; il ne reste qu'à passer le port. Le code appelant devient plus court et exprime mieux son intention que de répéter "localhost" partout.
Mémoïser avec lru_cache et cache
Section intitulée « Mémoïser avec lru_cache et cache »lru_cache mémorise les résultats d'une fonction en fonction de ses arguments : au second appel avec les mêmes entrées, le résultat est renvoyé instantanément sans recalcul. Cette mémoïsation transforme des algorithmes récursifs exponentiels en calculs quasi immédiats. Depuis Python 3.9, cache est un raccourci équivalent à lru_cache(maxsize=None).
from functools import lru_cache
@lru_cache(maxsize=None)def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(30))print(fibonacci.cache_info())832040CacheInfo(hits=28, misses=31, maxsize=None, currsize=31)Le gain est spectaculaire. Sur un calcul de fibonacci(32) mesuré en local, la version sans cache prend 147 ms contre 0,02 ms avec lru_cache, soit un facteur de plusieurs milliers. La méthode cache_info() donne le nombre de hits (résultats servis depuis le cache) et de misses (vrais calculs), utile pour vérifier l'efficacité.
Préserver les métadonnées avec wraps
Section intitulée « Préserver les métadonnées avec wraps »Quand on écrit un décorateur, la fonction interne remplace l'originale et masque son nom et sa docstring. wraps recopie ces métadonnées sur l'enveloppe, ce qui garde un code débogable et une documentation intacte. C'est un réflexe obligatoire sur tout décorateur sérieux, comme le rappelle le guide sur les décorateurs.
from functools import wraps
def chronometre(fonction): @wraps(fonction) def enveloppe(*args, **kwargs): return fonction(*args, **kwargs) return enveloppe
@chronometredef calculer(x): "Calcule le carré de x." return x * x
print(calculer.__name__)print(calculer.__doc__)calculerCalcule le carré de x.Sans @wraps, calculer.__name__ afficherait enveloppe et la docstring serait None. Ce détail casse les outils qui s'appuient sur l'introspection (générateurs de doc, débogueurs, frameworks de test).
Calculer une propriété une seule fois avec cached_property
Section intitulée « Calculer une propriété une seule fois avec cached_property »cached_property transforme une méthode en attribut calculé à la demande, puis mémorisé sur l'instance. Le premier accès déclenche le calcul, les suivants renvoient la valeur stockée sans recalcul. C'est parfait pour une valeur coûteuse mais stable sur la durée de vie de l'objet, comme un total agrégé ou un résultat de requête.
from functools import cached_property
class Rapport: def __init__(self, lignes): self.lignes = lignes
@cached_property def total(self): print(" (calcul du total...)") return sum(self.lignes)
r = Rapport([10, 20, 30])print(r.total) # calculeprint(r.total) # sert le cache (calcul du total...)6060Le message de calcul n'apparaît qu'une fois : le second accès lit la valeur mémorisée. Contrairement à lru_cache, le cache vit sur l'instance et disparaît avec elle, ce qui évite les fuites mémoire à l'échelle du processus.
Surcharger par type avec singledispatch
Section intitulée « Surcharger par type avec singledispatch »singledispatch implémente la surcharge de fonction selon le type du premier argument. Plutôt qu'une longue cascade de if isinstance(...), on enregistre une implémentation par type. Le code reste ouvert à l'extension : ajouter un nouveau type se fait en enregistrant une variante, sans toucher aux autres.
from functools import singledispatch
@singledispatchdef formater(valeur): return f"inconnu: {valeur}"
@formater.registerdef _(valeur: int): return f"entier: {valeur:,}"
@formater.registerdef _(valeur: list): return f"liste de {len(valeur)} éléments"
@formater.registerdef _(valeur: str): return f"texte: {valeur.upper()}"
print(formater(1500000))print(formater(["a", "b", "c"]))print(formater("bonjour"))print(formater(3.14)) # aucun type enregistréentier: 1,500,000liste de 3 élémentstexte: BONJOURinconnu: 3.14La fonction décorée par @singledispatch sert de cas par défaut, appelé quand aucun type enregistré ne correspond (ici le float). Chaque variante se déclare avec l'annotation de type sur son paramètre, ce qui rejoint la logique des type hints.
Quand ces outils rendent-ils le code meilleur
Section intitulée « Quand ces outils rendent-ils le code meilleur »Ces deux modules ne sont pas des gadgets : ils remplacent du code maison par des briques testées et optimisées en C. Utilisez itertools dès qu'une boucle manipule des séquences de façon standard (chaîner, grouper, combiner) ou quand la mémoire compte, grâce à l'évaluation paresseuse. Utilisez functools pour accélérer (mémoïsation), spécialiser (partial, singledispatch) ou fiabiliser vos décorateurs (wraps).
Le bon critère reste la lisibilité. Si reduce ou une combinaison alambiquée d'itertools devient plus obscure qu'une boucle explicite, gardez la boucle. Ces outils gagnent quand ils expriment une intention claire en moins de lignes, pas quand ils compressent le code au prix de la compréhension.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À retenir
Section intitulée « À retenir »itertoolscompose des itérateurs paresseux : peu de mémoire, pas de liste intermédiaire.chainenchaîne des itérables,isliceles découpe même quand ils sont infinis.groupbyregroupe des éléments adjacents : trier sur la clé avant est obligatoire.product,permutationsetcombinationsremplacent les boucles imbriquées.accumulateproduit des totaux courants ;zip_longestaligne des séquences de tailles différentes.reducereplie une séquence en une valeur, à réserver aux agrégations sans équivalent natif.partialfige des arguments ;lru_cacheetcachemémoïsent les fonctions pures.wrapspréserve nom et docstring d'une fonction décorée ;cached_propertycalcule une fois par instance ;singledispatchsurcharge par type.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Les annotations de type : annoter les types, socle de singledispatch et d'un code lisible.