2.2. Les structures de boucles#

Python dispose de deux structures permettant de faire des boucles : une lorsque le nombre d’itérations est connu à l’avance et une autre lorsque la boucle doit s’arrêter grâce à une condition.

La structure boucle avec compteur s’utilise avec la syntaxe suivante :

for compteur in iterateur:
    # Instructions

et la structure Tant que selon la syntaxe :

while condition:
	# Instructions

Attention

  • le code sait quand finit le bloc de code à réaliser si la condition est vrai grâce à l’indentation. Il n’y a ni mot clef end ni accolade.

  • il ne faut pas oublier les 2 points après le booléen (ou la condition).

2.2.1. Boucle for#

Voici la synthaxe de la boucle pour :

for ma_variable in objet_iterable:
	# Instructions
  • l’indentation est toujours synthaxique,

  • il ne faut pas oublier les :,

  • les objets_iterables classiques que nous utiliserons le plus souvent sont :

    • une liste,

    • l’itérateur range (ex : for i in range(10):),

    • un tableau numpy (ex : for i in np.linspace(0,10):), que nous verrons plus tard dans le cours sur le module numpy.

Remarque

l’intérêt des itérateurs est qu’il n’y a pas de stockage de tous les éléments de la liste mais seulement de l’élément courant (gros gain de place mémoire).

2.2.1.1. boucle à l’aide de l’itérateur range#

L’itérateur classique que nous utiliserons toujours est range

Il s’utilise de plusieurs façons selon les arguments qui lui sont passés :

# boucle de 0 à entier_fin - 1
for k in range(entier_fin):
    # Instructions

# boucle de entier_debut à entier_fin - 1
for k in range(entier_debut, entier_fin):
    # Instructions

# boucle de entier_debut à entier_fin - 1 par pas de entier_pas
for k in range(entier_debut, entier_fin, pas):
    # Instructions
help(range)
Help on class range in module builtins:

class range(object)
 |  range(stop) -> range object
 |  range(start, stop[, step]) -> range object
 |  
 |  Return an object that produces a sequence of integers from start (inclusive)
 |  to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
 |  start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
 |  These are exactly the valid indices for a list of 4 elements.
 |  When step is given, it specifies the increment (or decrement).
 |  
 |  Methods defined here:
 |  
 |  __bool__(self, /)
 |      True if self else False
 |  
 |  __contains__(self, key, /)
 |      Return key in self.
 |  
 |  __eq__(self, value, /)
 |      Return self==value.
 |  
 |  __ge__(self, value, /)
 |      Return self>=value.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __getitem__(self, key, /)
 |      Return self[key].
 |  
 |  __gt__(self, value, /)
 |      Return self>value.
 |  
 |  __hash__(self, /)
 |      Return hash(self).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __le__(self, value, /)
 |      Return self<=value.
 |  
 |  __len__(self, /)
 |      Return len(self).
 |  
 |  __lt__(self, value, /)
 |      Return self<value.
 |  
 |  __ne__(self, value, /)
 |      Return self!=value.
 |  
 |  __reduce__(...)
 |      Helper for pickle.
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __reversed__(...)
 |      Return a reverse iterator.
 |  
 |  count(...)
 |      rangeobject.count(value) -> integer -- return number of occurrences of value
 |  
 |  index(...)
 |      rangeobject.index(value) -> integer -- return index of value.
 |      Raise ValueError if the value is not present.
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  start
 |  
 |  step
 |  
 |  stop
entier_debut, entier_fin, entier_pas = -10, 10, 2
print(f"boucle sur range({entier_fin}) : ")
for k in range(entier_fin):
    print(f"{k:2d} ", end='')
boucle sur range(10) : 
 0  1  2  3  4  5  6  7  8  9 
print(f"boucle sur range({entier_debut}, {entier_fin}) : ")
for k in range(entier_debut, entier_fin):
    print(f"{k:2d} ", end='')
boucle sur range(-10, 10) : 
-10 -9 -8 -7 -6 -5 -4 -3 -2 -1  0  1  2  3  4  5  6  7  8  9 
print(f"boucle sur range({entier_debut}, {entier_fin}, {entier_pas}) : ")
for k in range(entier_debut, entier_fin, entier_pas):
    print(f"{k:2d} ", end='')
boucle sur range(-10, 10, 2) : 
-10 -8 -6 -4 -2  0  2  4  6  8 

Exercice

  1. Calculez à l’aide d’une boucle la somme de tous les entiers entre \(0\) et \(100\).

  2. Calculez le produit de tous les nombres impairs compris entre \(0\) et \(100\).

Hide code cell source
N = 100
s = 0
for k in range(N+1):
    s += k
print(f"La somme des entiers entre 0 et {N} vaut {s}")
La somme des entiers entre 0 et 100 vaut 5050
Hide code cell source
N = 100
p = 1
for k in range(1, N+1, 2):
    p *= k
print(f"Le produit des entiers impairs entre 0 et {N} vaut \n{p}")
Le produit des entiers impairs entre 0 et 100 vaut 
2725392139750729502980713245400918633290796330545803413734328823443106201171875

2.2.1.2. parcours des éléments d’une liste#

Voici un premier exemple de parcours des éléments d’une liste.

# Boucle sur les éléments d'une liste 
jours = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']
for k in range(len(jours)):
    print(f"{jours[k]} est un des jours de la semaine !")
lundi est un des jours de la semaine !
mardi est un des jours de la semaine !
mercredi est un des jours de la semaine !
jeudi est un des jours de la semaine !
vendredi est un des jours de la semaine !
samedi est un des jours de la semaine !
dimanche est un des jours de la semaine !
for jour in jours :
    print(f"{jour} est un des jours de la semaine !")
lundi est un des jours de la semaine !
mardi est un des jours de la semaine !
mercredi est un des jours de la semaine !
jeudi est un des jours de la semaine !
vendredi est un des jours de la semaine !
samedi est un des jours de la semaine !
dimanche est un des jours de la semaine !

Si l’on souhaite également avoir un compteur pour afficher le numéro du jour de la semaine, il y a plusieurs façons de faire.

numero = 0
for jour in jours:
    numero += 1
    print(f"{jour} est le jour numéro {numero} dans la semaine !")
lundi est le jour numéro 1 dans la semaine !
mardi est le jour numéro 2 dans la semaine !
mercredi est le jour numéro 3 dans la semaine !
jeudi est le jour numéro 4 dans la semaine !
vendredi est le jour numéro 5 dans la semaine !
samedi est le jour numéro 6 dans la semaine !
dimanche est le jour numéro 7 dans la semaine !
for numero, jour in enumerate(jours, start=1):
    print(f"{jour} est le jour numéro {numero} dans la semaine !")
lundi est le jour numéro 1 dans la semaine !
mardi est le jour numéro 2 dans la semaine !
mercredi est le jour numéro 3 dans la semaine !
jeudi est le jour numéro 4 dans la semaine !
vendredi est le jour numéro 5 dans la semaine !
samedi est le jour numéro 6 dans la semaine !
dimanche est le jour numéro 7 dans la semaine !
print(list(enumerate(jours)))
[(0, 'lundi'), (1, 'mardi'), (2, 'mercredi'), (3, 'jeudi'), (4, 'vendredi'), (5, 'samedi'), (6, 'dimanche')]
help(enumerate)
Help on class enumerate in module builtins:

class enumerate(object)
 |  enumerate(iterable, start=0)
 |  
 |  Return an enumerate object.
 |  
 |    iterable
 |      an object supporting iteration
 |  
 |  The enumerate object yields pairs containing a count (from start, which
 |  defaults to zero) and a value yielded by the iterable argument.
 |  
 |  enumerate is useful for obtaining an indexed list:
 |      (0, seq[0]), (1, seq[1]), (2, seq[2]), ...
 |  
 |  Methods defined here:
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __next__(self, /)
 |      Implement next(self).
 |  
 |  __reduce__(...)
 |      Return state information for pickling.
 |  
 |  ----------------------------------------------------------------------
 |  Class methods defined here:
 |  
 |  __class_getitem__(...) from builtins.type
 |      See PEP 585
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
l1 = range(1, 100)
l2 = ['lundi', 'mardi', 'mercredi']
for num, jour in zip(l1, l2):
    print(num, jour)
1 lundi
2 mardi
3 mercredi

2.2.2. Boucle while#

Une boucle while est utilisée lorsque le nombre d’itérations n’est pas connu à l’avance : on arrête la boucle quand une certaine condition est satisfaite. La syntaxe est la suivante

while condition:
    # itérations

Dans ce cas, la boucle est exécutée tant que la condition est vérifiée (retourne le booléen True).

Attention

Il est très facile de fabriquer une boucle infinie avec la commande while. On ajoute donc souvent un compteur de sécurité pour éviter ce problème.

Voici un exemple qui permet de calculer la plus grande puissance de \(2\) (négative) notée \(2^{-k}\) telle que \(1+2^{-k}=1\). Ce résultat n’est pas possible dans le monde des mathématiques mais évident dans le monde numérique…

k, kmax = 0, 100
pk = 1
while 1+pk != 1 and k < kmax:
    k += 1
    pk /= 2
if k == kmax:
    print("Critère d'arrêt atteint !")
print(f"1+2^({-k}) = {1+pk}")
print(f"1+{pk} = {1+pk}")
1+2^(-53) = 1.0
1+1.1102230246251565e-16 = 1.0

Quand faut-il utilisez une boucle while?

  • pour une récurrence complexe,

  • ou lorsque le nombre d’itérations n’est pas connu à l’avance.

récursivité

Généralement, une fonction récursive peut être remplacée par une boucle while et vis-versa. Dans la plupart des cas, la fonction récursive sera plus élégante mais généralement plus lente. Remarque : dans certains langages compilés (C++ entre autre), la différence de vitesse d’exécution tant à disparaître.

Exercice : le nombre d’or

Le nombre d’or \(\phi\) vérifie :

\[ \phi = 1+ \frac{1}{1+ \frac{1}{1+ \frac{1}{1+ \frac{1}{1+ \frac{1}{1+ %5 \dots }}}}} \]

Comme \(1 > 1/(1+x) > 1/2\) pour tout \(x \in ]0;1[\), on en déduit l’encadrement :

\[\begin{split} \begin{cases} u_n & = 1 +\underbrace{\big( 1 + \big( 1 + \big( 1 + \dots 1 \big)^{-1} \dots \big)^{-1} \big)^{-1}}_{n\text{ fois} }\\ v_n & = 1 +\underbrace{\big( 1 + \big( 1 + \big( 1 + \dots 2 \big)^{-1} \dots \big)^{-1} \big)^{-1}}_{n\text{ fois} }\\ & \min(u_n,v_n) < \phi < \max(u_n, v_n) \end{cases} \end{split}\]

On a donc les relations de récurrences \(u_{n+1} = 1+ 1/u_n\) et \(v_{n+1} = 1+ 1/v_n\) avec \(u_0=1\) et \(v_0=2\).

Codez l’algorithme qui, à partir d’un entier \(n\), affiche une approximation du nombre d’or avec \(n\) chiffres significatifs derrière la virgule.

Hide code cell source
val_or = 1.61803398874989484820458683436563811772
n = 15
u, v, e = 1, 2, 10**(-n)
k, kmax = 0, 100
while (v-u) > e and k < kmax:
    v, u = 1 + 1/u, 1 + 1/v
    k += 1
val_app = .5*(u+v)
print(f"Nombre d'or avec {n} chiffres corrects après la virgule : {val_app:.{n}f} (erreur = {val_app-val_or:10.3e})")
if k < kmax:
    print(f"Convergence atteinte en {k} itérations")
else:
    print("Attention : la convergence n'est peut-être pas atteinte !")
Nombre d'or avec 15 chiffres corrects après la virgule : 1.618033988749895 (erreur =  0.000e+00)
Convergence atteinte en 37 itérations