for
On a vu jusqu’ici que pour parcourir un tuple
ou une list
on utilisait un indice explicite. On a en fait une instruction composée dédiée à cette tâche en python : for
.
La structure générale est la suivante:
for VARIABLE in CONTENEUR:
INSTRUCTION1
INSTRUCTION2
...
INSTRUCTION_FINALE
NOUVELLE_INSTRUCTION
la VARIABLE
va alors prendre les valeurs successives du conteneur.
Avec une list
>>> nombres = [1, 5, 4, 6, 2]
>>> for nombre in nombres:
... print(nombre)
...
1
5
4
6
2
Avec un tuple
>>> mon_tuple = (1, 2, "a", [1, 2])
>>> for element in mon_tuple:
... print(element)
...
1
2
a
[1, 2]
Avec une str
>>> for lettre in "qsdmflkjzerb":
... print(lettre)
...
q
s
d
m
f
l
k
j
z
e
r
b
range
Dans le schéma général précédent, on a mentionné qu’on pouvait insérer n’importe quel conteneur dans une boucle for
. Mais force est de constater qu’on n’utilise en fait les éléments que les uns après les autres, on n’a pas littéralement besoin de toutes les valeurs simultanément.
L’instruction for
est en fait plus générale, on peut lui fournir n’importe quel iterable
. De manière plus précise il s’agit de tout objet python pouvant produire des valeurs successives à la demande.
Avant de voir une description générale de tels objets, on va commencer par un exemple extrêmement utile: range
.
Il permet de produire une suite de nombres de différentes façons:
La façon élémentaire avec un argument:
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
range(n)
s’arrête en fait sur n-1
!Avec deux arguments on précise aussi le démarrage:
>>> for i in range(5, 10):
... print(i)
...
5
6
7
8
9
Finalement avec trois arguments on précise aussi l’espacement entre les nombres produits:
>>> for nombre in range(1, 14, 2):
... print(nombre)
...
1
3
5
7
9
11
13
ATTENTION notez bien que range
ne produit pas une list
(à moins d’utiliser encore python2)
>>> nombres = range(1, 5)
>>> type(nombres)
<class 'range'>
>>> nombres
range(1, 5)
ATTENTION le sens des arguments dépend de l’ordre et du nombre total d’arguments. En cas de doute au début on n’hésitera pas à faire help(range)
.
En fait on peut mettre dans for
tout objet accepté par la fonction iter
(on dit qu’il s’agit d’un itérable), celle-ci renverra un objet accepté par la fonction next
qui permet de demander l’élément suivant (on qualifie un tel objet d’itérateur).
Une list
est ainsi itérable mais pas un itérateur:
>>> iterable = [1, 2, 3, 4]
>>> next(iterable)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not an iterator
>>> iterateur = iter(iterable)
>>> next(iterateur)
1
>>> next(iterateur)
2
>>> next(iterateur)
3
>>> next(iterateur)
4
>>> next(iterateur)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Fondamentalement, une boucle
for ELEMENT in ITERABLE:
...
fonctionne de la manière suivante:
au premier passage dans l’entête, on effectue ITERATEUR = iter(ITERABLE)
puis ELEMENT = next(ITERATEUR)
puis à chaque nouveau passage dans l’entête on refait ELEMENT = next(ITERATEUR)
,
le corps principal est exécuté après chaque utilisation de next
ne produisant pas l’erreur StopIteration
. Après avoir généré cette erreur on passe sort de l’instruction composée.
On verra comment donner un code équivalent à for
quand on aura vu le mécanisme d’interception des erreurs. (il s’agit de l’instruction composée try: except:
)
L’intérêt de ce procédé est d’éviter de l’occupation mémoire inutile quand on ne veut que les éléments les uns à la suite des autres et non simultanément.
enumerate,
reversed
et zip
ATTENTION notez que toutes les fonctions de cette section acceptent des itérables et renvoient des itérateurs!
On a tendance en python à éviter de gérer soit même les indices d’un conteneur (car c’est une source de bogues).
On n’est donc censé éviter presque systématiquement l’idiome:
for indice in range(len(conteneur)):
Si jamais on a besoin des indices on utilise enumerate
:
>>> for indice, lettre in enumerate("abcdefghijklm"):
... print(f"la {indice + 1}-ième lettre est {lettre}")
...
la 1-ième lettre est a
la 2-ième lettre est b
la 3-ième lettre est c
la 4-ième lettre est d
la 5-ième lettre est e
la 6-ième lettre est f
la 7-ième lettre est g
la 8-ième lettre est h
la 9-ième lettre est i
la 10-ième lettre est j
la 11-ième lettre est k
la 12-ième lettre est l
la 13-ième lettre est m
On récupère ainsi un couple (indice, élément)
qu’on a tendance à déstructurer instantanément comme ci-dessus.
Si on veut parcourir en sens inverse on peut là encore se passer d’un indiçage rusé:
>>> for lettre in reversed("abcdef"):
... print(lettre)
...
f
e
d
c
b
a
Finalement si on veut parcourir conjointement plusieurs itérables on utilise zip
:
>>> for triplet in zip("abc", "def", "ghi"):
... print(triplet)
...
('a', 'd', 'g')
('b', 'e', 'h')
('c', 'f', 'i')
ATTENTION si les itérables n’ont pas autant d’éléments les uns que les autres, on s’arrête à la fin du plus court:
>>> for test in zip("abcdef", "gh"):
... print(test)
...
('a', 'g')
('b', 'h')
n: int
, fournissez un code générant la liste des multiples de 5
entre 0
et n
.nombres: list[int]
, donnez le code permettant de calculer la moyenne et la variance.texte: str
, donnez un code permettant de calculer le nombre de fois où la lettre e
apparaît.Pour un descriptif de l’instruction composée for
regardez le paragraphe correspondant dans:
>>> help("LOOPING")
Pour les sequences
(tuple
, list
et range
) consultez
>>> help("SEQUENCES")
>>> help("SEQUENCEMETHODS")
Finalement on n’hésitera pas à consulter l’aide des fonctions introduites ci-dessus.
On donne une première version naïve (mais générale):
>>> n = 123
>>> resultat = list()
>>> for nombre in range(0, 1 + n):
... if nombre % 5 == 0:
... resultat.append(nombre)
...
>>> resultat
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120]
Une version plus optimale (mais plus restreinte) utilisant que l’on a en fait des nombres de 5
en 5
:
>>> n = 123
>>> resultat = list()
>>> for nombre in range(0, 1 + n, 5):
... resultat.append(nombre)
...
>>> resultat
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100, 105, 110, 115, 120]
Là encore on propose d’abord une solution naïve générale pour la moyenne:
>>> nombres = [1, 5, 2, 7, 123, 5, 2, 6]
>>> somme, longueur = 0, 0
>>> for nombre in nombres:
... somme = somme + nombre
... longueur = longueur + 1
...
>>> moyenne = somme / longueur
>>> moyenne
18.875
Mais on peut aussi dans ce cas précis faire:
>>> nombres = [1, 5, 2, 7, 123, 5, 2, 6]
>>> moyenne = sum(nombres) / len(nombres)
>>> moyenne
18.875
ATTENTION on a anticipé un peu sur la suite car le résultat après division est un float
(nombre à virgule flottante) Pour la variance, une fois la moyenne calculée on adapte la première méthode de la moyenne:
>>> nombres = [1, 5, 2, 7, 123, 5, 2, 6]
>>> moyenne = sum(nombres) / len(nombres)
>>> somme_quadratique = 0
>>> for nombre in nombres:
... somme_quadratique = somme_quadratique + (nombre - moyenne) ** 2
...
...
>>> variance = somme_quadratique / len(nombres)
>>> variance
1552.859375
On peut aussi tirer parti d’une formule classique pour avoir le code faisant un seul passage dans nombres
:
>>> nombres = [1, 5, 2, 7, 123, 5, 2, 6]
>>> somme, longueur, somme_quadratique = 0, 0, 0
>>> for nombre in nombres:
... somme = somme + nombre
... longueur = longueur + 1
... somme_quadratique = somme_quadratique + nombre ** 2
...
>>> variance = somme_quadratique / longueur - (somme / longueur) ** 2
>>> variance
1552.859375
Il suffit ici d’utiliser une boucle for
et un test if
:
>>> texte = """mlkfjqmeshzalekjhsqldkjfhqzlkeura
... qsmdkfjza
... qsdmlaeeraer
... e
... e
... eeaaz
... e
... """
>>> nombre_de_e = 0
>>> for lettre in texte:
... if lettre == "e":
... nombre_de_e = nombre_de_e + 1
...
>>> nombre_de_e
11