# Itérable
Un itérable est une collection dont on peut prendre les éléments un par un, concrétement en Python c’est un objet sur lequel on peut appliquer une boucle for.
Voici les itérables qu’on rencontre le plus souvent :
# Liste et tuple
Une liste est l’objet le plus simple à appréhender avec la boucle for.
for x in [1, 2, 3]: print(x) # 1 # 2 # 3
Un tuple (ou n-uplet en français) est un objet similaire à une liste à la seule différence qu’il ne peut être modifié une fois créé.
for x in (1, 2, 3): print(x) # 1 # 2 # 3
# Chaîne de caractères
Une chaîne de caractères est également itérable.
for c in "Python": print(c) # P # y # t # h # o # n
# range
for n in range(4): print(n) # 0 # 1 # 2 # 3
# Dictionnaire
Et oui, mêmes les dictionnaires sont itérables.
for k in {"x": 1, "y": 2}: print(k) # y # x
# Fichier
for l in open("a.txt"): print(l, end="") # 1ère ligne # 2nd ligne
end=""
à print car le fichier contient lui même des retours à la ligne.# Les générateurs
Un générateur est un objet dont les valeurs sont calculées au fur et à mesure et sur lequel on ne peux itérer qu’une seule fois.
avec une fonction génératrice :
def g(): counter = 0 print("on renvoie counter") yield counter print("on incrémente counter") counter +=1 yield counter print("on incrémente counter encore") counter +=1 yield counter generateur = g() # le code n'est pas exécuté à ce moment là for x in generateur: # mais maintenant print(x) # on renvoie counter # 0 # on incrémente counter # 1 # on incrémente counter encore # 2 for x in generateur: # là rien ne se passe, on a consummé notre générateur print(x)
Avec une expression génératrice :
g = (x for x in range(5)) print(list(g)) # [0, 1, 2, 3, 4] print(list(g)) # [] encore une fois, le générateur a été consummé
# Itérateur
Un itérateur est une sorte de curseur qui avance dans une collection d’objet (rien à voir avec les antiquaires je vous rassure).
Un itérateur est donc un itérable, mais il est itérable qu’une seule fois.
Pour créer un itérateur il y a plusieurs façons:
# La fonction iter
La fonction iter permet de facilement créer un itérateur à partir d’un itérable
i = iter([1, 2, 3]) for x in i: print(x) # 1 # 2 # 3
# La méthode __next__
On peut implémenter un itérateur avec la méthode __iter__ et __next__.
Dès que l’on souhaite arrêter la boucle on lève une exception StopIteration.
class MonIterateur(): def __init__(self, stop): self.current = 0 self.stop = stop def __iter__(self): return self def __next__(self): self.current += 1 if self.current > self.stop: raise StopIteration return self.current for x in MonIterateur(5): print(x) # 1 # 2 # 3 # 4 # 5
# Comment ça marche ?
Si on ajoute quelques print pour y voir plus clair
class MonIterateur(): def __init__(self, stop): print("on rentre dans la fonction __init__") self.current = 0 self.stop = stop def __iter__(self): print("on rentre dans la fonction __iter__") return self def __next__(self): print("on rentre dans la fonction __next__") self.current += 1 if self.current > self.stop: raise StopIteration return self.current iterateur = MonIterateur(5) # on rentre dans la fonction __init__ for x in iterateur: print(x) # on rentre dans la fonction __iter__ # on rentre dans la fonction __next__ # 1 # on rentre dans la fonction __next__ # 2 # on rentre dans la fonction __next__ # 3 # on rentre dans la fonction __next__ # 4 # on rentre dans la fonction __next__ # 5 # on rentre dans la fonction __next__ for x in iterateur: print(x) # on rentre dans la fonction __iter__ # on rentre dans la fonction __next__
En faite la boucle for demande d’abord un objet sur lequel itérer puis itère sur cet objet avec la méthode __next__
# Pourquoi ?!
Dès fois on ne veux pas avoir à définir la méthode __next__ dans la classe sur laquel on va itérer. C’est donc pour ça qu’on découpe le fonctionnement en deux
# L’intérieur d’une boucle for
En interne la boucle for fait une boucle infinie et appelle next sur un itérateur jusqu’à ce qu’une exception StopIteration soit levée.
for x in range(5): print(x) # 0 # 1 # 2 # 3 # 4
La boucle si dessus peut se réécrire de la façon suivante :
iterator = iter(range(5)) # on crée l'itérateur try: while True: # on boucle à l'infini x = next(iterator) print(x) except StopIteration: # jusqu'à ce que l'itérateur lève une exception "StopIteration" pass # 0 # 1 # 2 # 3 # 4
# Que faire avec tout ça ?
Il existe plein de fonctions et méthodes de classe qui utilisent des itérables, voici les plus communes.
# La méthode str.join
Un exemple vaut mille mots :
>>> " séparateur ".join("abc") 'a séparateur b séparateur c'
Et oui, on se souvient que les chaînes de caractères sont itérables.
str.join
doit renvoyer des chaînes de caractères.>>> " séparateur ".join(range(5)) Traceback (most recent call last): File "<pyshell#2>", line 1, in <module> " séparateur ".join(range(5)) TypeError: sequence item 0: expected str instance, int found
Mais en utilisant une expression génératrice :
>>> " séparateur ".join(str(n) for n in range(5)) '0 séparateur 1 séparateur 2 séparateur 3 séparateur 4'
# La fonction sum
La fonction sum permet de faire (🥁 roulement de tambours) des sommes
>>> sum(range(5), start=0) 10 >>> sum(range(5), start=1) 11
Mais pas que, tout objet supportant __add__ peut être utilisé:
>>> sum([[1], [2], [3]], start=[]) [1, 2, 3] # même effet que >>> [] + [1] + [2] + [3] [1, 2, 3]
# La fonction len
La fonction len n’est plus à présenter
>>> len("azerty") 6 >>> len([1, 2, 3, 4, 5, "a", "b"]) 7
# itertools.cycle
boucle à l’infini en repartant du début quand l’itérable est parcouru en entier.
>>> import itertools >>> cycle = itertools.cycle("abcd") >>> for _ in range(20): print(next(cycle), end="") 'abcdabcdabcdabcdabcd'
# itertools.repeat
repète à l’infini un élément, dans le cas où un second argument n
est donné l’élément est répété n
fois.
>>> import itertools >>> list(itertools.repeat(25, 4)) [25, 25, 25, 25] >>> list(itertools.repeat(25)) [25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, ...]
# itertools.product
>>> import itertools >>> from pprint import pprint # pretty print >>> pprint(list(itertools.product([1, 2, 3], [4, 5, 6], [7, 8, 9]))) [(1, 4, 7), (1, 4, 8), (1, 4, 9), (1, 5, 7), (1, 5, 8), (1, 5, 9), (1, 6, 7), (1, 6, 8), (1, 6, 9), (2, 4, 7), (2, 4, 8), (2, 4, 9), (2, 5, 7), (2, 5, 8), (2, 5, 9), (2, 6, 7), (2, 6, 8), (2, 6, 9), (3, 4, 7), (3, 4, 8), (3, 4, 9), (3, 5, 7), (3, 5, 8), (3, 5, 9), (3, 6, 7), (3, 6, 8), (3, 6, 9)]
# itertools.combinations
>>> import itertools >>> list(itertools.combinations([1, 2, 3, 4, 5], 2)) [(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]