W3docs

Les itérateurs Python

Apprenez le fonctionnement des itérateurs Python, comment créer des classes d'itérateurs personnalisées et quand les utiliser à la place des listes.

Un itérateur est l'une des abstractions les plus fondamentales de Python. Chaque fois que vous écrivez une boucle for, appelez zip(), ou utilisez une compréhension de liste, Python s'appuie silencieusement sur le protocole itérateur en coulisses. Ce chapitre explique ce que sont les itérateurs, comment construire les vôtres, comment utiliser le riche ensemble d'itérateurs intégrés, et quand les itérateurs sont le bon outil pour le travail.

Qu'est-ce qu'un itérateur ?

Python distingue deux concepts liés :

  • Un itérable est tout objet sur lequel vous pouvez itérer — une list, un tuple, un str, un dict, un set, ou tout objet dont la classe définit __iter__. Il peut produire un itérateur, mais ne suit pas lui-même sa position.
  • Un itérateur est un objet qui suit l'état du parcours. Il implémente deux méthodes qui forment ensemble le protocole itérateur :
    • __iter__() — renvoie l'objet itérateur lui-même. Cela permet aux itérateurs de fonctionner dans les boucles for et autres contextes d'itération.
    • __next__() — renvoie la valeur suivante à chaque appel. Lorsqu'il ne reste plus de valeurs, elle lève StopIteration.

La différence clé : vous pouvez parcourir une liste autant de fois que vous le souhaitez, car chaque boucle for demande un nouvel itérateur. Un itérateur est unidirectionnel et à usage unique — une fois épuisé, appeler next() dessus lève toujours StopIteration.

python— editable, runs on the server
graph LR
  A[Iterator Object] --> B[__iter__]
  B --> C[Returns self]
  A --> D[__next__]
  D --> E[Next Value]
  D --> F{No values left?}
  F -->|Yes| G[Raises StopIteration]
  F -->|No| E

Comment une boucle for utilise les itérateurs

La boucle for n'est qu'un sucre syntaxique pour le protocole itérateur. En interne, Python traduit :

for item in some_iterable:
    print(item)

en approximativement ceci :

_it = iter(some_iterable)   # call __iter__()
while True:
    try:
        item = next(_it)    # call __next__()
    except StopIteration:
        break
    print(item)

Comprendre cette traduction permet de voir clairement pourquoi tout objet qui implémente __iter__ et __next__ fonctionne de façon transparente dans une boucle for, avec zip(), enumerate(), et tout autre contexte qui attend un itérable.

Construire un itérateur personnalisé

Pour créer un itérateur personnalisé, définissez une classe qui implémente à la fois __iter__ et __next__. Voici un itérateur Countdown qui compte à rebours depuis un nombre donné jusqu'à 1 :

class Countdown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self        # the iterator is its own iterable

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        value = self.current
        self.current -= 1
        return value

for n in Countdown(5):
    print(n)
# Output:
# 5
# 4
# 3
# 2
# 1

Notez que __iter__ renvoie self. C'est ce qui permet de placer le même objet directement dans une boucle for — la boucle appelle iter() dessus, ce qui appelle __iter__(), qui renvoie l'itérateur lui-même.

Ajouter un paramètre de pas

Vous pouvez ajouter toute logique souhaitée à l'intérieur de __next__. Voici un itérateur StepRange qui imite range() mais accepte une valeur de pas :

class StepRange:
    def __init__(self, start, stop, step=1):
        self.current = start
        self.stop = stop
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.stop:
            raise StopIteration
        value = self.current
        self.current += self.step
        return value

print(list(StepRange(0, 10, 3)))
# Output: [0, 3, 6, 9]

Appeler list() sur n'importe quel itérateur l'épuise et collecte toutes les valeurs dans une liste — un schéma utile quand vous avez besoin de tous les résultats en même temps.

Les fonctions intégrées iter() et next()

Les fonctions intégrées iter() et next() sont le moyen standard de travailler directement avec le protocole itérateur.

  • iter(obj) — appelle obj.__iter__() et renvoie l'itérateur résultant.
  • next(it) — appelle it.__next__() et renvoie la valeur suivante.
  • next(it, default) — renvoie default au lieu de lever StopIteration quand l'itérateur est épuisé. C'est la façon la plus sûre d'examiner l'élément suivant sans bloc try/except.
words = ["hello", "world"]
it = iter(words)

print(next(it))           # hello
print(next(it))           # world
print(next(it, "done"))   # done  (exhausted; returns default)

La forme à deux arguments de next() est particulièrement utile dans les scénarios de flux ou d'analyse syntaxique où vous souhaitez gérer gracieusement la fin de l'entrée.

Les itérateurs sont à usage unique

C'est le piège le plus courant avec les itérateurs : une fois épuisé, un itérateur ne peut pas être rembobiné.

it = iter([1, 2, 3])

for x in it:
    print(x)        # prints 1, 2, 3

for x in it:
    print(x)        # prints nothing — iterator is exhausted

Si vous devez itérer plusieurs fois, conservez l'itérable d'origine (par exemple, la liste) et appelez iter() à nouveau, ou utilisez une compréhension de liste pour matérialiser d'abord toutes les valeurs.

Fonctions intégrées qui renvoient des itérateurs

La bibliothèque standard de Python est construite sur les itérateurs. Ces fonctions renvoient toutes des itérateurs plutôt que des listes, ce qui les rend efficaces en mémoire même pour de très grandes séquences :

range()

range(start, stop, step) renvoie un itérateur d'entiers. Il ne stocke pas les entiers en mémoire — il calcule chacun à la demande.

for i in range(1, 6):
    print(i)
# Output: 1  2  3  4  5

zip()

zip() prend plusieurs itérables et renvoie un itérateur de tuples, en associant les éléments position par position. L'itération s'arrête à l'entrée la plus courte.

names = ["Alice", "Bob", "Carol"]
scores = [95, 88, 72]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
# Output:
# Alice: 95
# Bob: 88
# Carol: 72

enumerate()

enumerate() enveloppe tout itérable et renvoie des paires (index, valeur). Utilisez-le pour éviter de maintenir une variable compteur manuelle.

fruits = ["apple", "banana", "cherry"]

for i, fruit in enumerate(fruits, start=1):
    print(f"{i}. {fruit}")
# Output:
# 1. apple
# 2. banana
# 3. cherry

map() et filter()

Les deux fonctions renvoient des itérateurs (en Python 3). map(fn, iterable) applique une fonction à chaque élément ; filter(fn, iterable) ne conserve que les éléments pour lesquels la fonction renvoie True.

numbers = [1, 2, 3, 4, 5]

doubled = list(map(lambda x: x * 2, numbers))
print(doubled)          # [2, 4, 6, 8, 10]

evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)            # [2, 4]

Vérifier si un objet est un itérateur

Utilisez isinstance() avec les classes de base abstraites du module collections.abc pour tester l'itérabilité et le statut d'itérateur :

from collections.abc import Iterable, Iterator

my_list = [1, 2, 3]
my_iter = iter(my_list)

print(isinstance(my_list, Iterable))   # True  — list is iterable
print(isinstance(my_list, Iterator))   # False — list is NOT an iterator
print(isinstance(my_iter, Iterator))   # True  — list_iterator is an iterator
print(isinstance(my_iter, Iterable))   # True  — all iterators are also iterables

Tout itérateur est aussi un itérable (car __iter__ renvoie self), mais tout itérable n'est pas nécessairement un itérateur.

Quand utiliser des itérateurs plutôt que des listes

SituationUtiliser
Besoin d'accès aléatoire (items[5])list
Besoin d'itérer une seule fois, la mémoire est importanteitérateur / générateur
Séquences infinies ou très grandesitérateur / générateur
Besoin d'itérer plusieurs foislist (conserver l'original)
Pipeline de transformationsitérateurs chaînés (map, filter, itertools)

Pour les grands ensembles de données — lecture de millions de lignes depuis un fichier, traitement de données en flux — un itérateur évite de tout charger en mémoire en même temps. Pour les petites collections finies où vous accédez aux éléments de façon répétée, une liste est plus simple.

Itérateurs vs. générateurs

Un générateur est un raccourci pratique pour écrire un itérateur. Au lieu d'une classe avec __iter__ et __next__, vous écrivez une fonction qui utilise yield. Python la convertit automatiquement en itérateur.

# Iterator class
class Countdown:
    def __init__(self, start):
        self.current = start
    def __iter__(self):
        return self
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        value = self.current
        self.current -= 1
        return value

# Equivalent generator function
def countdown(start):
    while start > 0:
        yield start
        start -= 1

print(list(countdown(5)))   # [5, 4, 3, 2, 1]

Utilisez un itérateur basé sur une classe lorsque vous avez besoin de méthodes supplémentaires ou d'un état mutable au-delà de ce qu'un simple générateur offre. Utilisez un générateur pour la plupart des autres cas — il est plus concis et tout aussi puissant.

Consultez le chapitre Python Generators pour un traitement complet de yield, des expressions de générateur et de send().

Pratique

Pratique
Which methods make up the Python iterator protocol?
Which methods make up the Python iterator protocol?
Was this page helpful?