Skip to article frontmatterSkip to article content

la boucle for

une instruction for ressemble à ceci :

for item in iterable:
    bloc
    aligné
    d_instructions

break et continue

comme dans beaucoup d’autres langages, et comme pour le while :

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 break

bouh c’est vilain !

dès que vous voyez for i in range(len(truc)) vous devez vous dire qu’il y a mieux à faire:

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

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

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 ?

# 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

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

import sys

L = list(range(1000))
sys.getsizeof(L)
8056
# avec iter() on fabrique 
# un itérateur
I = iter(L)

sys.getsizeof(I)
48

cette boucle Python

for i in range(100_000):
    # do stuff

est 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:

range

# 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

# 10**20 c'est 100 millions de Tera

# un range est presque un iterateur
iterator = range(10**20)
iterator
range(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:
        break
0 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:
        break
2 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

# 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 :

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:
        break
0x10 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:

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:

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")
        break
10
20
30

quel objet est itérable ?

quel objet est un itérateur ? (avancé)

pour savoir si un objet est un itérateur
tester si
iter(obj) is obj

def is_iterator(obj):
    return iter(obj) is obj

par exemple

# 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