la boucle
forest la méthode préférée pour itérer sur un ensemble de valeursen général préférable au
whileen Pythonon peut faire un
forsur n’importe quel itérablece n’est pas le cas pour le
whileavec
forc’est l’itérable qui se charge de la logique
et aussi de nombreuses techniques pour itérer de manière optimisée
compréhensions
itérateurs
expressions génératrices
générateurs (encore appelées fonctions génératrices)
attention / rappel : avec numpy, pas de
for, programmation vectorielle
la boucle for¶
une instruction for ressemble à ceci :
for item in iterable:
bloc
aligné
d_instructionsbreak et continue¶
comme dans beaucoup d’autres langages, et comme pour le while :
breaksort complètement de la bouclecontinuetermine abruptement
l’itération courante et passe à la suivante
for .. else¶
en fait la forme générale de la boucle for c’est
for item in iterable:
bloc
aligné
else:
bloc # exécuté lorsque la boucle sort "proprement"
aligné # c'est-à-dire pas avec un breakbouh c’est vilain !¶
dès que vous voyez vous devez vous dire qu’il y a mieux à faire:for i in range(len(truc))
liste = [10, 20, 40, 80, 120]
# la bonne façon de faire un for
for item in liste:
print(item, end=" ")10 20 40 80 120 # et **non pas** cette
# horrible périphrase !
for i in range(len(liste)):
item = liste[i]
print(item, end=" ")10 20 40 80 120 boucle for sur un dictionnaire¶
rappel: on peut facilement itérer sur un dictionnaire
la plupart du temps, sur à la fois clés et valeurs
for k, v in d.items():pour itérer sur les clés, restons simple:
for k in d:enfin sur les valeurs
for v in d.values():
agenda = {
'paul': 12,
'pierre': 14,
'jean': 16,
}# l'unpacking permet d'écrire
# un code élégant
for key, value in agenda.items():
print(f"{key} → {value}")paul → 12
pierre → 14
jean → 16
boucles for : limite importante¶
règle très importante: à l’intérieur d’une boucle
il ne faut pas modifier l’objet sur lequel on itère
s = {1, 2, 3}
# on essaie de modifier l'objet itéré
try:
for x in s:
if x == 1:
s.remove(x)
except Exception as exc:
print(f"OOPS {type(exc)} {exc}")OOPS <class 'RuntimeError'> Set changed size during iteration
la technique usuelle consiste à utiliser une copie
s = {1, 2, 3}
# avec les listes on peut aussi utiliser [:]
# mais ici sur un ensemble ça ne fonctionnerait pas
for x in s.copy():
if x == 1:
s.remove(x)
s{2, 3}question de style¶
rappelez-vous qu’on peut unpack dans un for; ça permet souvent d’utiliser des noms de variables explicites
D = {'alice': 35, 'bob': 9, 'charlie': 6}# pas pythonique (implicite)
for t in D.items():
print(t[0], t[1])alice 35
bob 9
charlie 6
# pythonique (explicite)
for nom, age in D.items():
print(nom, age)alice 35
bob 9
charlie 6
itérables et itérateurs¶
c’est quoi un itérable ?¶
par définition, c’est un objet .. sur lequel on peut faire un
fornotamment avec les séquences natives : chaînes, listes, tuples, ensembles
et aussi dictionnaires, et des tas d’autres objets, mais patience
# une chaine est un itérable
chaine = "un été"
for char in chaine:
print(char, end=" ")u n é t é # un ensemble aussi
ensemble = {10, 40, 80}
for element in ensemble:
print(element, end=" ")40 10 80 la boucle for, mais pas que¶
on a défini les itérables par rapport à la boucle
formais plusieurs fonctions acceptent en argument des itérables
sum,max,minmap,filteretc...
L = [20, 34, 57, 2, 25]
min(L), sum(L)(2, 138)# ceci retourne un itérateur
map(lambda x: x**2, L)<map at 0x7f8c589edcf0># pour voir "ce qu'il y a dedans"
list(map(lambda x: x**2, L))[400, 1156, 3249, 4, 625]itérateurs¶
les itérateurs sont une sous-famille des itérables
qui présentent la particularité de consommer peu de mémoire
en fait un objet itérateur capture uniquement la logique de l’itération, mais pas les données
c’est-à-dire où on en est, et comment passer au suivant
import sys
L = list(range(1000))
sys.getsizeof(L)8056# avec iter() on fabrique
# un itérateur
I = iter(L)
sys.getsizeof(I)48cette boucle Python
for i in range(100_000):
# do stuffest comparable à ceci en C
for (int i=0;
i<100000;
i++) {
/* do stuff */
}ce qui montre qu’on peut s’en sortir avec seulement un entier comme mémoire
et donc on ne veut pas devoir allouer une liste de 100.000 éléments juste pour pouvoir faire cette boucle !
combinaisons d’itérations¶
Python propose des outils pour créer et combiner les itérables:
fonctions natives builtin qui créent des itérateurs:
range,enumerate, etzip
dans un module dédié
itertools:chain,cycle,islice, ...
range¶
rangecrée un objet qui permet d’itèrer sur un intervalle de nombres entiersarguments : même logique que le slicing
début (inclus), fin (exclus), pas
sauf (curiosité) : si un seul argument, c’est la fin
# les nombres pairs de 10 à 20
for i in range(10, 21, 2):
print(i, end=" ")10 12 14 16 18 20 # le début par défaut est 0
for i in range(5):
print(i, end=" ")0 1 2 3 4 un range n’est pas une liste¶
l’objet retourné par
rangen’est pas une listeau contraire il crée un objet tout petit, un itérateur (*)
qui contient seulement la logique de l’itération
la preuve:
# 10**20 c'est 100 millions de Tera
# un range est presque un iterateur
iterator = range(10**20)
iteratorrange(0, 100000000000000000000)for item in iterator:
if item >= 5:
break
print(item, end=" ")0 1 2 3 4 count : un itérateur infini¶
du coup un itérateur peut même .. ne jamais terminer :
# count fait partie du module itertools
from itertools import count
count?# si on n'arrête pas la boucle nous mêmes
# ce fragment va boucler sans fin
for i in count():
print(i, end=" ")
if i >= 10:
break0 1 2 3 4 5 6 7 8 9 10 # on peut changer les réglages
# ici en partant de 2 avec un step de 5
for i in count(2, 5):
print(i, end=" ")
if i >= 32:
break2 7 12 17 22 27 32 enumerate¶
on a dit qu’on ne faisait jamais
for i in range(len(liste)):
item = liste[i]
print(item, end=" ")mais comment faire alors si on a vraiment besoin de l’index i ?
→ il suffit d’utiliser la builtin enumerate()
L = [1, 10, 100]
for i, item in enumerate(L):
print(f"{i}: {item}")0: 1
1: 10
2: 100
enumerate est typiquement utile sur un fichier, pour avoir le numéro de ligne
remarquez le deuxième argument de enumerate, ici pour commencer à 1
# on peut aussi commencer
# à autre chose que 0
with open("some-file.txt") as f:
for lineno, line in enumerate(f, 1):
print(f"{lineno}:{line}", end="")1:some text written
2:on a few lines
zip¶
zip permet d’itérer sur plusieurs itérables “en même temps”:
liste1 = [10, 20, 30]
liste2 = [100, 200, 300]for a, b in zip(liste1, liste2):
print(f"{a}x{b}", end=" ")10x100 20x200 30x300 un itérateur s’épuise¶
ATTENTION il y a toutefois une limite lorsqu’on utilise un itérateur
une fois que l’itérateur est arrivé à sa fin
il est “épuisé” et on ne peut plus boucler dessus
# avec une liste, pas de souci
L = [100, 200]
print('pass 1')
for i in L:
print(i)
print('pass 2')
for i in L:
print(i)pass 1
100
200
pass 2
100
200
# iter() permet de construire
# un itérateur sur un itérable
R = iter(L)
print('pass 1')
for i in R:
print(i)
print('pass 2')
for i in R:
print(i)pass 1
100
200
pass 2
du coup par exemple,
ne pas essayer d’itérer deux fois sur un zip() ou un enumerate(), vous observeriez le même phénomène
le module itertools - assemblage d’itérables¶
on trouve dans le module itertools plusieurs utilitaires très pratiques :
countpour énumérer les entiers (voir plus haut)chainpour chainer plusieurs itérablescyclepour rejouer un itérable en bouclerepeatpour énumérer plusieurs fois le même objetislicepour n’énumérer que certains morceauxzip_longestfonctionne commezipmais s’arrête au morceau le plus long
chain¶
from itertools import chain
data1 = (10, 20, 30)
data2 = (100, 200, 300)# chain()
for d in chain(data1, data2):
print(f"{d}", end=" ")10 20 30 100 200 300 # c'est comme un lego, on peut combiner toutes ces fonctions
for i, d in enumerate(chain(data1, data2)):
print(f"{i}x{d}", end=" ")0x10 1x20 2x30 3x100 4x200 5x300 cycle¶
# cycle() ne termine jamais non plus
from itertools import cycle
data1 = (10, 20, 30)
for i, d in enumerate(cycle(data1)):
print(f"{i}x{d}", end=" ")
if i >= 10:
break0x10 1x20 2x30 3x10 4x20 5x30 6x10 7x20 8x30 9x10 10x20 repeat¶
# repeat()
from itertools import repeat
data1 = (10, 20, 30)
data2 = (100, 200, 300)
# pour peut répéter le même élément plusieurs fois
padding = repeat(1000, 3)
for i, d in enumerate(chain(data1, padding, data2)):
print(f"{i}x{d}", end=" ")0x10 1x20 2x30 3x1000 4x1000 5x1000 6x100 7x200 8x300 islice¶
fonctionne comme le slicing, mais sur n’importe quel itérable
# avec islice on peut par exemple
# sauter une ligne sur deux dans un fichier
from pathlib import Path
# on crée un fichier
with Path('islice.txt').open('w') as f:
for i in range(6):
f.write(f"{i}**2 = {i**2}\n")# pour ne relire qu'une ligne sur deux
from itertools import islice
with Path('islice.txt').open() as f:
for line in islice(f, 0, None, 2):
print(line, end="")0**2 = 0
2**2 = 4
4**2 = 16
# ou zapper les 3 premières
from itertools import islice
with Path('islice.txt').open() as f:
for line in islice(f, 3, None):
print(line, end="")3**2 = 9
4**2 = 16
5**2 = 25
# ou ne garder que les 3 premières
from itertools import islice
with Path('islice.txt').open() as f:
for line in islice(f, 3):
print(line, end="")0**2 = 0
1**2 = 1
2**2 = 4
zip_longest()¶
comme zip, mais s’arrête à l’entrée la plus longue
du coup il faut dire par quoi remplacer les données manquantes
from itertools import zip_longest
for i, d in zip_longest(
range(6), L, fillvalue='X'):
print(f"{i} {d}")0 100
1 200
2 X
3 X
4 X
5 X
itertools & combinatoires¶
Le module itertools propose aussi quelques combinatoires usuelles:
product: produit cartésien de deux itérablespermutations: les permutations ()combinations: p parmi net d’autres...
exemple avec product¶
from itertools import product
dim1 = (1, 2, 3)
dim2 = '♡♢♤'
for i, (d1, d2) in enumerate(product(dim1, dim2), 1):
print(f"i={i}, d1={d1} d2={d2}")i=1, d1=1 d2=♡
i=2, d1=1 d2=♢
i=3, d1=1 d2=♤
i=4, d1=2 d2=♡
i=5, d1=2 d2=♢
i=6, d1=2 d2=♤
i=7, d1=3 d2=♡
i=8, d1=3 d2=♢
i=9, d1=3 d2=♤
sous le capot¶
pour les curieux..
comment marche la boucle for ?¶
lorsqu’on itère sur un itérable
iterable = [10, 20, 30]sous le capot, la boucle for va faire:
créer un itérateur en appelant
iter(iterable)appeler
next()sur cet itérateurjusqu’à obtenir l’exception
StopIteration
iter() et next()¶
voici un équivalent approximatif
iterable = [10, 20, 30]
# cette boucle for
for item in iterable:
print(item)10
20
30
# est en gros équivalente
# à ce fragment
iterateur = iter(iterable)
while True:
try:
item = next(iterateur)
print(item)
except StopIteration:
# print("fin")
break10
20
30
quel objet est itérable ?¶
il existe beaucoup d’objets itérables en python
tous les objets séquence: listes, tuples, chaînes, etc.
les sets, les dictionnaires
les vues (
dict.keys(),dict.values()), etc.les fichiers
les générateurs
il faut penser à les utiliser, c’est le plus rapide et le plus lisible
quel objet est un itérateur ? (avancé)¶
pour savoir si un objet est un itérateur
tester siiter(obj) is obj
def is_iterator(obj):
return iter(obj) is objpar exemple¶
une liste n’est pas son propre itérateur
un fichier est son propre itérateur
# créons un fichier
with open("tmp.txt", 'w') as F:
for i in range(6):
print(f"{i=} {i**2=}", file=F)
# pour voir qu'un fichier ouvert en
# lecture est son propre itérateur
with open("tmp.txt") as F:
print(f"{is_iterator(F)=}")is_iterator(F)=True
# la liste non
L = list(range(5))
print(f"{is_iterator(L)=}")is_iterator(L)=False
# cycle en est un
C = cycle(L)
print(f"{is_iterator(C)=}")is_iterator(C)=True
# un zip() est un itérateur
Z = zip(L, L)
print(f"{is_iterator(Z)=}")is_iterator(Z)=True