Contactez-nous

Comprendre le protocole d'itération

Plongez au coeur du protocole d'itération en Python. Découvrez la différence entre itérables et itérateurs, les méthodes __iter__ et __next__, et le fonctionnement interne de la boucle 'for'.

Itérables et itérateurs : deux concepts clés

Pour comprendre le protocole d'itération en Python, il faut d'abord distinguer deux concepts fondamentaux : les itérables et les itérateurs.

  • Itérable : Un itérable est un objet qui peut être parcouru, c'est-à-dire un objet dont on peut obtenir les éléments un par un. Les listes, les tuples, les chaînes de caractères, les dictionnaires, les ensembles, les fichiers, sont des exemples d'itérables.
  • Itérateur : Un itérateur est un objet qui permet de parcourir un itérable. Il maintient une position courante dans l'itérable et fournit une méthode pour accéder à l'élément suivant.

En termes simples, un itérable est un objet "qui contient des données", tandis qu'un itérateur est un objet "qui sait comment parcourir ces données".

Le protocole d'itération : les méthodes __iter__ et __next__

Le protocole d'itération en Python repose sur deux méthodes spéciales : `__iter__` et `__next__`.

  • `__iter__` : Cette méthode est appelée sur un itérable pour obtenir un itérateur. Elle retourne un objet itérateur.
  • `__next__` : Cette méthode est appelée sur un itérateur pour obtenir l'élément suivant de l'itérable. Si l'itérateur a atteint la fin de l'itérable, il lève l'exception `StopIteration`.

Un objet est considéré comme un **itérable** s'il implémente la méthode `__iter__`.

Un objet est considéré comme un **itérateur** s'il implémente les méthodes `__iter__` et `__next__`.

Exemple (en créant un itérateur simple qui produit les nombres de 0 à n-1) :

class MonIterateur:
    def __init__(self, n):
        self.n = n
        self.courant = 0

    def __iter__(self):
        return self  # L'itérateur retourne lui-même

    def __next__(self):
        if self.courant < self.n:
            valeur = self.courant
            self.courant += 1
            return valeur
        else:
            raise StopIteration  # Signale la fin de l'itération

# Utilisation de l'itérateur
iterateur = MonIterateur(5)
for i in iterateur:
    print(i) # 0 1 2 3 4

Dans cet exemple : `MonIterateur` est à la fois un itérable (il a une méthode `__iter__`) et un itérateur (il a une méthode `__next__`). La méthode `__iter__` retourne l'objet lui-même (`self`). La méthode `__next__` retourne l'élément suivant (ou lève `StopIteration` si la fin est atteinte).

La boucle for : fonctionnement interne avec le protocole d'itération

Lorsque vous utilisez une boucle `for` en Python, le protocole d'itération est utilisé en coulisses.

L'instruction `for element in iterable:` est équivalente à :

# Obtention d'un itérateur à partir de l'itérable
iterateur = iter(iterable)  # Appel implicite à iterable.__iter__()

# Boucle tant qu'il y a des éléments
while True:
    try:
        # Obtention de l'élément suivant
        element = next(iterateur)  # Appel implicite à iterateur.__next__()
    except StopIteration:
        # Fin de l'itération
        break

    # Corps de la boucle for (utilisation de l'élément)
    # ...

La boucle `for` :

  1. Appelle la fonction intégrée `iter()` sur l'itérable pour obtenir un itérateur. `iter(iterable)` appelle en fait `iterable.__iter__()`.
  2. Appelle la fonction intégrée `next()` sur l'itérateur pour obtenir l'élément suivant, à chaque itération. `next(iterateur)` appelle en fait `iterateur.__next__()`.
  3. Exécute le corps de la boucle `for` avec l'élément obtenu.
  4. S'arrête lorsque l'exception `StopIteration` est levée par `next()`.

C'est ce mécanisme qui permet à la boucle `for` de fonctionner avec n'importe quel itérable, qu'il s'agisse d'une liste, d'un tuple, d'une chaîne de caractères, ou d'un objet personnalisé implémentant le protocole d'itération.

Fonctions intégrées utiles : iter() et next()

Les fonctions intégrées `iter()` et `next()` sont des raccourcis pour appeler les méthodes `__iter__` et `__next__` respectivement.

  • `iter(iterable)` : Retourne un itérateur pour l'itérable donné. C'est équivalent à `iterable.__iter__()`.
  • `next(iterator)` : Retourne l'élément suivant de l'itérateur. C'est équivalent à `iterator.__next__()`.

Exemple (en reprenant l'exemple `MonIterateur` précédent, mais en utilisant `iter` et `next` explicitement) :

iterateur = MonIterateur(3)

print(next(iterateur))  # Affiche 0
print(next(iterateur))  # Affiche 1
print(next(iterateur))  # Affiche 2
# print(next(iterateur))  # Lèverait StopIteration

Itérables vs. Itérateurs : récapitulatif et exemples

Récapitulatif :

  • Itérable : Un objet qui peut être parcouru (qui a une méthode `__iter__` retournant un itérateur). Exemples : listes, tuples, chaînes, dictionnaires, ensembles, fichiers.
  • Itérateur : Un objet qui permet de parcourir un itérable (qui a les méthodes `__iter__` et `__next__`). Il maintient une position courante et retourne l'élément suivant avec `__next__`. Il lève `StopIteration` lorsqu'il n'y a plus d'éléments.

Un itérateur est aussi un itérable (il a une méthode `__iter__`), mais l'inverse n'est pas vrai.

Exemples d'itérables qui ne sont *pas* des itérateurs :

ma_liste = [1, 2, 3]  # Liste (itérable)
mon_tuple = (1, 2, 3)  # Tuple (itérable)
ma_chaine = "Bonjour"  # Chaîne de caractères (itérable)

Exemples d'itérateurs :

  • L'objet retourné par `iter(ma_liste)`.
  • L'objet retourné par `map(fonction, iterable)`.
  • L'objet retourné par `filter(fonction, iterable)`.
  • L'objet retourné par `open("fichier.txt")` (un itérateur sur les lignes du fichier).
  • Les objets générateurs (voir section suivante).

Comprendre la distinction entre itérables et itérateurs est fondamental pour maîtriser l'itération en Python et pour concevoir des objets personnalisés qui peuvent être parcourus avec une boucle `for`.