4.3. Les itérateurs personnalisés¶
Afin qu’un objet puisse être employé comme un itérateur, il faut que sa méthode spéciale __next__()
soit mise en œuvre.
Créons pour exemple un itérateur qui renvoie l’une après l’autre les puissances entières successives d’une base donnée en paramètre du constructeur.
class PowerIt:
def __init__(self, base):
self.__base = base
self.__exponent = 0
def __next__(self):
value = self.__base ** self.__exponent
self.__exponent += 1
return value
it_2 = PowerIt(2)
it_7 = PowerIt(7)
print("Quelques puissances de 2 :")
for n in range(5):
print(f"2 ** {n} = {next(it_2)}")
print("Les puissances de 7 inférieures à 25000 :")
n = next(it_7)
while n < 25000:
print(n)
n = next(it_7)
Quelques puissances de 2 :
2 ** 0 = 1
2 ** 1 = 2
2 ** 2 = 4
2 ** 3 = 8
2 ** 4 = 16
Les puissances de 7 inférieures à 25000 :
1
7
49
343
2401
16807
Il est ainsi possible d’utiliser la fonction next()
(ou directement la méthode __next__()
) pour obtenir la valeur suivante de notre itérateur.
4.3.1. Combiner itérateurs et itérables¶
Il est bien sûr possible et souvent nécessaire d’associer un itérateur personnalisé à un itérable personnalisé.
Il faut alors écrire deux classes, l’une pour l’itérateur et l’autre pour l’itérable.
L’itérable devra mettre en œuvre la méthode spéciale __iter__()
en charge de renvoyer une instance de l’itérateur, qui devra lui-même mettre en œuvre la méthode __next__()
.
class PowerIt:
def __init__(self, base, max_exp):
self.__base = base
self.__max_exp = max_exp
self.__exponent = 0
def __next__(self):
if self.__exponent == self.__max_exp:
raise StopIteration
value = self.__base ** self.__exponent
self.__exponent += 1
return value
class Powers:
def __init__(self, base, max_exp=-1):
self.__base = base
self.__max_exp = max_exp
def __iter__(self):
return PowerIt(self.__base, self.__max_exp)
p_7 = Powers(7, 6)
p_2 = Powers(2)
print("Les 6 premières puissances de 7 :")
for power in p_7:
print(power)
print("Les puissances de 2 à la demande (appuyez sur ENTER pour la valeur suivante) :")
for power in p_2:
input(power) # affiche la valeur suivante et attend une entrée de l'utilisateur
Les 6 premières puissances de 7 :
1
7
49
343
2401
16807
Les puissances de 2 à la demande (appuyez sur ENTER pour la valeur suivante) :
1
2
4
8
16
...
Cette fois, l’itérateur possède un attribut supplémentaire max_exp
qui correspond au nombre maximal d’itérations autorisées.
Ce maximum atteint, la méthode spéciale __next__()
lève l’exception StopIteration
comme le font les itérateurs de listes quand ils en arrivent au bout.
Par défaut, nous initialisons l’attribut max_exp
à -1
, ce qui a pour effet de ne pas imposer de limite à l’itérateur.
Celui-ci continuera donc de produire les valeurs à la demande sans jamais lever l’exception StopIteration
.
4.3.2. Les itérateurs itérables¶
Lorsque nous ne nous soucions pas de faire la différence entre itérateur et itérable, il est possible de n’écrire qu’une seule classe faisant office des deux en mettant en œuvre à la fois __iter__()
et __next__()
, de la manière suivante.
class Powers:
def __init__(self, base, max_exp=-1):
self.__base = base
self.__max_exp = max_exp
self.__exponent = 0
def __iter__(self):
return self
def __next__(self):
if self.__exponent == self.__max_exp:
raise StopIteration
value = self.__base ** self.__exponent
self.__exponent += 1
return value
L’utilisation de la classe et son fonctionnement sont identiques à la version précédente de notre programme.
La méthode __iter__()
de la classe Powers
retourne ici l’instance courante même, car c’est celle-ci qui sert aussi d’itérateur.