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.
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.
| Question | Itérable | Itérateur |
|---|---|---|
| Ce que c'est | Une source d'éléments | Un curseur qui avance |
| Combien de parcours | Autant que voulu | Un seul, puis plus rien |
| Ce qu'il doit fournir | __iter__ | __iter__ et __next__ |
| Exemples courants | Liste, chaîne, dictionnaire | zip, 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.
total = 0
for chiffre in 12345:
total += chiffre
# TypeError: 'int' object is not iterableUn 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.
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.
class Panier:
def __init__(self, articles):
self.articles = articles
def __iter__(self):
return iter(self.articles)
for article in Panier(["pain", "sel"]):
print(article)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
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.
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.
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.