Historique de la question
Python, à partir de la version 2.4, a complété les expressions de liste (list comprehensions) par ce qu'on appelle des "expressions génératrices". Elles permettent de créer une séquence paresseuse de valeurs, similaire aux générateurs, mais sous une forme compacte et lisible.
Problème
Les expressions de liste ([x for x in iterable]) créent une liste, chargeant immédiatement tous les éléments en mémoire. Cela est inefficace, voire dangereux, si le nombre d'éléments est très élevé. Les fonctions génératrices (utilisant yield) sont plus flexibles, mais nécessitent une définition de fonction distincte et plus de lignes de code.
Solution
Les expressions génératrices ((x for x in iterable)) offrent une syntaxe concise pour produire des séquences paresseuses (les éléments sont calculés au besoin, et non chargés tous en même temps). Elles ressemblent aux expressions de liste, mais utilisent des parenthèses :
# Expression de liste charge tout en mémoire squares_list = [x**2 for x in range(10**6)] # Expression génératrice : les éléments arrivent à la demande, la mémoire est presque non utilisée squares_gen = (x**2 for x in range(10**6)) # Obtenir les cinq premières valeurs du générateur for _ in range(5): print(next(squares_gen))
Caractéristiques importantes :
Peut-on "parcourir" la même expression génératrice plusieurs fois ?
Non, après avoir itéré une fois, le générateur est "épuisé". Pour le parcourir à nouveau, il faut créer un nouveau générateur ou utiliser une expression de liste.
it = (x for x in range(3)) print(list(it)) # [0,1,2] print(list(it)) # [] — plus aucune valeur ne peut être récupérée
Les générateurs conservent-ils l'état entre les utilisations ?
Oui, l'expression génératrice conserve la "position" entre les appels de next() (ou lors d'une nouvelle itération), mais ne peut pas être réinitialisée au début, à moins que vous ne créiez un nouvel objet.
Peut-on utiliser une expression génératrice plusieurs fois dans une seule ligne ?
Non ! Si vous "déballer" le générateur dans plusieurs endroits en même temps (par exemple, dans plusieurs fonctions simultanément, sans le renvoyer dans une liste), une partie des données est perdue — chaque utilisation enfant déplace le pointeur vers l'avant.
g = (x for x in range(3)) print(sum(g), list(g)) # sum(g) obtiendra tout, list(g) restera vide
Dans un projet d'analyse de fichiers volumineux, on a utilisé :
data = (parse_line(line) for line in file) process(list(data)) other_process(list(data))
Avantages :
Inconvénients :
On a utilisé une expression de liste, si une réutilisation des données est nécessaire, ou on a créé un générateur pour une consommation unique :
# Générateur uniquement pour une analyse unique (par exemple, pour calculer la somme) total = sum(parse_line(line) for line in file)
Avantages :
Inconvénients :