4.1. Les itérateurs¶
Les itérateurs sont utiles lorsque l’on doit effectuer séquentiellement des opérations sur une suite de valeurs.
Nous avons jusqu’à présent utilisé la boucle for
pour exécuter des instructions sur les valeurs d’une liste prises une à une.
Cette approche a cependant ses limites.
Essayons par exemple d’additionner les 100’000 plus petits nombres premiers au moyen d’une fonction relativement naïve qui en génère la liste.
import math
def is_prime(n):
for d in range(2, int(math.sqrt(n)) + 1):
if n % d == 0:
return False
return True
def find_n_first_primes(n):
primes = []
x = 1
while len(primes) < n:
if is_prime(x):
primes.append(x)
x += 1
return primes
total = 0
for n in find_n_first_primes(100_000):
total += n
print(total)
62259399013
Ce programme est non-seulement très intensif en calculs (il devrait prendre quelques secondes à s’exécuter), mais il est également inefficace et gourmand en termes de ressources de stockage.
En effet, il crée et maintient en mémoire une liste de 100’000 éléments, alors que cette liste n’est même pas nécessaire vu que nous devons juste retenir la somme des nombres et non pas les mémoriser individuellement.
Un utilisation trop intensive de la mémoire vive de l’ordinateur pourrait même conduire à l’épuisement de ses ressources et l’interruption du programme avec l’exception MemoryError
.
Les itérateurs permettent de résoudre élégamment ce problème en profitant de la syntaxe efficace et claire de la boucle for
pour générer les valeurs à la volée, sans devoir créer la liste entière au préalable.
Un itérateur est un objet particulier qui a la propriété de renvoyer des valeurs une à une, séquentiellement et à la demande.
La boucle for
utilise les itérateurs de façon cachée afin d’exécuter des opérations sur chaque élément d’un objet itérable, capable de fournir un itérateur pour le parcourir.
4.1.1. Utiliser les itérateurs¶
On obtient un itérateur à partir d’un objet itérable, tel qu’une liste, en appelant sa méthode spéciale __iter__()
ou grâce à la fonction iter()
.
Une fois l’instance d’itérateur obtenue, on accède aux valeurs une à une à l’aide de sa méthode spéciale __next__()
ou avec la fonction next()
.
>>> l = [2, 5, 6]
>>> it = iter(l) # équivalent à : it = l.__iter__()
>>> next(it) # équivalent à : it.__next__()
2
>>> next(it)
5
>>> next(it)
6
>>> next(it)
Traceback (most recent call last):
File "<input>", line...
StopIteration
L’itérateur retourne les valeurs de la liste l’une après l’autre, jusqu’à qu’il n’y en ait plus.
Si nous essayons quand même d’obtenir la valeur suivante, l’itérateur lève une exception StopIteration
indiquant qu’il n’y a plus aucune valeur à renvoyer.
Note
Maintenant que nous avons vu comment fonctionne un itérateur, nous pouvons mieux comprendre ce qu’il se passe en réalité à l’intérieur d’une boucle for
.
Considérez le code ci-dessous :
for item in iterable:
# corps de la boucle (en utilisant `item`)...
Ce code peut être réécrit de la manière suivante :
it = iter(iterable) # on crée un itérateur sur l'objet iterable
while True:
try:
item = next(it) # on tente d'obtenir la prochaine valeur
# corps de la boucle (en utilisant `item`)...
except StopIteration: # lorsqu'il n'y a plus de valeurs...
break # on interrompt la boucle infinie
Si les deux versions sont fonctionnellement équivalentes, la première avec la boucle for
est nettement plus concise et élégante.