Mise en œuvre du protocole Iterator de Python

Vous construisez des objets personnalisés sur lesquels vous souhaitez prendre en charge l’itération, mais vous souhaitez implémenter facilement le protocole itérateur.

La façon la plus simple d’implémenter une itération sur un objet est d’utiliser une fonction générateur. Dans l’article “Comment déléguer l’itération en Python“, une classe Noeud a été présentée pour représenter les structures arborescentes.

Peut-être voulez-vous implémenter un itérateur qui traverse les nœuds en profondeur d’abord. Voici comment vous pourriez le faire:

class Noeud:
    def __init__(self, valeur):
        self._valeur = valeur
        self._enfants = []

    def __repr__(self):
        return 'Noeud({!r})'.format(self._valeur)

    def ajouter_enfant(self, noeud):
        self._enfants.append(noeud)

    def __iter__(self):
        return iter(self._enfants)

    def parcours_profondeur(self):
        yield self
        for c in self:
            yield from c.parcours_profondeur()

# Exemple
if __name__ == '__main__':
    racine= Noeud(0)
    enfant1 = Noeud(1)
    enfant2 = Noeud(2)
    racine.ajouter_enfant(enfant1)
    racine.ajouter_enfant(enfant2)
    child1.ajouter_enfant(Noeud(3))
    child1.ajouter_enfant(Noeud(4))
    child2.ajouter_enfant(Noeud(5))

    for ch in racine.parcours_profondeur():
        print(ch)
    # Sortie: Noeud(0), Noeud(1), Noeud(3), Noeud(4), Noeud(2), Noeud(5)

Dans ce code, la méthode parcours_profondeur() est simple à lire et à décrire. Il se cède d’abord lui-même et ensuite il itére sur chaque enfant en donnant les éléments produits par la méthode parcours_profondeur() de l’enfant (en utilisant l’instruction yield from).

Le protocole itérateur de Python exige que la méthode __iter__() renvoie un objet itérateur spécial qui implémente une opération __next__() et utilise une exception StopIteration pour terminer un signal. Cependant, la mise en œuvre de tels objets peut souvent être une affaire désordonnée. Par exemple, le code suivant montre une implémentation alternative de la méthode parcours_profondeur() utilisant une classe d’itérateur associée:

class Noeud:
    def __init__(self, valeur):
        self._valeur = valeur
        self._enfants = []

    def __repr__(self):
        return 'Noeud({!r})'.format(self._valeur)

    def ajouter_enfant(self, other_Noeud):
        self._enfants.append(other_Noeud)

    def __iter__(self):
        return iter(self._enfants)

    def parcours_profondeur(self):
        return Iterateur_Parcours_Profondeur(self)

class Iterateur_Parcours_Profondeur(object):
    '''
     Parcours en profondeur
    '''
    def __init__(self, start_Noeud):
        self._Noeud = start_Noeud
        self._enfants_iter = None
        self._enfant_iter = None

    def __iter__(self):
        return self

    def __next__(self):
        # Rendre self si elle vient de commencer; créer un itérateur pour les enfants

        if self._enfants_iter is None:
            self._enfants_iter = iter(self._Noeud)
            return self._Noeud

        # Si on traite un enfant, retourner son élément suivant
        elif self._enfant_iter:
            try:
                nextenfant = next(self._enfant_iter)
                return nextenfant
            except StopIteration:
                self._enfant_iter = None
                return next(self)

        # Avancer à l'enfant suivant et commencer son itération
        else:
            self._enfant_iter = next(self._enfants_iter).parcours_profondeur()
            return next(self)

La classe Iterateur_Parcours_Profondeur fonctionne de la même manière que la version du générateur, mais c’est un désordre car l’itérateur doit maintenir beaucoup d’états complexes sur l’endroit où il se trouve dans le processus d’itération.

Franchement, personne n’aime écrire des codes pareils. Donc, il vaut mieux définir votre itérateur comme un générateur.

LAISSER UN COMMENTAIRE

Please enter your comment!
Please enter your name here