Générateurs en Python

Ce billet date de plusieurs années, ses informations peuvent être devenues obsolètes.

Ce billet est le deuxième d'une série en quatre volets. Voir le volet 1, le volet 3 ou le volet 4.

Les générateurs sont une catégorie spéciale de fonction qui facilite l'écriture d'itérateurs.

Je vais me cantonner dans ce billet aux générateurs ou fonctions génératrices (les deux termes seront utilisés de manière interchangeable) dit simples au sens de la PEP 255.

Générateurs (ou fonctions génératrices)

En Python, une fonction est une fonction génératrice lorsque son corps contient le mot clé yield.

def frange(start, stop, increment):
    while start < stop:
        yield start
        start += increment

for n in frange(0, 4, 0.5):
    print(n)

Lorsqu'elle est invoquée :

  • ses arguments sont associés à son espace local, comme lors d'une invocation traditionnelle
  • son code n'est pas exécuté, à la place un objet de type générateur-itérateur est retourné
  • ce générateur-itérateur répond ensuite aux itérations et quand sa méthode .__next__() est invoquée, le corps de la fonction génératrice est exécuté jusqu'à ce qu'une instruction yield produise un résultat et la mette en pause
  • au .__next__() suivant, l'exécution de la fonction génératrice reprend à l’endroit de la pause jusqu'au yield suivant et ainsi de suite
  • le générateur-itérateur se termine quand une instruction return ou une exception StopIteration est rencontrée, après quoi il n'est plus utilisable (il faudra ré-invoquer la fonction génératrice pour en produire un nouveau)

À partir du fonctionnement décrit ci-dessus on peut donner différentes définitions d'un générateur :

  • c'est un type de fonction fabrique (factory) qui produit un générateur-itérateur (qui se conforme au protocole des itérateurs)
  • c'est un type de fonction qui produit une succession de résultats plutôt qu'un résultat unique
  • c'est un type de fonction ayant la subtilité de pouvoir se mettre en pause en retournant un résultat intermédiaire à son appelant tout en maintenant son état local, de façon à pouvoir être reprise plus tard exactement à l'endroit où elle s'était arrêtée

Les générateurs peuvent être utilisés pour :

Les présentations les plus connues sur les générateurs sont sans doute celles de David Beazley qui donne des exemples concrets d'utilisation. Dans le cadre des générateurs simples on peut aller lire Generator Tricks for Systems Programmers jusqu'à la partie 5 incluse.

Generator Expressions

Une Generator Expression est une syntaxe compacte pour écrire un générateur. La PEP 289 les présente comme une généralisation des List Comprehensions plus performante et plus économe en mémoire.

Cette notation est née du constat que dans la plupart des cas d'utilisation d'une List Comprehension il n'y a pas besoin d'avoir toute la liste en mémoire mais seulement le besoin d'itérer sur chacun des éléments un par un. C'est souvent considéré comme une best practice.

Les Generator Expressions sont particulièrement utiles avec des fonctions qui consomment un iterable et retournent une valeur unique (any(), all(), sum(), min(), max() etc.). Lorsqu'une de ces fonctions n'a qu'un seul argument positionnel on peut lui passer une generator expression sans parenthèses supplémentaires :

sum(i for i in range(10000))

Une generator expression ne peut plus être réutilisée une fois consommée.

Ressources

Avant Itérateurs en Python Après Coroutines via générateurs améliorés en Python

Tag Kemar Joint