Python: les itérateurs et générateurs
Pour une petite application que j’avais décidé d’écrire en Python, j’avais besoin de parcourir des chaînes de caractères en prenant les caractères 3 par 3. En tant qu’amateur de C++, ma première idée était forcément de créer une boucle for du genre de celle qu’on écrit en C++:
std::string my_string("azertyuiop"); for(unsigned int i = 0; i < my_string.length(); i+=3) { std::cout << my_string.substr(i, 3) << std::endl; }
Cette façon d’écrire une boucle n’est pas très compliquée à transcrire en Python. On peut utiliser la fonction range() à laquelle on peut passer des paramètres dont le début, la fin et le ‘pas’ (step) de la boucle. Cela donne quelque chose comme:
my_string = "azertyuiop" for i in range(0, len(my_string), 3): print i, t[i:i + 3]
C’est considéré comme une méthode pas très pythonique. On préférera utiliser un itérateur ou un générateur.
Voyons brièvement et basiquement de quoi il s’agit.
Les itérateurs
Les itérateurs sont des objets permettant de parcourir facilement des containers que ce soient des strings – qui ne sont que des containers de caractères – des listes ou des containers d’objets. Par exemple, pour récupérer tous les caractères constituant une chaîne de caractères, on procède comme ceci:
for c in my_string: print c
C’est très simple à utiliser.
Il est très facile aussi de créer soi-même un itérateur de façon à ce qu’il se plie à ce que l’on veut faire. Pour cela, il suffit de créer un objet qui contient deux méthodes: __iter__() et next().
__iter__() est appelé implicitement au début de la boucle et doit renvoyer un objet itérateur.
next() doit lever une exception StopIteration quand on est à la fin de la boucle et qu’il n’y a plus d’éléments à traiter.
Dans le cas d’un itérateur qui renvoie les caractères trois par trois, l’itérateur ressemblerait à ceci:
class triplet: def __init__(self, data): self.data = data self.count = 0 self.length = len(data) def __iter__(self): return self def next(self): if self.count > len(self.length): raise StopIteration else: result = self.data[self.count:self.count+3] self.count += 3 return result
Et on peut l’utiliser dans une boucle de cette façon:
test = triplet("azertyuiop") for item in test: print item
Les générateurs
Cette méthode utilisant un objet itérateur me plaît bien. C’est clair, efficace et très OOP.
Il existe une autre méthode qui est très simple et qui utilise les principes de l’itérateur même si c’est bien caché. Il s’agit des fonctions générateurs. Il s’agit en fait d’une fonction qui retourne un itérateur qui, lui-même, peut être utilisé pour contrôler une boucle par exemple.
Le simple fait d’utiliser le mot-clef yield fait de la fonction une fonction générateur. Lorsque ce mot clef est rencontré, il retourne la valeur en argument et l’execution est suspendue jusqu’à la prochaine itération.
Dans notre cas, cela donne ceci:
def triplet(str): current=0 while current <= len(str): yield str[current:current+3] current += 3
Et on l’utilise dans une boucle comme d’habitude:
for t in triplet("azertyuiop"): print t
Comme on l’a vu brièvement ici, les itérateurs et les générateurs sont très simple d’emploi et on les retrouve très fréquemment dans les codes écrits en Python. Je vous renvoie à la documentation si vous voulez en savoir plus.
Posted: septembre 19th, 2012 under python.
Comments: none
Tweet