Apprenez en lisant dans l'ordre

itertools et functools — Boîtes à outils d'itération et de composition de fonctions

Recettes d'itération combinations / product / chain / accumulate, gel d'argument avec partial, et accélération d'une récursion via la mémoïsation @lru_cache.

itertools est un ensemble de fonctions qui construisent de nouveaux itérables à partir d'itérables existants (combinaisons, concaténation, accumulation), et functools est un ensemble de fonctions qui transforment la fonction elle-même (liaison d'arguments, mémoïsation). Les deux peuvent remplacer les motifs boucle for + suivi d'état en une seule ligne, réduisant drastiquement ton code.

Itérables — valeurs sur lesquelles tu peux boucler

La plupart des fonctions itertools prennent un "itérable" en entrée et retournent un nouvel itérable, donc il vaut la peine de fixer d'abord ce qu'est un itérable. Un itérable (la chose que tu peux écrire à droite de for x in ___:) est tout ce que tu peux passer à une boucle for, à list(...), à sum(...) ou à map(...). list / tuple / str / dict / set / range, plus les générateurs que tu écris toi-même (fonctions qui utilisent yield), se comportent tous comme des itérables en Python.

Itérables courants
list[1, 2, 3]tuple(1, 2, 3)str"abc"dict{"a": 1}set{1, 2, 3}rangerange(5)
Ce que tu peux mettre après for x in est un itérable. Les types intégrés comme list / tuple / str / dict / set / range sont des itérables, et les générateurs personnalisés construits avec yield aussi.

itertools — boîte à outils de combinaison et d'itération

itertools est un module qui rassemble des fonctions construisant de nouveaux itérables à partir d'itérables existants. Les quatre que tu utiliseras le plus sont combinations (combinaisons non ordonnées), product (produit cartésien sur plusieurs ensembles), chain (concaténer des itérables en un seul) et accumulate (totaux ou produits cumulés). Connaître ces quatre te permet de réécrire les doubles boucles for et les boucles à suivi d'état bien plus proprement.

Les quatre fonctions itertools principales
combinationsCombinaisons non ordonnéesproductToutes paires entre ensembleschainConcaténer des itérablesaccumulateSomme / produit cumulé
combinations pour les paires / triplets non ordonnés, product pour toutes les paires entre ensembles, chain pour concaténer des itérables, et accumulate pour conserver un état pendant le scan. Les quatre retournent des itérateurs, donc enveloppe avec list(...) pour matérialiser.
FonctionEntrée → SortieExemple
combinations(it, k)Combinaisons à k éléments (non ordonnées)[A,B,C] → (A,B), (A,C), (B,C)
permutations(it, k)Permutations à k éléments (ordonnées)[A,B] → (A,B), (B,A)
product(*its)Produit cartésien entre ensembles[S,M] × [red,blue] → 4 combinaisons
chain(*its)Concaténer plusieurs itérables[1,2] + [3,4] → [1,2,3,4]
accumulate(it)Accumuler depuis la gauche (somme par défaut)[10,20,30] → [10, 30, 60]

Liste les paires de deux boissons choisies parmi trois options et toutes les SKUs taille × couleur. combinations donne les paires non ordonnées, product donne le produit cartésien entre ensembles.

① Importe combinations et product depuis itertools

② À partir des boissons ["coffee", "tea", "juice"], construis des paires de 2 avec combinations, matérialise et affiche sous la forme Paires : ◯

③ Construis les toutes-combinaisons de tailles ["S", "M", "L"] × couleurs ["red", "blue"] avec product, matérialise et affiche sous la forme Tous les SKUs : ◯

(Si ton code s'exécute correctement, l'explication apparaîtra.)

Éditeur Python

Exécuter le code pour voir le résultat

Concatène les données de vente de deux jours et calcule le total cumulé. chain assemble plusieurs itérables en un seul et accumulate scanne depuis la gauche en portant une valeur cumulée.

① Importe chain et accumulate depuis itertools

② Concatène les ventes du jour 1 [120, 80] et du jour 2 [200, 150, 90] avec chain, matérialise et affiche sous la forme Toutes les ventes : ◯

③ Applique accumulate au résultat concaténé, matérialise et affiche sous la forme Total cumulé : ◯

Éditeur Python

Exécuter le code pour voir le résultat

functools.partial — lier des arguments à l'avance

functools.partial construit une nouvelle fonction avec certains arguments déjà verrouillés. Quand tu dois passer une fonction avec des arguments liés à un callback ou à une fonction d'ordre supérieur, ça t'évite d'écrire des fonctions wrapper d'une ligne comme def wrapper(...): .... Partout où tu appelles la même fonction de façon répétée avec de légères variations d'arguments, partial coupe le bruit du wrapper et rend le code plus lisible.

from functools import partial

def format_with_unit(price, unit):
    return f"{price}{unit}"

# Construit une nouvelle fonction avec l'unité "USD" pré-liée
to_usd = partial(format_with_unit, unit="USD")

# Applique à des prix individuels — seul price doit être passé maintenant
print(to_usd(100))   # 100USD
print(to_usd(200))   # 200USD
print(to_usd(300))   # 300USD
Comment partial fonctionne
power(base, exp)Retourne base ** exppartial(power, exp=2)Verrouille exp=2square(3) → 9square(5) → 25
partial(function, arg=value) retourne une nouvelle fonction avec certains arguments verrouillés. Verrouille exp=2 sur power(base, exp) et tu obtiens une fonction square qui n'a besoin que de base.

Utilise partial pour verrouiller exp sur power(base, exp) et dériver des fonctions dédiées square / cube.

① Importe partial depuis functools

② Définis def power(base, exp): qui retourne `base ** exp

③ Avec partial, construis une fonction `square` avec `exp=2` verrouillé. Appelle square(3) et square(5) et affiche sous la forme 3 au carré : ◯ / 5 au carré : ◯

④ Avec partial, construis une fonction `cube` avec `exp=3` verrouillé. Appelle cube(2) et affiche sous la forme 2 au cube : ◯

Éditeur Python

Exécuter le code pour voir le résultat

functools.lru_cache — mettre en cache les résultats avec mémoïsation

"Je veux réutiliser le résultat d'une fonction coûteuse appelée de façon répétée avec les mêmes arguments" — ça apparaît dès que les sorties d'une fonction sont déterminées par ses entrées et que la vitesse compte. Le faire à la main signifie gérer ton propre dict de cache, mais @lru_cache le fait en une seule ligne de décorateur.

@lru_cache est un décorateur qui met en cache les valeurs retournées d'une fonction. Quand on l'appelle à nouveau avec les mêmes arguments, il retourne directement la valeur en cache, en sautant le recalcul. LRU (Least Recently Used — supprime les entrées les moins récemment utilisées) élimine les vieilles entrées de cache pour que tu puisses limiter l'usage mémoire avec quelque chose comme maxsize=128.

Comment lru_cache fonctionne
fib(30) première foisExécute le corpsMet en cache+ retourne la valeurfib(30) seconde foisLit depuis le cache→ Retour instantané
Le premier appel rate le cache, donc le corps de la fonction s'exécute et le résultat est stocké comme (args → valeur retournée). Les appels suivants avec les mêmes args retournent immédiatement depuis le cache — le corps ne s'exécute jamais.

Ne l'ajoute pas aux fonctions à effets de bord

@lru_cache suppose que "mêmes arguments → même valeur retournée". Applique-le à des fonctions avec des effets de bord (lecture de fichiers / écriture en BD / retour de l'heure courante) ou dont le résultat varie pour les mêmes entrées, et tu auras un bug où le premier résultat reviendra pour toujours. Applique-le seulement aux fonctions pures (même entrée → même sortie, pas d'effet de bord).

Accélère un Fibonacci récursif avec @lru_cache. Même à fib(30) le nombre d'appels est énorme sans cache, mais avec le décorateur c'est instantané.

① Importe lru_cache depuis functools

② Définis une fonction fib(n) décorée par @lru_cache(maxsize=128) (retourne n si n < 2, sinon retourne fib(n-1) + fib(n-2))

③ Affiche fib(30) sous la forme fib(30) : ◯

④ Affiche fib(50) sous la forme fib(50) : ◯ (sans cache, ça ne se terminerait pas dans un temps raisonnable)

⑤ Affiche maxsize : ◯ en utilisant fib.cache_info().maxsize

Éditeur Python

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Quelle est la façon la plus concise de construire le produit cartésien de [1, 2, 3] et [A, B] ?

Question 2Que retourne partial(f, x=10) ?

Question 3Pourquoi @lru_cache accélère-t-il un Fibonacci récursif naïf ?