Les itérateurs et générateurs en Python
La boucle for en python ne fonctionne pas comme dans la majorité des autres langages de programmation. Elle permet de parcourir les objets "itérables", c'est à dire, de manière générale, les chaines, les listes, les tuples et les dictionnaire. En réalité, la boucle for à besoin d'un objet iterator pour fonctionner. Cet objet possède une méthode next() qui renvoit à tour de rôle chacunes des valeurs que doit renvoyer l'objet pour chaque itération, et déclenche une exception StopIteration quand il arrive au bout. Petit exemple avec une liste :
>>> liste = ['Python', 'c\'est', 'bon']
>>> iterateur = liste.__iter__()
>>> iterateur
<listiterator object at 0x808968c>;
>>> iterateur.next()
'Python'
>>> iterateur.next()
'c\'est';
>>> iterateur.next()
'bon'
>>> iterateur.next()
Traceback (most recent call last):
File '<stdin>', line 1, in <module>;
StopIteration
Comme vous pouvez le constater, chaque liste possède une méthode __iter__() qui retourne un objet iterator. Cet objet peut aussi être obtenu grace à la primitive iter(objet). On peut donc conclure qu'un objet itérable est un objet qui possède une méthode __iter__() qui retourne elle même un objet iterator.
En python, on ne se soucie pas de savoir si tel objet est de tel type, mais plutôt de savoir si l'on peut faire telle chose avec tel objet. On appel cela le duck typing : "Si ça marche comme un canard, que ça cancane comme un canard, alors on peut appeler ça un canard". Il est donc possible de créer très simplement un itérateur, et par extension un objet itérable :
>>> class Iterateur:
... i = 0
... def next(self):
... if self.i >= 10: raise StopIteration
... self.i += 1
... return 2**self.i
... def __iter__(self): return self
...
>>> objet_iterateur = Iterateur()
>>> for i in objet_iterateur: print i
...
1
2
4
8
16
32
64
128
256
512
Les générateurs
Les générateurs sont une manière de créer des itérateurs. Il existe deux moyens d'utiliser les générateurs : au travers d'une fonction en utilisant le mot clé yield au lieu de return et en utilisant les "generator expressions" qui ressemblent à la définition de liste par "list comprehension".
Puisqu'un petit exemple vaut mieux qu'un beau discours :
>>> def iterateur():
... for i in xrange(10):
... yield 2**i
...
>>> for i in iterateur(): print i
...
1
2
4
8
16
32
64
128
256
512
Et de manière plus concise avec les "generator expressions" (voir mon billet sur la compréhension de liste pour voir des exemples de la syntaxe générale, le seul différence résidant dans l'utilisation des parenthèses au lieu des crochets) :
>>> for i in (2**x for x in xrange(10)): print i
...
1
2
4
8
16
32
64
128
256
512
Comprendre comment fonctionnent les objets itérables en Python est très important, d'autant qu'ils sont de plus en plus utilisés dans le langage, et permettra donc de mieux comprendre le fonctionnement (ou le non fonctionnement) de son code.

