Un générateur est un objet qui se comporte comme un itérateur, mais il ne stocke pas tous ses éléments en mémoire en même temps, en les générant à la volée, un par un, lorsque cela est nécessaire.
Un générateur permet d'optimiser l'utilisation de la mémoire tout en gérant des séquences de données potentiellement infinies.
Créer un générateur
Il existe deux manières principales pour créer un générateur.
Utiliser yield dans une fonction
Une fonction qui contient le mot-clé yield devient une "fonction génératrice". L'appel à cette fonction ne l'exécute pas mais renvoie un générateur qui produira les valeurs sur demande :
def simple_generateur(n): # Ce code ne s'exécute que lorsque l'on commence à itérer sur le générateur. print("-> Début de l'exécution du code du générateur.") for i in range(n): # La fonction se met en pause ici et fournit la valeur à l'appelant. yield i # 1. On crée l'objet générateur. gen = simple_generateur(3) print("Objet générateur créé.") print("\nDébut de la boucle for (ce qui va 'démarrer' le générateur) :") # 2. La boucle for appelle next() pour la première fois, # ce qui exécute le code du générateur jusqu'au premier yield. for nombre in gen: print(f"Valeur obtenue : {nombre}")
Utiliser une expression génératrice
Une expression génératrice ressemble à une compréhension de liste, mais utilise des parenthèses au lieu des crochets. Une manière concise de créer des générateurs simples :
# Compréhension de liste : crée une liste complète en mémoire ma_liste = [x * x for x in range(5)] # Résultat : [0, 1, 4, 9, 16] # Expression de générateur : crée un objet générateur sans calculer les valeurs mon_generateur_expr = (x * x for x in range(5)) # Résultat : <generator object <genexpr> at 0x...> # On peut le consommer avec une boucle for print(f"\nUtilisation de l'expression générateur :") for valeur in mon_generateur_expr: print(valeur)
Les générateurs permettent :
-
Une évaluation "paresseuse" : les valeurs sont calculées uniquement au moment où on les demande.
-
Une meilleure efficacité en mémoire : permet de travailler avec des grands ensembles de données (une seule valeur est en mémoire à la fois).
-
Itération unique : un générateur ne peut être parcouru qu'une seule fois. Une fois consommé, il est "consommé". Le parcourir de nouveau ne passera par à travers le générateur une seconde fois.
mon_generateur_expr = (x * x for x in range(5)) # Résultat : <generator object <genexpr> at 0x...> # On peut le consommer avec une boucle for print(f"\nUtilisation de l'expression générateur une première fois :") for valeur in mon_generateur_expr: print(valeur) print(f"\nDeuxième utilisation : rien n'est affiché car le générateur a déjà été 'consommé'") for valeur in mon_generateur_expr: print(valeur)