Contactez-nous

Créer des itérateurs personnalisés

Apprenez à créer des itérateurs personnalisés en Python en implémentant les méthodes spéciales __iter__ et __next__. Concevez des objets qui peuvent être parcourus avec une boucle 'for'.

Implémenter le protocole d'itération : __iter__ et __next__

Pour créer un itérateur personnalisé en Python, vous devez définir une classe qui implémente le protocole d'itération, c'est-à-dire qui définit les méthodes spéciales `__iter__` et `__next__`.

  • `__iter__(self)` : Cette méthode doit retourner l'objet itérateur lui-même (généralement `self`). Elle est appelée une seule fois, au début de l'itération.
  • `__next__(self)` : Cette méthode doit retourner l'élément suivant de la séquence, ou lever l'exception `StopIteration` si la fin de la séquence est atteinte. Elle est appelée à chaque itération.

Exemple : Un itérateur qui produit les carrés des nombres de 0 à n-1 :

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

    def __iter__(self):
        return self  # L'objet est son propre itérateur

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

# Utilisation de l'itérateur
carres = Carres(5)
for carre in carres:
    print(carre) # 0 1 4 9 16

Dans cet exemple, la classe `Carres` implémente le protocole d'itération. La méthode `__init__` initialise l'itérateur avec la valeur maximale `n` et la valeur courante `0`. La méthode `__iter__` retourne `self`. La méthode `__next__` retourne le carré de la valeur courante, incrémente la valeur courante, et lève `StopIteration` lorsque la valeur courante atteint `n`.

Exemple : Itérateur pour une séquence de Fibonacci

Voici un exemple plus avancé : un itérateur qui produit les nombres de la suite de Fibonacci jusqu'à une valeur maximale :

class Fibonacci:
    def __init__(self, max_val):
        self.max_val = max_val
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.a > self.max_val:
            raise StopIteration
        valeur = self.a
        self.a, self.b = self.b, self.a + self.b  # Calcul du terme suivant
        return valeur

# Utilisation
fib = Fibonacci(100)
for nombre in fib:
    print(nombre) # 0 1 1 2 3 5 8 13 21 34 55 89

Dans cet exemple :

  • `__init__` initialise l'itérateur avec la valeur maximale et les deux premiers termes de la suite (0 et 1).
  • `__iter__` retourne `self`.
  • `__next__` vérifie si le terme courant (`self.a`) dépasse la valeur maximale. Si c'est le cas, il lève `StopIteration`. Sinon, il retourne le terme courant et calcule le terme suivant.

Avantages des itérateurs personnalisés

Créer des itérateurs personnalisés présente plusieurs avantages :

  • Contrôle total sur l'itération : Vous pouvez définir précisément comment les éléments sont générés et parcourus.
  • Lazy evaluation (évaluation paresseuse) : Les éléments ne sont générés qu'au moment où ils sont demandés, ce qui peut être très efficace en termes de mémoire, surtout pour les séquences très grandes ou infinies.
  • Code plus propre et plus lisible : Le code qui utilise un itérateur est souvent plus clair et plus facile à comprendre que le code équivalent qui utiliserait des indices et des manipulations de listes.
  • Réutilisabilité : Vous pouvez réutiliser votre itérateur dans différents contextes.
  • Intégration avec la boucle `for` : Votre itérateur personnalisé peut être utilisé directement dans une boucle `for`, comme n'importe quel autre itérable.

Itérateurs infinis

Un itérateur peut être infini, c'est-à-dire qu'il peut potentiellement produire des éléments indéfiniment. Dans ce cas, la méthode `__next__` ne doit jamais lever `StopIteration`.

Exemple : Un itérateur qui produit tous les nombres entiers positifs :

class EntiersPositifs:
    def __init__(self):
        self.courant = 1

    def __iter__(self):
        return self

    def __next__(self):
        valeur = self.courant
        self.courant += 1
        return valeur # Pas de StopIteration

# Utilisation (attention : boucle infinie !)
# entiers = EntiersPositifs()
# for n in entiers:
#     print(n)

Il faut être très prudent avec les itérateurs infinis. Si vous les utilisez dans une boucle `for` sans condition d'arrêt, votre programme ne se terminera jamais. Vous pouvez utiliser une instruction `break` pour sortir de la boucle, ou utiliser des fonctions comme `itertools.islice` pour limiter le nombre d'éléments consommés.

Un autre exemple, un itérateur qui produit des nombres aléatoires :

import random

class NombresAleatoires:
    def __iter__(self):
        return self

    def __next__(self):
        return random.random()