
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 16Dans 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 89Dans 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()