⚙️python

Les generateurs Python : yield et iterateurs paresseux

30 mars 2026 5 min de lecture

Introduction : Au-delà des listes classiques

En Python, tu connais déjà les listes, les tuples et les boucles for. Mais que se passe-t-il quand tu dois traiter une énorme quantité de données, comme lire un fichier de plusieurs gigaoctets ligne par ligne, ou générer une séquence infinie de nombres ? Créer une liste complète en mémoire n'est pas efficace, voire impossible. C'est là que les générateurs entrent en jeu. Ils sont un concept clé en NSI pour écrire du code élégant et performant.

Un générateur est une fonction spéciale qui te permet de générer une séquence de valeurs une par une, et à la demande. Au lieu de calculer et stocker toutes les valeurs d'un coup (comme une liste), il les produit au fil de l'eau, quand tu en as besoin. On parle d'évaluation paresseuse (lazy evaluation). Le mot-clé magique qui rend cela possible est yield.

Comprendre yield : la pause magique

La différence fondamentale entre une fonction normale et une fonction génératrice réside dans l'instruction return et l'instruction yield.

  • return : Termine l'exécution de la fonction et renvoie une valeur. La prochaine fois que tu appelleras la fonction, elle recommencera depuis le début.
  • yield : Met en pause l'exécution de la fonction et renvoie une valeur. Elle sauvegarde tout son état local. La prochaine fois que tu lui demanderas une valeur, elle reprendra exactement là où elle s'était arrêtée.

Voici ton premier générateur :

def generateur_simple():
print("Début" )
yield 1
print("Reprise" )
yield 2
print("Fin" )
yield 3

# Appeler la fonction ne l'exécute pas immédiatement !
mon_gen = generateur_simple()
print(type(mon_gen)) #

# On demande les valeurs une par une avec next()
print(next(mon_gen)) # Affiche "Début", puis 1
print(next(mon_gen)) # Affiche "Reprise", puis 2
print(next(mon_gen)) # Affiche "Fin", puis 3
# print(next(mon_gen)) # Lancerait une exception StopIteration

Comme tu peux le voir, les instructions print s'exécutent au moment où on appelle next(), pas avant. Le générateur est un itérateur : tu peux aussi l'utiliser directement dans une boucle for, qui gère automatiquement l'arrêt (StopIteration).

Les avantages majeurs : Mémoire et Performance

Imaginons que tu veuilles la liste des 10 premiers millions de nombres carrés.

# Version LISTE (coûteuse en mémoire)
carres_liste = [x**2 for x in range(10_000_000)] # Tout est calculé et stocké !

# Version GENERATEUR (économe)
carres_gen = (x**2 for x in range(10_000_000)) # Note les parenthèses !
# Rien n'est calculé pour l'instant. C'est une expression génératrice.

Avec la liste, Python doit allouer de la mémoire pour 10 millions d'entiers. Avec le générateur (ici sous forme d'expression génératrice), il ne garde en mémoire qu'un seul nombre à la fois et la logique pour passer au suivant. C'est un gain énorme !

Cas d'usage concrets en NSI

  • Lire de gros fichiers : with open('gros_fichier.txt') as f: for ligne in f: utilise en fait un générateur ! Le fichier n'est pas chargé en entier.
  • Générer des séquences infinies : Une suite mathématique, un flux de données (comme des lectures de capteur).
  • Pipelines de traitement : Tu peux enchaîner des générateurs pour filtrer et transformer des données étape par étape, sans listes intermédiaires.

Aller plus loin : Expressions génératrices et yield from

Tu as déjà vu l'expression génératrice avec des parenthèses. C'est un raccourci syntaxique très pratique pour créer des générateurs simples.

Pour les générateurs plus complexes, l'instruction yield from est ton amie. Elle te permet de déléguer la génération à un autre itérable ou générateur. C'est essentiel pour composer des générateurs.

def aplatir(iterable):
"""Génère tous les éléments d'une structure imbriquée."""
for element in iterable:
if isinstance(element, (list, tuple)):
yield from aplatir(element) # Délégation récursive !
else:
yield element

liste_imbriquee = [1, [2, 3], [4, [5, 6]], 7]
for n in aplatir(liste_imbriquee):
print(n, end=' ') # Affiche : 1 2 3 4 5 6 7

Projet pratique : Un générateur de nombres premiers

Appliquons cela dans un mini-projet typique NSI : un générateur de nombres premiers infinis (ou jusqu'à une limite).

def nombres_premiers(limite=None):
"""Génère les nombres premiers jusqu'à 'limite' (ou indéfiniment)."""
nombres_vus = []
candidat = 2
while limite is None or candidat <= limite:
est_premier = True
for premier in nombres_vus:
if premier * premier > candidat:
break
if candidat % premier == 0:
est_premier = False
break
if est_premier:
nombres_vus.append(candidat)
yield candidat # On livre le premier au moment où on le trouve
candidat += 1 if candidat == 2 else 2 # On teste seulement les impairs après 2

# Utilisation
print("Les 10 premiers nombres premiers :")
premiers_gen = nombres_premiers()
for i, premier in enumerate(premiers_gen):
if i >= 10:
break
print(premier, end=' ')
# Affiche : 2 3 5 7 11 13 17 19 23 29

Ce générateur est efficace car il ne stocke que la liste des premiers déjà trouvés et génère le résultat immédiatement, sans avoir besoin de pré-calculer toute la liste jusqu'à la limite.

Conclusion

Les générateurs Python, avec le mot-clé yield, sont des outils indispensables pour tout développeur qui se soucie de l'efficacité mémoire et de la clarté du code. Ils transforment une approche eager (impatiente) en une approche paresseuse, générant les données à la demande. Maîtriser ce concept te permettra de :

  • Traiter des flux de données massifs.
  • Créer des itérateurs personnalisés et élégants.
  • Comprendre en profondeur le fonctionnement des boucles for en Python.
  • Écrire du code plus performant et scalable pour tes projets NSI et au-delà.

N'hésite pas à expérimenter en créant tes propres générateurs pour des suites mathématiques, des parseurs de données simples ou des simulations.

📚 Pour aller plus loin

Questions fréquentes

Quelle est la différence entre un itérateur et un générateur ?

Un itérateur est un objet qui implémente les méthodes `__iter__()` et `__next__()`. Un générateur est un type particulier d'itérateur, créé soit par une fonction contenant `yield`, soit par une expression génératrice. Tous les générateurs sont des itérateurs, mais l'inverse n'est pas vrai. Les générateurs sont un moyen simple et rapide de créer des itérateurs.

Peut-on utiliser 'return' dans une fonction génératrice ?

Oui, mais un `return` dans une fonction génératrice signale la fin de l'itération et déclenche l'exception `StopIteration`. La valeur retournée par `return` n'est pas une valeur "générée" et n'est pas accessible via une boucle `for`. Elle est accessible comme la valeur de l'exception `StopIteration` (via `.value`), ce qui est utile dans des cas avancés de coroutines.

Les générateurs sont-ils plus lents que les listes ?

Pas nécessairement. Si tu dois parcourir TOUS les éléments une seule fois, un générateur peut être aussi rapide, voire plus rapide car il évite l'allocation mémoire initiale. Le vrai gain est sur l'utilisation de la mémoire. Par contre, si tu as besoin d'accéder aux éléments de manière aléatoire (par index) ou de parcourir la séquence plusieurs fois, une liste (qui stocke tout) sera plus appropriée et plus rapide pour ces accès.

Comment reprendre un générateur là où je me suis arrêté ?

Le générateur garde son état automatiquement ! Chaque appel à `next(générateur)` ou chaque étape d'une boucle `for` le fait reprendre exactement après le dernier `yield` exécuté. Tu n'as rien à faire de spécial. Par contre, si tu veux recommencer depuis le début, tu dois créer une nouvelle instance du générateur en rappelant la fonction génératrice.

Bravo ! Tu as lu cet article
Inscris-toi pour sauvegarder ta progression et gagner des XP
Creer mon compte
generateurs Pythonyield iterator
Pixel