Les generators, introduits dans la version 2.2, offrent une technique de programmation originale, qui permet à une fonction ou une méthode de renvoyer des résultats intermédiaires.
Le mot-clef return est remplacé par yield.
La première fois que ce mot est rencontré, l'exécution est stoppéen et un objet de type generator est renvoyé, qui contient le contexte local de la fonction, et une méthode next
A chaque appel de cette méthode, la fonction est appelée jusqu'au prochain yield.
On définit une fonction avec trois yield (et aucun return). On va voir que
Définissons notre generator :
>>> def ca_compte(): ... yield 'Et de un' ... yield 'Et de deux' ... yield 'Et de trois' ... >>> je_compte = ca_compte()
Premier appel...
>>> je_compte.next() 'Et de un'
Et ça continue ainsi, de yield en yield...
>>> je_compte.next() 'Et de deux' >>> je_compte.next() 'Et de trois'
Enfin, une fois que tous les yield ont été appelés, une exception StopIteration est levée...
>>> jeCompte.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
La méthode next pour passer à la prochaine valeur, et la levée de l'exception StopIteration, rendent les generators compatibles avec les boucles :
>>> je_compte = ca_compte() >>> for element in je_compte: ... print element ... Et de un Et de deux Et de trois
Voici à quoi peuvent servir les generators, en l'occurence à programmer proprement la suite de Fibonacci :
>>> def fibonacci(): ... a, b = 0, 1 ... while True: ... yield b ... a, b = b, a+b ... >>> suite = fibonacci() >>> n = input('Combien de termes ? ') >>> for k in range(n): ... print suite.next(), ... 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
On a mis un yield dans une boucle while infinie : on pourra donc appeler autant de fois que l'on veut suite.next(). Cette manière de procéder peut se réutiliser dans plein de contextes différents.
Le generator suivant permet d'obtenir autant de nombres premiers que l'on veut :
>>> def nombre_premier(): ... nombre = 2 ... premiers = [2] ... yield nombre ... while True: ... nombre += 1 ... if len([k for k in premiers if nombre%k == 0]) == 0: ... yield nombre ... premiers.append(nombre) ... >>> premier = nombre_premier() >>> premier.next() 2 >>> premier.next() 3 >>> premier.next() 5 >>> premier.next() 7 >>> premier.next() 11
Les generators expressions (genexp) fournit une écriture concise pour les generators simples, en se basant sur les list comprehensions : les crochets de ces dernières sont remplacées par des parenthèses...
>>> gen = ( k for k in range(10) if k%3 == 0) >>> gen.next() 0 >>> gen.next() 3 >>> gen.next() 6 >>> gen.next() 9 >>> gen.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
Le module itertools offre des outils avancés pour travailler sur les generators.