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 StopIterationComme 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 7Projet 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 29Ce 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
foren 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.
