• Aucun résultat trouvé

C'est sur la seconde ligne que nous allons nous attarder : à force d'utiliser ce type de syntaxe, vous avez dû vous y habituer et ce type de parcours doit vous être familier. Mais il se cache bel et bien un mécanisme derrière cette instruction.

Quand Python tombe sur une ligne du type for element in ma_liste:, il va appeler l'itérateur de ma_liste. L'itérateur, c'est un objet qui va être chargé de parcourir l'objet conteneur, ici une liste.

L'itérateur est créé dans la méthode spéciale __iter__ de l'objet. Ici, c'est donc la méthode __iter__ de la classe list qui est appelée et qui renvoie un itérateur permettant de parcourir la liste.

À chaque tour de boucle, Python appelle la méthode spéciale __next__ de l'itérateur, qui doit renvoyer l'élément suivant du parcours ou lever l'exception StopIteration si le parcours touche à sa fin.

Ce n'est peut-être pas très clair… alors voyons un exemple.

Avant de plonger dans le code, sachez que Python utilise deux fonctions pour appeler et manipuler les itérateurs : iter permet d'appeler la méthode spéciale __iter__ de l'objet passé en paramètre et next appelle la méthode spéciale __next__ de l'itérateur passé en paramètre.

Code : Python Console

>>> ma_chaine = "test"

>>> iterateur_de_ma_chaine = iter(ma_chaine) >>> iterateur_de_ma_chaine <str_iterator object at 0x00B408F0> >>> next(iterateur_de_ma_chaine) 't' >>> next(iterateur_de_ma_chaine) 'e' >>> next(iterateur_de_ma_chaine) 's' >>> next(iterateur_de_ma_chaine) 't' >>> next(iterateur_de_ma_chaine)

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

StopIteration

>>>

On commence par créer une chaîne de caractères (jusque là, rien de compliqué).

__iter__ de la chaîne, qui renvoie l'itérateur permettant de parcourir ma_chaine.

On va ensuite appeler plusieurs fois la fonction next en lui passant en paramètre l'itérateur. Cette fonction appelle la méthode spéciale __next__ de l'itérateur. Elle renvoie successivement chaque lettre contenue dans notre chaîne et lève une exception StopIteration quand la chaîne a été parcourue entièrement.

Quand on parcourt une chaîne grâce à une boucle for (for lettre in chaine:), c'est ce mécanisme d'itérateur qui est appelé. Chaque lettre renvoyée par notre itérateur se retrouve dans la variable lettre et la boucle s'arrête quand l'exception

StopIteration est levée.

Vous pouvez reprendre ce code avec d'autres objets conteneurs, des listes par exemple.

Créons nos itérateurs

Pour notre exemple, nous allons créer deux classes :

RevStr : une classe héritant de str qui se contentera de redéfinir la méthode __iter__. Son mode de parcours sera ainsi altéré : au lieu de parcourir la chaîne de gauche à droite, on la parcourra de droite à gauche (de la dernière lettre à la première).

ItRevStr : notre itérateur. Il sera créé depuis la méthode __iter__ de RevStr et devra parcourir notre chaîne du dernier caractère au premier.

Ce mécanisme est un peu nouveau, je vous mets le code sans trop de suspense. Si vous vous sentez de faire l'exercice, n'hésitez pas, mais je vous donnerai de toute façon l'occasion de pratiquer dès le prochain chapitre.

Code : Python

class RevStr(str):

"""Classe reprenant les méthodes et attributs des chaînes construites

depuis 'str'. On se contente de définir une méthode de parcours différente : au lieu de parcourir la chaîne de la première à la dernière

lettre, on la parcourt de la dernière à la première.

Les autres méthodes, y compris le constructeur, n'ont pas besoin d'être redéfinies"""

def __iter__(self):

"""Cette méthode renvoie un itérateur parcourant la chaîne

dans le sens inverse de celui de 'str'"""

return ItRevStr(self) # On renvoie l'itérateur créé pour

l'occasion

class ItRevStr:

"""Un itérateur permettant de parcourir une chaîne de la dernière lettre

à la première. On stocke dans des attributs la position courante et la

chaîne à parcourir"""

def __init__(self, chaine_a_parcourir):

"""On se positionne à la fin de la chaîne"""

self.chaine_a_parcourir = chaine_a_parcourir

self.position = len(chaine_a_parcourir)

def __next__(self):

"""Cette méthode doit renvoyer l'élément suivant dans le

parcours,

ou lever l'exception 'StopIteration' si le parcours est fini"""

if self.position == 0: # Fin du parcours

raise StopIteration

self.position -= 1 # On décrémente la position

À présent, vous pouvez créer des chaînes devant se parcourir du dernier caractère vers le premier. Code : Python Console

>>> ma_chaine = RevStr("Bonjour") >>> ma_chaine

'Bonjour'

>>> for lettre in ma_chaine: ... print(lettre) ... r u o j n o B >>>

Sachez qu'il est aussi possible de mettre en œuvre directement la méthode __next__ dans notre objet conteneur. Dans ce cas, la méthode __iter__ pourra renvoyer self. Vous pouvez voir un exemple, dont le code ci-dessus est inspiré, sur la

documentation de Python.

Cela reste quand même plutôt lourd non, de devoir faire des itérateurs à chaque fois ? Surtout si nos objets conteneurs doivent se parcourir de plusieurs façons, comme les dictionnaires par exemple.

Oui, il subsiste quand même beaucoup de répétitions dans le code que nous devons produire, surtout si nous devons créer plusieurs itérateurs pour un même objet. Souvent, on utilisera des itérateurs existants, par exemple celui des listes. Mais il existe aussi un autre mécanisme, plus simple et plus intuitif : la raison pour laquelle je ne vous montre pas en premier cette autre façon de faire, c'est que cette autre façon passe quand même par des itérateurs, même si c'est implicite, et qu'il n'est pas mauvais de savoir comment cela marche en coulisse.

Il est temps à présent de jeter un coup d'œil du côté des générateurs.

Documents relatifs