Un itérable en Python : tout ce qui se parcourt élément par élément

Un itérable est un objet que Python sait parcourir élément par élément. Ce qui l'oppose à un itérateur, et pourquoi certains ne se parcourent qu'une fois.
5 min de lecture
Believemy logo

Définition

Écrire une boucle qui parcourt une liste est facile. En écrire une autre pour un fichier ouvert, une autre pour un dictionnaire, une autre encore pour un intervalle de nombres serait pénible, et personne ne s'y résout. Python évite cette duplication grâce à une convention commune : tout objet capable de livrer son contenu un élément à la fois se prête à la même boucle. Cette capacité, plutôt qu'un type particulier, porte un nom : un itérable. Rien n'oblige un itérable à être une liste, il suffit qu'il sache répondre présent quand on le lui demande.

Une chaîne, un dictionnaire, un fichier ouvert et un intervalle de nombres sont tous itérables, alors qu'ils n'ont presque rien d'autre en commun, comme le montre le code suivant.

PYTHON
for lettre in "abc":
    print(lettre)

for ligne in open("notes.txt"):
    print(ligne)

for cle in {"nom": "Ada", "ville": "Londres"}:
    print(cle)

La boucle for ne connaît aucun de ces objets en particulier, et c'est ce qui la rend réutilisable. Elle applique un protocole : elle réclame à l'objet un itérateur, puis lui demande ses éléments un à un jusqu'au signal de fin. Tout objet capable d'y répondre devient itérable de fait, et le reste du langage le traite comme n'importe quelle collection.


La source et le curseur

La confusion entre itérable et itérateur explique la moitié des surprises sur le sujet. L'itérable est la source : une liste, une chaîne, un fichier. L'itérateur est le curseur fabriqué à partir de cette source, celui qui se souvient d'où il en est. La fonction iter() passe de l'un à l'autre, et chaque appel produit un curseur neuf, posé au début.

Le tableau suivant met les deux objets côte à côte.

QuestionItérableItérateur
Ce que c'estUne source d'élémentsUn curseur qui avance
Combien de parcoursAutant que vouluUn seul, puis plus rien
Ce qu'il doit fournir__iter____iter__ et __next__
Exemples courantsListe, chaîne, dictionnairezip, générateur

Il cache une asymétrie utile : tout itérateur est aussi itérable, puisqu'il sait se fournir lui-même, alors que l'inverse est faux. Une liste ne garde aucun souvenir entre deux boucles, ce qui permet de la parcourir dix fois de suite sans la moindre précaution.


Ce qui n'est pas itérable

Certains objets refusent d'être parcourus, et le message d'erreur qui en résulte est reconnaissable entre tous : il désigne toujours un objet manipulé comme une collection alors qu'il n'en est pas une.

PYTHON
total = 0

for chiffre in 12345:
    total += chiffre

# TypeError: 'int' object is not iterable

Un entier, un flottant, un booléen, None et une fonction ne sont pas itérables : ils n'ont rien à distribuer. La TypeError qui en résulte se corrige rarement là où elle apparaît. Le cas le plus fréquent est une fonction qui renvoie None sur un chemin oublié, un if sans else, dont le résultat part dans une boucle : l'erreur pointe la boucle, la cause se trouve vingt lignes plus haut.


Le piège du parcours unique

Les outils qui produisent un itérateur plutôt qu'une collection sont partout depuis Python 3, et ils ne se parcourent qu'une seule fois. Le second parcours ne lève aucune erreur, il donne simplement un résultat vide, plus difficile à repérer qu'un plantage, comme le montre l'exemple suivant.

PYTHON
paires = zip([1, 2, 3], "abc")

print(list(paires))  # [(1, 'a'), (2, 'b'), (3, 'c')]
print(list(paires))  # []

Le curseur est arrivé au bout, il y a laissé son signal de fin, la StopIteration, et rien ne le rembobine. Deux parades existent : convertir une fois pour toutes en liste quand les données tiennent en mémoire, ou reconstruire l'objet avant chaque parcours sinon. Cet arbitrage entre mémoire et recalcul est le seul vrai choix que le sujet impose.


Fabriquer le vôtre

Un objet devient itérable dès qu'il sait répondre à iter(). Le chemin court passe par yield : une fonction qui cède ses valeurs au lieu de les renvoyer produit un objet parcourable sans qu'aucune classe ne soit écrite. Le chemin explicite consiste à écrire une classe qui délègue le travail à un attribut déjà itérable.

PYTHON
class Panier:
    def __init__(self, articles):
        self.articles = articles

    def __iter__(self):
        return iter(self.articles)

for article in Panier(["pain", "sel"]):
    print(article)
Attention

Une erreur fréquente consiste à mettre __next__ directement sur la classe, plutôt que de le confier à un itérateur séparé. L'objet fonctionne au premier parcours, puis se vide comme n'importe quel itérateur au second.

Trois lignes suffisent, et l'objet gagne d'un coup tout ce qui repose sur ce protocole : la boucle, la conversion en liste, le test d'appartenance, le dépaquetage, les fonctions de calcul comme sum ou max.


Questions fréquentes

Question

Comment savoir si un objet est itérable ?

En l'essayant plutôt qu'en interrogeant son type : un appel à iter(objet) réussit, ou lève une erreur de type. C'est l'application directe du duck typing, principe selon lequel un objet vaut par ce qu'il sait faire et non par la famille dont il descend.

Question

Pourquoi len échoue-t-il sur un générateur alors que la boucle fonctionne ?

Parce qu'être itérable ne dit rien de la taille. len exige que l'objet connaisse son nombre d'éléments à l'avance, ce qu'un générateur ignore par construction. Les compter revient à tout parcourir, donc à tout consommer.

Question

Parcourir une chaîne donne des lettres, comment obtenir des mots ?

Une chaîne est itérable au niveau du caractère, et c'est un choix du langage plutôt qu'un accident. Pour parcourir des mots, découpez d'abord la chaîne avec split(), puis parcourez le résultat.

Termes connexes

Découvrez notre glossaire Python

Parcourez les termes et définitions les plus couramment utilisés dans le domaine du développement avec Python.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.