ProgrammationDéveloppeur Backend

Qu'est-ce que les expressions génératrices (generator expressions) en Python, en quoi diffèrent-elles des fonctions génératrices et des expressions de liste, et où leur utilisation est-elle la plus justifiée ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

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 :

  • Les expressions génératrices ne chargent pas toute la collection en mémoire en même temps.
  • Utilisées dans les endroits où un objet itérable est nécessaire (par exemple, dans sum(), max(), any()).
  • La syntaxe est compacte et ne nécessite pas de définition de fonction distincte.

Questions pièges.

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

Erreurs typiques et anti-patrons

  • Utiliser des expressions génératrices dans des cas où la collection est nécessaire dans son intégralité — cela conduit à un usage unique, après quoi les données sont perdues.
  • Passer un générateur "épuisé" au lieu d'un nouveau (vous obtiendrez une collection vide).

Exemple de la vie réelle

Cas négatif

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 :

  • Facile à modifier le code pour différents types de données.

Inconvénients :

  • Après le premier appel de list(data), le générateur est épuisé, les données vont uniquement au premier processeur, le deuxième ne reçoit rien.

Cas positif

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 :

  • Économie de mémoire, simplicité du code.

Inconvénients :

  • Les données ne peuvent pas être réutilisées sans recréer le générateur.