La boucle while en Python

Avec la boucle while Python, un bloc de code est exécuté de manière répétée tant qu’une condition est vraie. Les boucles while sont utilisées en premier lieu dans Python lorsque le nombre d’itérations nécessaires n’est pas déterminé à l’avance. Nous expliquons comment fonctionne la boucle while en Python.

Conseil

Apprenez à écrire du code Python de manière autonome grâce à notre tutoriel Python.

Qu’est-ce que la boucle while en Python ?

La boucle while en Python est une structure de contrôle. Les structures de contrôle déterminent le chemin de code à suivre lors de l’exécution. Les boucles sont généralement utilisées pour exécuter un bloc de code de manière répétée. Voici un aperçu des principales structures de contrôle en Python :

Structure de contrôle en Python Explication
Branchement if-else Exécute le bloc de code une fois, dans la mesure où la condition est vraie
Boucle while en Python Exécute le bloc de code de manière répétée tant que la condition est vraie
Boucle for en Python Itère sur les éléments d’une collection en exécutant un bloc de code pour chaque élément

Généralement, les débutants en programmation qui ne connaissent pas les boucles tentent de reproduire leur fonctionnalité. Au lieu de définir une structure de données avec plusieurs éléments et de la traiter avec une boucle, ils définissent une variable distincte pour chaque ensemble. De plus, le code pour le traitement des ensembles est dupliqué. Il en résulte un code sous-optimal, illustré ici par l’exemple de l’affichage de trois personnes avec leur nom et leur âge :

person1 = ('Jim', 17)
person2 = ('Jack', 42)
person3 = ('John', 63)
print(f"{person1[0]}, {person1[1]} years old")
print(f"{person2[0]}, {person2[1]} years old")
print(f"{person3[0]}, {person3[1]} years old")

En plus de la duplication indésirable du code, cette approche pose un problème logistique : si le nombre d’ensembles n’est connu qu’au moment de l’exécution, les variables individuelles correspondantes ne peuvent pas être définies à l’avance. Pour résoudre ce problème, on utilise des collections d’éléments et des boucles pour les traiter.

Si l’on sait au moment de l’exécution combien de répétitions sont nécessaires, la boucle for en Python donne le meilleur résultat. A titre d’exemple, nous reproduisons le code de sortie de trois personnes avec leur âge à l’aide de la boucle for. Le code fonctionne sans duplication et ne contient que deux variables, quel que soit le nombre d’enregistrements :

people = ('Jim', 'Jack', 'John')
ages = (17, 42, 63)
for person, age in zip(people, ages):
    print(f"{person}, {age} years old")

Contrairement à la boucle for, une boucle while en Python est utilisée lorsque l’on ne sait pas combien de répétitions seront nécessaires. Un exemple est l’échange de messages via une connexion ouverte. Tant que la connexion reste ainsi, les messages sont traités. Une instruction if dans le corps de la boucle évalue un signal et met fin à la connexion si on le souhaite :

while connection_open():
    print('Ready to receive')
    process_messages()
    if should_close_connection():
        close_connection()
# once loop terminates
print('Connection was closed')

Les boucles while sont donc utilisées pour des répétitions en principe illimitées. Des exemples connus sont les distributeurs automatiques de billets, l’invite de commande Linux et la « Read-Eval-Print Loop » (REPL) de Python. Voici une illustration schématique d’une exécution de REPL par une boucle sans fin while :

# Loop
while True:
    # Read user input
    user_input = read_input()
    # Evaluate input and produce result
    result = eval(user_input)
    # Print result
    print(result)

Comment fonctionne la boucle while en Python ?

La boucle while de Python fonctionne de manière similaire à l’instruction de branchement if-else de Python. Les deux structures de contrôle se composent de deux parties :

  1. Une condition qui est évaluée
  2. Un corps contenant des instructions

La différence réside dans le nombre de fois où le corps est exécuté. Le corps d’une instruction if est exécuté au maximum une fois :

if condition:
    run_once()

Contrairement à l’instruction if, le corps de la boucle while en Python peut être exécuté plusieurs fois :

while condition:
    run_again()

Lors du passage d’une boucle while Python sur un modèle classique :

  1. La condition est évaluée
  2. Si la condition est vraie, le corps de la boucle est exécuté
  3. La condition est à nouveau évaluée :
    1. Si la condition est toujours vraie, le processus se répète
    2. Si la condition n’est pas vraie, la boucle se termine

Comme l’instruction if, une boucle while en Python peut disposer d’un bloc else optionnel. Le bloc else est exécuté une seule fois si la condition est ou devient fausse :

while False:
    # this code doesn't loop
    never_runs()
else:
    # instead, this code runs once
    runs_once()

Quelle est la différence entre la boucle for et la boucle while dans Python ?

La boucle while dans Python est également apparentée à la boucle for. Les deux exécutent un bloc de code de manière répétée. On parle également d’« itérations ». La différence réside dans le nombre de répétitions.

En Python, les boucles for sont utilisées en premier lieu pour itérer sur les éléments d’une collection. Le nombre maximal d’itérations est limité par la longueur de la collection. Nous allons itérer sur la lettre du mot « Python » et éditer chaque lettre séparément :

for letter in 'Python':
    print(letter)

La boucle while de Python n’est pas spécialisée dans l’itération sur des collections et peut être utilisée de manière flexible. En général, la boucle while est la construction de boucle de base. La fonctionnalité d’une boucle for peut être construite à l’aide d’une boucle while ; l’inverse n’est pas vrai.

Regardons quelques exemples. Nous reproduisons la fonctionnalité d’une boucle for traditionnelle avec une variable de boucle numérique en tant que boucle while Python. Pour cela, nous définissons une variable compteur à l’extérieur de la boucle et nous incrémentons sa valeur à l’intérieur du corps de la boucle :

counter = 0
limit = 10
while counter < limit:
    print(counter)
    counter += 1

La boucle for équivalente est plus courte et plus directe :

for counter in range(10):
    print(counter)

Il en va de même lorsque nous utilisons une boucle while pour itérer sur la lettre d’un mot. Nous utilisons un itérateur et la fonction next(). Lorsque l’itérateur est épuisé, « None » est renvoyé à la place d’une lettre ; la boucle se termine. Le code qui en résulte est nettement plus compliqué que la boucle for équivalente. Il est évident que la boucle while de Python n’est pas l’outil optimal pour résoudre ce problème :

word = 'Python'
letters = iter(word)
letter = ''
while letter is not None:
    letter = next(letters, None)
    if letter:
        print(letter)

Attention aux boucles infinies while dans Python

Les boucles while en Python sont notamment intéressantes pour implémenter des boucles sans fin. Cela peut sembler paradoxal au premier abord. En effet, les boucles infinies produites par erreur sont redoutables. La condition ne devient jamais fausse et le programme reste bloqué :

while True:
    print("Forever…")

Il existe de nombreux cas d'application pour les boucles infinies intentionnelles. Une boucle infinie while produite par inadvertance est généralement causée par une expression qui est toujours évaluée à True. En voici un exemple :

while 1 == 1 + 1 - 1:
    print("And ever…")
Conseil

Si vous êtes pris dans une boucle sans fin while dans le REPL Python, la combinaison de touches Ctrl + C vous aidera. Elle envoie un signal d’arrêt à l’interpréteur Python, qui interrompt l’exécution de la boucle.

Annuler et ignorer les passages d’une boucle while en Python

En règle générale, une boucle while itère jusqu’à ce que la condition de la boucle ne soit plus vraie. Une astuce courante consiste à utiliser une variable « flag » comme condition. Pour cela, une variable booléenne est définie en dehors de la boucle et évaluée dans la condition de boucle. Lorsqu’une certaine condition est atteinte à l’intérieur du corps de la boucle, nous activons alors le flag. Lors de l’évaluation de la condition avant le prochain passage, la nouvelle valeur entraîne la fin de la boucle :

aborted = False
while not aborted:
    print("Still going…")
    if some_cond:
        # will prevent next iteration
        aborted = True

Ce modèle est fréquent, mais il n’est pas très élégant. Que se passe-t-il si, dans le corps de la boucle, du code supplémentaire est ajouté après la variable flag ? Celui-ci devra alors être ignoré. Pour cela, Python connaît l’instruction break dans les boucles while.

Si une instruction break est exécutée dans une boucle, la boucle se termine immédiatement. L’instruction break dans les boucles est donc similaire à l’instruction return dans les fonctions. Cependant, break ne renvoie pas de valeur. Il est courant d’utiliser l’instruction break pour interrompre une boucle sans fin :

while True:
    print("Still going…")
    if some_cond:
        break
        # we never get here
        print("We shouldn't be here")
# we end up here after breaking
print("Done.")

L’instruction continue est proche de l’instruction break. Si une instruction continue est exécutée dans le corps de la boucle, le code suivant est ignoré. L’itération suivante se poursuit. Avec break et continue, il est possible d’implémenter des menus simples, basés sur du texte, comme on les connaît des premiers jeux informatiques :

# `continue` takes us here
while True:
    print("Press G to start a new game")
    print("Press S to see stats")
    print("Press M for main menu")
    print("Press Q to quit")
    
    key_press = input("Your choice \n")[0].upper()
    print(f"You pressed {key_press}")
    
    if key_press == "G":
        # start game routines
        print("Starting game …")
    elif key_press == "S":
        # show stats
        print("Showing stats …")
    elif key_press == "M":
        # back to main menu
        print("Returning to menu")
        continue
    elif key_press == "Q":
        # break out of loop
        print("Quitting")
        break
    else:
        print("Unknown command. Try again")
# `break` takes us here
...

Sortir des boucles while Python imbriquées

L’utilisation de boucles imbriquées les unes dans les autres conduit rapidement à la confusion. Il est alors utile de sortir de la dernière boucle lancée à l’aide d’une instruction break. Mais que se passe-t-il si l’on souhaite quitter une boucle située au-dessus ? Dans ce cas, il n’existe pas de mot-clé spécifique ; en principe, une solution au moyen d’une variable flag fonctionne.

Il est plus élégant de sortir des boucles while imbriquées en Python en combinant intelligemment break, continue et else. Nous construisons notre boucle afin que la boucle externe soit interrompue lorsqu’une instruction break est exécutée dans la boucle interne. Pour ce faire, nous utilisons une instruction continue à l’intérieur du bloc else interne afin de sauter le break externe si nécessaire :

# `continue` takes us here
while outer_cond:
    while inner_cond:
        ...
        if some_cond:
            print("Breaking out of inner loop")
            break
    # no inner `break` occured
    else:
        print("Continuing outer loop")
        # skip rest of outer loop body
        continue
    # we only get here if inner `break` occured
    print("Breaking out of outer loop")
    break
# outer `break` takes us here
...

Comment utiliser la boucle while dans Python ?

Dans la pratique, on trouve de nombreux cas d’utilisation de la boucle while en Python. En général, les boucles while sont utilisées pour des algorithmes dont le nombre de répétitions n’est pas déterminé à l’avance ou change pendant l’exécution. Les boucles while sont souvent utilisées en combinaison avec d’autres structures de contrôle telles que les embranchements et les instructions try-else. Considérons ces quelques exemples.

Utiliser une boucle while en Python dans une collection

Pour itérer sur les éléments d’une collection, la boucle for s’impose en Python. Du moins, c’est le cas si nous ne modifions pas la collection à partir du corps de la boucle. Mais qu’en est-il si nous effectuons des modifications en parcourant les éléments ? Si nous voulons supprimer des éléments de la collection lors de l’itération ?

Les boucles for peuvent provoquer des erreurs étranges si la collection sous-jacente change pendant l’itération. La boucle while est la plus appropriée en Python. Nous utilisons la collection directement comme condition de la boucle while. Tant que la collection contient des éléments, elle est évaluée comme vraie dans le contexte booléen. Si la collection est vide, la boucle s’interrompt :

pieces = ['x', 'o', 'o', 'o', 'x', 'o', 'x', 'x']
while pieces:
    piece = pieces.pop()
    print(f"Removed {piece}")
# test
assert pieces == []

Implémenter sa propre fonction range() avec la boucle while dans Python

Les boucles while de Python permettent d’implémenter des générateurs. Un générateur est une fonction qui utilise l’instruction yield et qui génère des valeurs à la demande. Nous écrivons notre propre implémentation de la fonction range(). Nous utilisons l’instruction yield dans une boucle while pour générer des nombres consécutifs. Lorsque l’instruction yield est atteinte, une valeur est générée et la boucle se met en pause :

def my_range(start, stop):
    # only positive ranges implemented
    if stop <= start:
        return None
    current = start
    while current < stop:
        yield current
        # next call of next() continues here
        current += 1
# test
assert list(my_range(7, 9)) == list(range(7, 9))

Optimiser un modèle avec la boucle while de Python

Un modèle est calculé sur la base d’un ensemble de paramètres. Les paramètres sont ensuite ajustés et le modèle est recalculé. Une fonction cible permet d’estimer si la modification des paramètres a permis d’obtenir un meilleur modèle. Si c’est le cas, le processus est répété. Il est ainsi possible de trouver de manière itérative des paramètres optimaux pour le modèle.

Normalement, le modèle se stabilise après quelques répétitions, de sorte que le progrès est de plus en plus faible. Si la progression tombe en dessous d’une certaine valeur seuil, nous interrompons l’optimisation. Pour garantir que la boucle se termine, nous limitons en outre le nombre maximal de passages. Voici schématiquement une approche d’optimisation de modèle utilisant une boucle while Python :

limit = 5
round = 0
progress = True
while progress and round < limit:
    # attempt next optimization
    round += 1
    # compute optimized parameters
    params = optimize(params)
    # make a copy of the old model
    old_model = model
    # compute new model using optimized parameters
    model = run(model, params)
    # worthwhile to further optimize?
    progress = has_improved(model, old_model)

Établir une connexion en Python avec la boucle while et try-except

L’établissement d’une connexion peut échouer. Il est donc souhaitable d’implémenter l’établissement de la connexion avec plusieurs tentatives. Comme nous ne pouvons pas savoir à l’avance combien de tentatives seront nécessaires, nous utilisons une boucle while en Python. Nous limitons également le nombre maximal de tentatives. Si aucune des tentatives n’a réussi, nous nous arrêtons avec un message d’erreur.

Voici une solution schématique : nous utilisons une instruction try-except pour intercepter une erreur lors de l’établissement de la connexion. L’utilisation d’une instruction break dans le bloc try et d’une instruction continue dans le bloc except nous permet de garantir une itération correcte. Si la connexion échoue, nous essayons à nouveau avec continue. Si la connexion est établie, nous terminons la boucle avec break :

max_tries = 10
attempt = 0
conn = None
# `continue` takes us here
while attempt < max_tries:
    attempt += 1
    print("Trying to get a connection")
    try:
        # might raise `ConnectionException`
        conn = get_connection()
        # got our connection
        break
    # `get_connection()` raised `ConnectionException`
    except ConnectionException:
        print("Something went wrong. Trying again")
        continue
# went through `max_tries` unsuccessful connection attempts
else:
    assert conn is None
    print("Unable to connect")
# `break` takes us here
assert conn is not None
print("Connection established")

Utiliser la boucle while de Python pour itérer sur des structures récursives

La boucle while de Python est bien adaptée à la résolution de problèmes récursifs. Ainsi, la boucle convient pour itérer sur :

    • Des listes imbriquées
    • Des structures arborescentes
    • Des graphes

Voyons comment cela fonctionne avec l’exemple d’une poupée Matriochka. Ce jouet bien connu est composé de poupées imbriquées les unes dans les autres. De l’extérieur, le nombre de niveaux n’est pas visible. Au lieu de cela, nous procédons de manière itérative : nous ouvrons la poupée la plus extérieure et regardons ce que nous trouvons à l’intérieur. S’il s’agit d’une autre matriochka, nous répétons le processus. C’est exemple est un cas typique d’utilisation d’une boucle while.

Nous modélisons la matriochka comme une liste imbriquée avec un seul élément à la fois. Soit une autre liste est incluse, soit un objet qui n’est pas une liste. Nous itérons sur la matriochka tant qu’elle est une liste. A l’intérieur du corps de la boucle, nous utilisons une affectation pour descendre d’un niveau. A un moment donné, nous trouvons un élément qui n’est pas une liste. Nous avons alors trouvé l’objet final qu’il contient et interrompons l’itération :

def open_matroshka(matroshka):
    """
    * Matroshka dolls stacked five levels deep, with `None` inside:
    `matroshka = [[[[[None]]]]]`
    """
    while type(matroshka) is list:
        print("Opening the next matroshka")
        # go one level deeper
        matroshka = matroshka.pop()
    else:
        print(f"Reached the bottom and found {matroshka}")
        return matroshka
# test
matroshka = [[[[[None]]]]]
assert open_matroshka(matroshka) is None

Cette approche simple fonctionne quelle que soit la profondeur des poupées imbriquées. Notre algorithme creuse jusqu’au fond et fait apparaître l’objet qu’il contient. C’est toute la magie de la boucle while de Python en action.

Se déplacer sur un plateau de jeu avec une boucle while Python

Une utilisation fréquente des boucles while en Python consiste à déplacer une pièce sur un plateau de jeu. Si l’on veut être sûr d’aller sur toutes les cases, il faut deux boucles imbriquées l’une dans l’autre. Il faut alors tenir compte du temps d’exécution : pour les grands espaces, le programme peut être très long à exécuter.

Nous mettons en œuvre une simple « marche aléatoire », dans laquelle une pièce est déplacée de manière aléatoire sur le plateau de jeu jusqu’à ce qu’elle se trouve à une destination. Un tel modèle de mouvement se retrouve par exemple dans le déplacement d’une particule dans un liquide ou d’une mouche qui vole dans l’espace. Comme nous ne savons pas à l’avance combien d’itérations seront nécessaires, nous utilisons une boucle while Python.

Nous définissons d’abord la fonction random_walk(), qui contient la boucle while. Avec l’opérateur Python, nous vérifions si la position actuelle est la destination. Si ce n’est pas le cas, l’itération se poursuit :

def random_walk(board = (10, 10), goal = (4, 4), start = (0, 0)):
    # ensure arguments are valid
    if not (goal[0] in range(board[0]) and goal[1] in range(board[1]) and start[0] in range(board[0]) and start[1] in range(board[1])):
        print(f"Goal {goal} and / or start position {start} outside of board with dimensions {board}")
        return None, 0
    steps = 0
    pos = start
    # as long as we haven't reached the goal
    while not pos == goal:
        # move to neighboring position
        pos = get_neighbor(pos, board)
        steps += 1
        print(f"Moved to position {pos}")
    print(f"Reached goal at {pos} after {steps} steps")
    return pos, steps

Nous définissons également une fonction auxiliaire get_neighbor() qui renvoie un champ possible autour d’une position donnée :

def get_neighbor(pos, bounds):
    from random import choice
    """
        x = 0    . . .    m
            - - - - - - - -
     y = 0 |
           |
         . |           (x, y-1)
         . |  (x-1, y) (x, y)  (x+1, y)
         . |           (x, y+1)
           |
         n |
   
    """
    x, y = pos
    # computer neighbors
    neighbors = [ (x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1) ]
    # filter out neighbors that are outside of bounds
    neighbors = [ pos for pos in neighbors if 0 <= pos[0] < bounds[0] and 0 <= pos[1] < bounds[1] ]
    # select a random neighbor
    neighbor = choice(neighbors)
    return neighbor

Ensuite, nous testons l’implémentation de notre Random Walk :

random_walk(board = (10, 10), goal = (4, 4), start = (5, 7))