Le module itertools offre plusieurs outils pour une utilisation avancée des generators...
Le module itertools fournit un itérateur count contenant un compteur, qui s'incrémente à chaque appel, en commençant
>>> from itertools import count
>>> for k in range(7): ... print icpt.next(), ... 5 6 7 8 9 10 11
Afin de pouvoir illustrer les outils du module itertools, on suppose avoir créé deux generators, l'un pour la suite de Fibonacci, l'autre pour les nombres premiers...
>>> def fibonacci(): ... a, b = 0, 1 ... while True: ... yield b ... a, b = b, a+b ... >>> suite = fibonacci() >>> 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()
Considérons le generator suivant...
>>> def gen(): ... yield 1 ... yield 2 ...
Au premier appel, on obtiendra 1, puis 2, et enfin une exception de type StopIteration...
>>> unGen = gen() >>> unGen.next() 1 >>> unGen.next() 2 >>> unGen.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
Si l'on souhaite plutôt boucler, obtenir un cycle (après 2, recommencer à 1), on peut utiliser l'outil cycle de itertools...
>>> unGen = gen() >>> from itertools import cycle >>> unCycle = cycle(unGen) >>> unCycle.next() 1 >>> unCycle.next() 2 >>> unCycle.next() 1
ifilter(fun, gen) permet de ne conserver que les éléments $x$ du generator $gen$ tels que $fun(x)$ est True
Ainsi, si l'on souhaite obtenir le generator des nombres premiers de la forme : un carré plus 1 ($5 = 2^2 + 1$, $17 = 4^2 + 1$), on peut
>>> from math import sqrt
... return int(sqrt(float(x)-1))**2+1 == x ... >>> premiers = nombre_premier() >>> from itertools import ifilter >>> premiers_formes = ifilter(est_carre_plus_un, premiers) >>> for k in range(10): ... print premiers_formes.next(), ... 2 5 17 37 101 197 257 401 577 677
Il existe aussi une méthode ifilterfalse, dont l'explication est immédiate.
takewhile(fun, seq) renvoie les éléments de seq tant que fun(seq(n)) renvoie True.
Par exemple, si l'on veut obtenir les nombres de Fibonacci inférieurs à 100, on peut
>>> def est_inferieur(x):
... >>> from itertools import takewhile >>> fibos = fibonacci() >>> cent_fibos = takewhile(est_inferieur, fibos) >>> while True: ... print cent_fibos.next(), ... 1 1 2 3 5 8 13 21 34 55 89 Traceback (most recent call last): File "<stdin>", line 2, in <module> StopIteration
takewhile(fun, seq) renvoie les éléments de seq dès que fun(seq(n)) renvoie faux.
Par exemple, pour obtenir la suite nombres de fibonacci supérieurs à 100, il suffit de reprendre le code ci-dessus, et de remplacer takewhile par dropwhile :
>>> fibos = fibonacci() >>> pas_cent_fibos = dropwhile(est_inferieur, fibos) >>> for k in range(10): ... print pas_cent_fibos.next(), ... 144 233 377 610 987 1597 2584 4181 6765 10946
Extrait des tranches d'itérateurs...
>>> iprime = nombre_premier() >>> from itertools import islice >>> iprime100prem = islice(iprime,20) >>> while True: ... print iprime100prem.next(), ... 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 Traceback (most recent call last): File "<stdin>", line 2, in <module> StopIteration
>>> iprime = nombre_premier() >>> from itertools import islice >>> iprime1000 = islice(iprime, 1000, 1010) >>> while True: ... print iprime1000.next(), ... 8117 8123 8147 8161 8167 8171 8179 8191 8209 8219
La fonction chain de itertools permet de concaténer plusieurs generators : quand on arrive à la fin d'un generator, on passe au suivant.
>>> def gen1(): ... yield 1 ... yield 2 ... >>> def gen2(): ... yield 'a' ... yield 'b' ... >>> from itertools import chain >>> unGen1 = gen1() >>> unGen2 = gen2() >>> une_chaine = chain(unGen1, unGen2) >>> while True: ... une_chaine.next(), ... 1 2 'a' 'b' Traceback (most recent call last): File "<stdin>", line 2, in <module> StopIteration >>>
La fonction izip(seq1, seq2) du module itertools permet de renvoyer le generator constitué des couples (seq1[k], seq2[k]) :
>>> from itertools import izip >>> couple = izip(suite, premier) >>> for k in range(10): ... print couple.next(), ... (1, 2) (1, 3) (2, 5) (3, 7) (5, 11) (8, 13) (13, 17) (21, 19) (34, 23) (55, 29) >>>
Le generator défini ci-dessous renvoie effectivement un couple constitué du nième terme de la suite de Fibonacci et du nième nombre premier.
Si l'on passe N generators à izip, alors la même chose se produit : un generator de N-uples est créé.
imap(fun, seq1, seq2) renvoie le generator dont les termes sont (fun(seq1[k]), fun(seq2[k]))
>>> def carre(x,y): ... return x**2, y**2 ... >>> from itertools import imap >>> couple = imap(carre, suite, premier) >>> for k in range(10): ... print couple.next(), ... (1, 4) (1, 9) (4, 25) (9, 49) (25, 121) (64, 169) (169, 289) (441, 361) (1156, 529) (3025, 841)
On récupère de cette manière la liste des carrés des couples Fibonacci, nombres premiers.
Une fois encore, imap peut accepter un nombre quelconque de generators en entrée.