
Un générateur, c'est une fonction qui produit des valeurs une par une au lieu de tout calculer d'un coup. Résultat concret : vous pouvez parcourir un fichier de logs de 10 Go avec seulement quelques kilo-octets de RAM. Le mot-clé yield rend ça possible en mettant la fonction en pause entre chaque valeur. Ce guide vous accompagne pas à pas, en partant de ce que vous connaissez déjà (les listes et les boucles for) jusqu'aux cas réels.
Prérequis : savoir écrire une fonction Python avec return (voir le guide sur les fonctions).
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Créer une fonction génératrice avec
yieldet comprendre le mécanisme de pause. - Mesurer le gain mémoire d'un générateur face à une liste.
- Écrire des expressions génératrices et les chaîner en pipeline.
- Déléguer l'itération avec
yield from, y compris en récursion. - Appliquer les générateurs à des cas réels : gros fichiers, flux infini, pagination.
- Aller plus loin avec
send(),close()et les context managers.
Partons de ce que vous connaissez : les listes
Section intitulée « Partons de ce que vous connaissez : les listes »Vous savez déjà créer une liste et boucler dessus :
nombres = [0, 1, 2, 3, 4]
for n in nombres: print(n)La boucle for parcourt la liste élément par élément. Ça fonctionne très bien pour 5 éléments. Mais que se passe-t-il si vous en avez un million ?
On peut créer une liste en une seule ligne grâce à une compréhension de liste (list comprehension). La syntaxe [expression for variable in itérable] applique l'expression à chaque élément et range le résultat dans une liste :
# Forme longue (boucle classique)carres = []for x in range(5): carres.append(x ** 2)# carres = [0, 1, 4, 9, 16]
# Forme courte (compréhension de liste), même résultatcarres = [x ** 2 for x in range(5)]# carres = [0, 1, 4, 9, 16]C'est une écriture compacte très courante en Python (voir le guide sur les listes pour plus de détails). Maintenant, que se passe-t-il si on l'utilise avec un million d'éléments ?
carres = [x ** 2 for x in range(1_000_000)]Python construit toute la liste en mémoire avant que la boucle for ne commence. Mesurons la taille avec sys.getsizeof(), une fonction du module sys qui renvoie la taille en octets d'un objet en mémoire :
import sys
carres_liste = [x ** 2 for x in range(1_000_000)]print(sys.getsizeof(carres_liste))# 8448728 → environ 8 Mo en RAMPour un million d'éléments, 8 Mo ça passe. Mais pour 100 millions ? 800 Mo. Pour un milliard ? 8 Go. Et si vous lisez un fichier de logs de 10 Go avec readlines(), c'est 10 Go de RAM consommés d'un coup.
La question est : est-ce qu'on a vraiment besoin de TOUS les éléments en même temps ? En général non : la boucle for ne traite qu'un seul élément à la fois. C'est exactement ce problème que les générateurs résolvent.
Votre première fonction génératrice
Section intitulée « Votre première fonction génératrice »yield : comme return, mais en pause
Section intitulée « yield : comme return, mais en pause »Voici deux fonctions qui font presque la même chose. La première utilise return, la seconde utilise yield :
def carres_liste(n): """Calcule tous les carrés et renvoie une liste.""" resultat = [] for i in range(n): resultat.append(i ** 2) return resultat # Renvoie TOUT d'un coup
# Utilisationfor carre in carres_liste(5): print(carre)# 0, 1, 4, 9, 16def carres_generateur(n): """Produit les carrés un par un.""" for i in range(n): yield i ** 2 # Produit UNE valeur, puis se met en pause
# Utilisation (identique !)for carre in carres_generateur(5): print(carre)# 0, 1, 4, 9, 16Le résultat est le même. Mais la mécanique interne est radicalement différente.
La preuve par la mémoire
Section intitulée « La preuve par la mémoire »Mesurons la mémoire occupée par chacun avec sys.getsizeof() (qui renvoie la taille en octets). On affiche le résultat avec une f-string, une chaîne préfixée par f qui permet d'insérer des variables entre accolades {} (voir le guide sur le formatage de chaînes pour approfondir) :
import sys
# La version liste (définie plus haut)resultat_liste = carres_liste(1_000_000)
# La version générateur (définie plus haut)resultat_gen = carres_generateur(1_000_000)
taille_liste = sys.getsizeof(resultat_liste)taille_gen = sys.getsizeof(resultat_gen)
print(f"Liste : {taille_liste} octets") # → 8448728 (~8 Mo)print(f"Générateur : {taille_gen} octets") # → 200 octetsLa liste pèse 8 Mo, le générateur pèse 200 octets. C'est plus de 40 000 fois moins. Et cette taille reste constante, que vous génériez 1 000 ou 1 milliard d'éléments.
Comprendre le mécanisme pas à pas
Section intitulée « Comprendre le mécanisme pas à pas »Étape 1 : L'appel ne lance rien
Section intitulée « Étape 1 : L'appel ne lance rien »def compteur(maximum): print("--- DÉMARRAGE ---") n = 0 while n < maximum: print(f" Je vais produire {n}") yield n print(f" Je reprends après {n}") n += 1 print("--- FIN ---")
gen = compteur(3)print(type(gen))# <class 'generator'># (Aucun print de la fonction n'apparaît !)Quand vous appelez compteur(3), Python ne lance pas le code. Il crée un objet générateur : une structure qui garde une référence vers la fonction et l'état de son exécution, initialement figé avant la première ligne. Le code ne commencera que quand vous demanderez la première valeur.
Étape 2 : next() lance l'exécution jusqu'au prochain yield
Section intitulée « Étape 2 : next() lance l'exécution jusqu'au prochain yield »valeur = next(gen)# --- DÉMARRAGE --- ← le code démarre enfin# Je vais produire 0 ← s'exécute normalement# ← PAUSE sur yieldprint(valeur)# 0 ← la valeur produite par yieldLa fonction s'exécute depuis le début jusqu'au premier yield. Elle renvoie la valeur 0 et se fige. Toutes ses variables (n = 0, maximum = 3) restent intactes en mémoire.
Étape 3 : next() reprend exactement au bon endroit
Section intitulée « Étape 3 : next() reprend exactement au bon endroit »Le deuxième next() ne redémarre pas la fonction du début : il reprend juste après le yield où l'exécution s'était figée. Observez l'ordre des messages : le Je reprends après 0 sort avant le Je vais produire 1. C'est cette reprise en place, avec toutes les variables locales conservées, qui distingue un générateur d'une fonction ordinaire.
valeur = next(gen)# Je reprends après 0 ← reprend APRÈS le yield précédent# Je vais produire 1 ← continue la boucle# ← PAUSE sur yieldprint(valeur)# 1Étape 4 : Le générateur s'épuise
Section intitulée « Étape 4 : Le générateur s'épuise »next(gen)# Je reprends après 1# Je vais produire 2# → renvoie 2
next(gen)# Je reprends après 2# --- FIN --- ← la boucle while est terminée# StopIteration ← EXCEPTION ! Plus de valeurs.Quand la fonction atteint sa fin (ou un return sans valeur), Python lève StopIteration. C'est le signal qui dit "c'est terminé, il n'y a plus rien à produire".
La boucle for fait tout ça automatiquement
Section intitulée « La boucle for fait tout ça automatiquement »Vous n'avez pas besoin d'appeler next() manuellement. La boucle for le fait et gère StopIteration pour vous :
for valeur in compteur(3): print(f"Reçu : {valeur}")C'est équivalent à ce code (que vous n'avez jamais besoin d'écrire) :
gen = compteur(3)while True: try: valeur = next(gen) except StopIteration: break print(f"Reçu : {valeur}")Comparaison return vs yield
Section intitulée « Comparaison return vs yield »| Critère | return | yield |
|---|---|---|
| Ce qui se passe | La fonction s'exécute entièrement, puis renvoie le résultat | La fonction se met en pause à chaque yield et renvoie une valeur |
| Mémoire | Tout le résultat est en mémoire (liste complète) | Une seule valeur à la fois (taille constante) |
| Ce que vous récupérez | La valeur directement (liste, dict, string...) | Un objet generator sur lequel vous itérez |
| Réutilisable ? | Oui, le résultat est une liste classique | Non, le générateur est à usage unique |
| Quand l'utiliser | Petit résultat, besoin d'index ou de len() | Gros volume, traitement séquentiel, flux infini |
Exemple concret : la suite de Fibonacci
Section intitulée « Exemple concret : la suite de Fibonacci »La suite de Fibonacci (0, 1, 1, 2, 3, 5, 8, 13, ...) est un cas d'école parfait : c'est une séquence potentiellement infinie où chaque valeur dépend des précédentes.
def fibonacci_liste(n): """Renvoie les n premiers nombres de Fibonacci.""" fib = [] a, b = 0, 1 for _ in range(n): fib.append(a) a, b = b, a + b return fib # Toute la liste d'un coup
print(fibonacci_liste(10))# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]def fibonacci(): """Produit la suite de Fibonacci à l'infini.""" a, b = 0, 1 while True: # Boucle infinie ! yield a # Produit le nombre courant a, b = b, a + b
# Prendre les 10 premiersfrom itertools import islicefor n in islice(fibonacci(), 10): print(n, end=" ")# 0 1 1 2 3 5 8 13 21 34La version liste doit savoir à l'avance combien d'éléments produire. Le générateur s'en fiche : il produit des valeurs à l'infini, et c'est l'appelant qui décide quand s'arrêter. Et quand il s'arrête, plus aucune mémoire n'est gaspillée.
Les expressions génératrices
Section intitulée « Les expressions génératrices »Vous connaissez les list comprehensions ([... for ... in ...]). Les expressions génératrices utilisent la même syntaxe, mais avec des parenthèses au lieu de crochets :
# List comprehension → construit toute la liste en mémoirecarres_liste = [x ** 2 for x in range(1_000_000)]
# Generator expression → produit les valeurs une par unecarres_gen = (x ** 2 for x in range(1_000_000))La seule différence visible, c'est [] vs (). Mais côté mémoire, c'est le jour et la nuit (rappelez-vous : 8 Mo vs 200 octets).
Chaîner des expressions génératrices (pipeline)
Section intitulée « Chaîner des expressions génératrices (pipeline) »L'intérêt principal des expressions génératrices, c'est qu'elles se chaînent pour former un pipeline de traitement :
# Pipeline : lire → nettoyer → filtrer → afficherwith open("/var/log/syslog") as f: lignes = (l.strip() for l in f) # 1. Nettoyer erreurs = (l for l in lignes if "ERROR" in l) # 2. Filtrer recentes = (l for l in erreurs if "2026-02" in l) # 3. Affiner
for erreur in recentes: # 4. Traiter print(erreur)Ici, aucune liste intermédiaire n'est créée. Le fichier est lu ligne par ligne, chaque ligne passe dans la chaîne, et seules les lignes qui correspondent sont affichées. Même un fichier de 50 Go est traité sans problème.
yield from : déléguer à un sous-générateur
Section intitulée « yield from : déléguer à un sous-générateur »Quand un générateur doit produire les valeurs d'un autre itérable, vous pouvez écrire une boucle for + yield. Mais yield from est plus direct :
def tous_les_nombres(): for n in range(3): yield n for n in range(10, 13): yield n
print(list(tous_les_nombres()))# [0, 1, 2, 10, 11, 12]def tous_les_nombres(): yield from range(3) # Délègue à range(3) yield from range(10, 13) # Puis à range(10, 13)
print(list(tous_les_nombres()))# [0, 1, 2, 10, 11, 12]yield from iterable dit : "produis toutes les valeurs de cet itérable, une par une, comme si je les avais yield moi-même". C'est du sucre syntaxique, mais ça rend le code plus lisible.
Cas pratique : parcourir une arborescence de fichiers
Section intitulée « Cas pratique : parcourir une arborescence de fichiers »from pathlib import Path
def parcourir(dossier): """Parcourt récursivement un dossier et produit chaque fichier.""" chemin = Path(dossier) for item in sorted(chemin.iterdir()): if item.is_file(): yield item elif item.is_dir(): yield from parcourir(item) # Récursion via yield from
# Utilisationfor fichier in parcourir("/etc/nginx"): print(fichier)Ici yield from parcourir(item) délègue la récursion au sous-dossier. Chaque fichier remonte directement à l'appelant, sans liste intermédiaire.
Cas pratiques
Section intitulée « Cas pratiques »Lire un fichier volumineux sans exploser la RAM
Section intitulée « Lire un fichier volumineux sans exploser la RAM »def lire_fichier(chemin, encodage="utf-8"): """Lit un fichier ligne par ligne sans tout charger en mémoire.""" with open(chemin, encoding=encodage) as f: for numero, ligne in enumerate(f, 1): yield numero, ligne.rstrip("\n")
# Chercher les erreurs dans un fichier de 10 Gofor num, ligne in lire_fichier("/var/log/syslog"): if "ERROR" in ligne: print(f"L{num}: {ligne}")Le fichier n'est jamais entièrement chargé. Chaque ligne est lue, traitée, puis oubliée. Vous pourriez scanner un fichier de 100 Go avec 10 Mo de RAM.
Pipeline de traitement de logs
Section intitulée « Pipeline de traitement de logs »Un pipeline de générateurs connecte plusieurs étapes de traitement, chacune consommant les valeurs produites par la précédente et transmettant les siennes à la suivante :
def lire_lignes(chemin): """Étape 1 : lire le fichier ligne par ligne.""" with open(chemin) as f: yield from (l.rstrip("\n") for l in f)
def filtrer(lignes, mot_cle): """Étape 2 : ne garder que les lignes contenant le mot-clé.""" for ligne in lignes: if mot_cle in ligne: yield ligne
def extraire_ip(lignes): """Étape 3 : extraire l'adresse IP de chaque ligne.""" import re pattern = re.compile(r"\d{1,3}(?:\.\d{1,3}){3}") for ligne in lignes: match = pattern.search(ligne) if match: yield match.group()
# Assembler le pipeline (rien ne s'exécute encore !)lignes = lire_lignes("/var/log/auth.log")echecs = filtrer(lignes, "Failed password")ips = extraire_ip(echecs)
# C'est seulement ici que tout se déclenche :from collections import Countertop_ips = Counter(ips).most_common(10)for ip, count in top_ips: print(f"{ip:>15} : {count} tentatives")Ce pipeline lit le fichier, filtre les erreurs d'authentification, et extrait les IP, tout ça ligne par ligne, sans jamais charger le fichier entier.
Surveiller un fichier en temps réel (tail -f)
Section intitulée « Surveiller un fichier en temps réel (tail -f) »import time
def tail_follow(chemin): """Suit un fichier comme la commande tail -f.""" with open(chemin) as f: f.seek(0, 2) # Se positionner à la fin du fichier while True: ligne = f.readline() if ligne: yield ligne.rstrip("\n") else: time.sleep(0.1) # Attendre de nouvelles lignes
# Surveiller les connexions SSH en temps réelfor ligne in tail_follow("/var/log/auth.log"): if "Accepted" in ligne: print(f"Connexion : {ligne}")Ce générateur ne s'arrête jamais : il attend les nouvelles lignes et les produit au fur et à mesure. C'est un flux infini, et seul un générateur peut le modéliser proprement.
Paginer des résultats
Section intitulée « Paginer des résultats »def paginer(iterable, taille=10): """Découpe un itérable en pages (lots) de N éléments.""" page = [] for item in iterable: page.append(item) if len(page) == taille: yield page page = [] if page: yield page # Dernière page (éventuellement incomplète)
# Déployer des serveurs par lots de 5serveurs = ["web-1", "web-2", "db-1", "db-2", "cache-1", "web-3", "db-3", "cache-2", "monitor-1", "lb-1"]
for i, lot in enumerate(paginer(serveurs, 5), 1): print(f"Lot {i} : {lot}")# Lot 1 : ['web-1', 'web-2', 'db-1', 'db-2', 'cache-1']# Lot 2 : ['web-3', 'db-3', 'cache-2', 'monitor-1', 'lb-1']yield et les context managers
Section intitulée « yield et les context managers »Vous pouvez utiliser yield pour créer des context managers (les blocs with) grâce au décorateur @contextmanager :
from contextlib import contextmanagerimport time
@contextmanagerdef chronometre(label): """Mesure le temps d'exécution d'un bloc de code.""" debut = time.perf_counter() print(f" {label} - début") yield # ← LE CODE DU BLOC with S'EXÉCUTE ICI duree = time.perf_counter() - debut print(f" {label} - {duree:.3f}s")
# Utilisationwith chronometre("Traitement des logs"): time.sleep(0.5) print(" Analyse en cours...")
# Traitement des logs - début# Analyse en cours...# Traitement des logs - 0.502sLe yield sépare le code d'entrée (avant) du code de sortie (après). Tout ce qui est dans le bloc with s'exécute "à la place" du yield.
yield dans Textual et les frameworks
Section intitulée « yield dans Textual et les frameworks »Si vous avez lu le guide sur Textual, vous avez vu yield dans la méthode compose() :
from textual.app import App, ComposeResultfrom textual.widgets import Header, Footer, Static
class MonApp(App): def compose(self) -> ComposeResult: yield Header() yield Static("Contenu principal") yield Footer()compose() est un générateur : Textual appelle next() dessus pour récupérer chaque widget un par un, dans l'ordre. Chaque yield correspond à un widget qui apparaît à l'écran. C'est plus lisible qu'un return [Header(), Static(...), Footer()] parce que les widgets sont déclarés dans l'ordre d'apparition, comme un plan de l'interface.
Piloter un générateur : send(), throw(), close()
Section intitulée « Piloter un générateur : send(), throw(), close() »send() : envoyer une valeur dans le générateur
Section intitulée « send() : envoyer une valeur dans le générateur »yield peut aussi recevoir une valeur depuis l'extérieur :
def accumulateur(): """Additionne les valeurs envoyées via send().""" total = 0 while True: valeur = yield total # ← produit total ET reçoit la prochaine valeur if valeur is None: break total += valeur
gen = accumulateur()next(gen) # Amorcer le générateur → produit 0gen.send(10) # → 10 (total = 0 + 10)gen.send(25) # → 35 (total = 10 + 25)gen.send(5) # → 40 (total = 35 + 5)Le yield joue un double rôle : il envoie total vers l'extérieur, et reçoit la valeur passée par send().
close() : arrêter proprement un générateur
Section intitulée « close() : arrêter proprement un générateur »def flux_infini(): n = 0 try: while True: yield n n += 1 except GeneratorExit: print("Générateur fermé proprement")
gen = flux_infini()print(next(gen)) # 0print(next(gen)) # 1gen.close() # Générateur fermé proprementclose() lève GeneratorExit à l'intérieur du générateur, ce qui permet d'exécuter du code de nettoyage dans un bloc try/except.
throw() : injecter une exception
Section intitulée « throw() : injecter une exception »throw() lève une exception à l'endroit exact où le générateur est en pause, c'est-à-dire sur la ligne yield. Le générateur peut l'attraper dans un try/except et poursuivre, ou la laisser remonter. Cette mécanique sert surtout à signaler une condition d'erreur à une coroutine sans l'interrompre brutalement.
def generateur_robust(): while True: try: valeur = yield print(f"Reçu : {valeur}") except ValueError as e: print(f"Erreur gérée : {e}")
gen = generateur_robust()next(gen) # Amorcergen.send("ok") # Reçu : okgen.throw(ValueError("donnée invalide")) # Erreur gérée : donnée invalidegen.send("reprise") # Reçu : repriseBonnes pratiques
Section intitulée « Bonnes pratiques »Quand utiliser un générateur
Section intitulée « Quand utiliser un générateur »Ce tableau tranche les cas d'usage réels. Lisez-le à l'envers pour éviter le piège le plus courant : un générateur ne se laisse ni indexer ni mesurer avec len(). Dès que votre code a besoin de resultat[42], de connaître le nombre d'éléments à l'avance, ou de reparcourir les données plusieurs fois, un return classique renvoyant une liste reste le bon choix.
| Situation | Générateur ? | Pourquoi |
|---|---|---|
| Fichier volumineux (> 100 Mo) | Oui | Évite l'explosion mémoire |
| Flux de données infini (logs en temps réel) | Oui | Impossible de tout stocker, le flux ne s'arrête jamais |
| Pipeline de transformations (lire → filtrer → transformer) | Oui | Mémoire stable, code lisible et composable |
| Petit résultat (< 1 000 éléments) | Non | return est plus simple et le résultat est réutilisable |
Besoin d'accès par index (resultat[42]) | Non | Un générateur ne supporte pas l'indexation |
Besoin de len() sur le résultat | Non | len() ne fonctionne pas sur un générateur |
Erreurs courantes
Section intitulée « Erreurs courantes »Presque toutes les surprises avec les générateurs viennent de deux oublis : ne pas itérer, et croire qu'on peut reparcourir. Si votre print affiche <generator object ...>, vous avez oublié la boucle for ou le list(). Si la deuxième itération semble vide, c'est que le générateur était déjà épuisé. Gardez ces deux réflexes et la plupart des messages ci-dessous disparaissent.
| Erreur | Ce qui se passe | Comment corriger |
|---|---|---|
Le résultat est <generator object ...> au lieu des valeurs | Vous avez oublié d'itérer sur le générateur | Utilisez une boucle for, ou list(gen) |
| Le générateur est vide au 2e parcours | Un générateur s'épuise après un seul parcours | Recréez le générateur : gen = ma_fonction() |
StopIteration inattendue | next() appelé après épuisement | Utilisez une boucle for (elle gère ça automatiquement) |
TypeError: 'generator' object is not subscriptable | Tentative d'indexation gen[0] | Convertissez d'abord en liste : list(gen)[0] |
SyntaxError avec yield dans une lambda | yield n'est pas supporté dans les lambdas | Utilisez une fonction def classique |
Dépannage
Section intitulée « Dépannage »Ces symptômes reviennent régulièrement dès qu'un générateur touche à des fichiers ou à yield from. Le plus insidieux est le fichier « vide » : il vient presque toujours d'une itération tentée après la fermeture du bloc with, quand le fichier n'est plus ouvert. Partez de la colonne de gauche, identifiez le symptôme, puis appliquez la solution.
| Symptôme | Cause probable | Solution |
|---|---|---|
| Le générateur ne produit aucune valeur | Oubli d'itérer (boucle for ou next()) | for x in mon_generateur(): ... |
| Le fichier n'est pas lu | Le bloc with est fermé avant l'itération | Itérez à l'intérieur du with, pas après |
RuntimeError: generator raised StopIteration | StopIteration levée dans un yield from (Python 3.7+) | Utilisez return au lieu de raise StopIteration |
| Performance plus lente qu'une liste | Overhead des appels next() sur un petit volume | Pour les petites données, une liste est plus rapide, c'est normal |
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 »yieldmet la fonction en pause et produit une valeur. Au prochainnext(), elle reprend exactement là où elle s'était arrêtée, avec toutes ses variables locales conservées.- Un générateur ne stocke rien en mémoire : il calcule la prochaine valeur à la demande. C'est ce qu'on appelle l'évaluation paresseuse (lazy evaluation).
- Les expressions génératrices
(x for x in ...)sont la version compacte des fonctions génératrices. Elles se chaînent pour former des pipelines sans consommer de mémoire. yield fromdélègue l'itération à un autre itérable ou générateur, idéal pour les parcours récursifs.- Un générateur est à usage unique : après un parcours complet, il est épuisé. Il faut en recréer un.
- En pratique, utilisez la boucle
forpour consommer un générateur, elle gèrenext()etStopIterationautomatiquement. @contextmanager+yieldpermet de créer des context managers (with) en quelques lignes.- Textual, asyncio et d'autres frameworks s'appuient sur
yieldpour structurer l'exécution.
FAQ : générateurs et yield
Section intitulée « FAQ : générateurs et yield »yield transforme une fonction en générateur. Au lieu de renvoyer un résultat unique avec return, la fonction produit des valeurs une par une et se met en pause après chacune :def compteur(maximum):
n = 0
while n < maximum:
yield n # produit une valeur, puis pause
n += 1
for valeur in compteur(3):
print(valeur) # 0, 1, 2
Au next() suivant, la fonction reprend exactement où elle s'était arrêtée. Cela permet de traiter de gros volumes sans tout charger en mémoire.return |
yield |
|
|---|---|---|
| Exécution | tout d'un coup | en pause à chaque valeur |
| Mémoire | résultat complet stocké | une valeur à la fois |
| Récupère | une liste, un dict... | un objet générateur |
| Réutilisable | oui | non (usage unique) |
return convient à un petit résultat réutilisable ; yield à un gros volume ou un flux, traité séquentiellement sans saturer la RAM.# 1. fonction avec yield
def carres(n):
for i in range(n):
yield i ** 2
# 2. expression génératrice (parenthèses)
carres = (i ** 2 for i in range(n))
Il occupe une mémoire constante (200 octets pour un million d'éléments), se parcourt avec une boucle for, et s'épuise après un seul passage.yield from délègue la production à un autre itérable ou générateur :def tous_les_nombres():
yield from range(3) # produit 0, 1, 2
yield from range(10, 13) # puis 10, 11, 12
C'est équivalent à une boucle for ... yield, mais plus lisible. C'est surtout précieux pour les parcours récursifs :def parcourir(dossier):
for item in dossier.iterdir():
if item.is_dir():
yield from parcourir(item) # récursion
else:
yield item
# compréhension de liste : tout en mémoire
carres = [x ** 2 for x in range(1_000_000)] # ~8 Mo
# expression génératrice : une valeur à la fois
carres = (x ** 2 for x in range(1_000_000)) # ~200 octets
L'expression génératrice ne stocke rien et se chaîne en pipeline. Choisissez la liste si vous avez besoin d'index ou de len(), le générateur pour un gros volume traité séquentiellement.gen = carres(5)
list(gen) # [0, 1, 4, 9, 16]
list(gen) # [] ← vide, épuisé !
Deux solutions :- Recréer le générateur en rappelant la fonction :
gen = carres(5). - Si plusieurs parcours sont nécessaires, convertir en liste :
valeurs = list(carres(5)), au prix de la mémoire.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Construire une TUI avec Textual : découvrir comment yield structure l'interface dans compose().
- Gérer les exceptions Python : traiter StopIteration et GeneratorExit correctement.