Algo. et prog. 2 en
Logo de Python
Arnaud COUTURIER - Python 3.10

Iterables, iterators

La boucle for que vous connaissez repose sur un mécanisme qu'on appelle protocole d'itération. Ce protocole repose sur deux types d'objets qui sont simples mais efficaces:

Ces deux types séparent les objets qui contiennent les données qui sont de type iterable, des objets qui permettent d'itérer, de parcourir ces données, ce sont les itérateurs. L'avantage des objets de type iterateur est qu'ils peuvent être parcourus de façon simple et toujours identique, peu importe les données sous-jacentes contenues dans l'objet iterable correspondant. L'avantage du type iterable est qu'à partir d'un objet de ce type on peut obtenir autant d'objets iterator qu'on veut pour le parcourir.

De nombreux types built-in sont des iterables, par exemple str, list, tuple et range. En fait tous les types séquences sont aussi des iterables (mais tous les iterables ne sont pas forcément des séquences).

Pour obtenir un objet iterator à partir d'un objet iterable on utilise la fonction built-in iter():


		>>> iter([10, 20, 30])
		<list_iterator object at 0x000001D11D298F70>

		>>> iter((10, 20, 30))
		<tuple_iterator object at 0x000001D11D29BE20>

		>>> iter(range(10))
		<range_iterator object at 0x000001D11C85E010>

		>>> iter("Bonjour")
		<str_iterator object at 0x000001D11D298640>
	

Puis on obtient les éléments les uns après les autres en appelant la fonction built-in next() qui prend en paramètre un iterator et renvoie le prochain élément de l'iterable sous-jacent. Quand il n'y a plus d'élément disponible next() provoque une exception de type StopIteration:


		>>> i = iter([10, 20, 30])
		>>> next(i)
		10
		>>> next(i)
		20
		>>> next(i)
		30
		>>> next(i)
		StopIteration
		>>> next(i)
		StopIteration
	

Un iterator ne peut être parcouru qu'une seule fois. Lorsqu'il a été "consommé", c'est-à-dire parcouru entièrement, si on veut parcourir à nouveau l'iterable, il faut créer un nouvel itérateur avec iter().

Les objets iterable ont la méthode spéciale __iter__() et les iterator ont les méthodes spéciales __iter__() et __next__().

La méthode __iter__() appelée sur un objet iterable fait exactement la même chose que la fonction iter(): elle retourne un nouvel objet iterator capable de parcourir cet iterable.


		>>> L = [10, 20, 30]
		>>> iter(L)
		<list_iterator object at 0x000001D11D29B9A0>
		>>> L.__iter__()
		<list_iterator object at 0x000001D11D29BD60>
	

La méthode __iter__() appelée sur un objet iterator retourne lui-même. Ainsi un iterator est considéré lui-même comme étant un iterable.


		>>> i = iter([10, 20, 30])
		>>> i is iter(i)
		True
	

La méthode __next__() appelée sur un objet iterator fait exactement la même chose que la fonction next(): elle retourne le prochain élément.


		>>> i = iter([10, 20, 30])
		>>> next(i)
		10
		>>> i.__next__()
		20
		>>> i.__next__()
		30
		>>> i.__next__()
		StopIteration
	

Utilisations du protocole d'itération

On n'utilise jamais le protocole d'itération directement comme on vient de le voir, c'est la boucle for qui le fait pour nous, ainsi que les compréhensions (qu'on verra plus loin dans ce cours). L'utilité de connaître ce protocole est de pouvoir créer nos propres types/classes iterable et iterator. Pour cela nos classes devront implémenter les méthodes spéciales __iter__() pour les iterables et __iter__() et __next__() pour les iterator.

On peut désormais détailler le fonctionnement de la boucle for plus en détail, voyons cet exemple typique:


		for i in range(10):
			print(i)
	
  1. Lorsqu'elle commence, la boucle for évalue l'expression à droite du in qui doit nécessairement donner un objet de type iterable. Dans cet exemple, le constructeur range(10) nous retourne un objet de type range qui est bien un type iterable, comme on l'a vu plus haut.

  2. La boucle appelle iter() sur cet iterable et garde en mémoire l'objet iterator résultat. Dans cet exemple ça sera un objet de type range_iterator très exactement. Cet objet est invisible pour nous, il est géré en interne par la boucle.

  3. Avant chaque itération, la boucle appellera next() sur son iterator, et assignera la valeur retournée à la variable à gauche du in. Dans l'exemple la variable i prendra avant chaque itération la valeur que next() sur le range_iterator retournera, soit tour à tour les nombres 0, 1, 2 ... jusqu'à 9.

  4. Le code utilisateur dans le bloc de la boucle est executé. Dans cet exemple c'est print(i)

  5. Les étape 3 et 4 sont répétées jusqu'à ce que la fonction next() provoque une exception StopIteration, à ce moment la boucle est terminée, et le programme continue après celle-ci.