5. Contrôle de flux et boucles

Nous avons précédemment fait appel aux variables logiques, en sélectionnant des éléments dans un tableau. Le contrôle de flux utilise également des variables logiques pour permettre à différents codes d'être exécutés selon que certaines conditions sont remplies ou non. Le contrôle de flux en Python se présente sous deux formes: instruction conditionnelle et les boucles.

5.1 Contrôle des espaces et des flux

Python utilise des modifications d’espace pour indiquer le début et la fin des blocs de contrôle de flux, l’indention est donc importante. Par exemple, lors de l'utilisation des blocs if. . . elif. . . else, tous les blocs de contrôle doivent avoir le même niveau d'indentation et toutes les instructions à l'intérieur des blocs de contrôle doivent avoir le même niveau d'indentation. Revenir au niveau d'indentation précédent indique à Python que le bloc est terminé. La meilleure pratique consiste à utiliser uniquement des espaces (et non des tabulations) et 4 espaces lors du démarrage d'un nouveau niveau d'indentation. Ce qui représente un équilibre raisonnable entre lisibilité et gaspillage d'espace.

5.2 if ... elif ... else

Les blocs if ... elif ... else commencent toujours par une instruction if immédiatement suivie d'une expression logique scalaire. elif et else sont facultatifs et peuvent toujours être replacés par des instructions if imbriquées aux dépens d'une logique plus complexe et d'une imbrication plus profonde. La forme générique d'un block if ... elif ... else est donnée par

if logical_1: Code to run if logical_1 elif logical_2: Code to run if logical_2 and not logical_1 ... ... else: Code to run if all previous logicals are false Une forme simple qui revient souvent

if logical: Code to run if logical true

ou

if logical: Code to run if logical true else: Code to run if logical false

Un petit exemple

In [ ]:
x = 5
if x<5:
    x = x + 1

elif x>5:
    x = x - 1
else:
    x = x * 2

print(x)

5.3 for

Les boucles for commencent par for item dans iterable:, et la structure générique d’une boucle for est

for item in iterable: Code to run

item est un élément d'un objet itérable de Python. Les exemples les plus courants sont les range, les listes, les tuples, les tableaux ou les matrices. La boucle for parcourt tous les éléments de l'objet itérable, en commençant par l’élément $0$ et se poursuivant jusqu’à l’élément final. Lors de l'utilisation de tableaux multidimensionnels, seule la dimension extérieure est directement itérable. Par exemple, si $x$ est un tableau à $2$ dimensions, les éléments itérables sont $x[0]$, $x[1]$ et ainsi de suite.

In [ ]:
import numpy as np
count = 0
for i in range(100):
    count += i
    
count = 0
x = np.linspace(0,500,50)
for i in x:
    count += i    
   
count = 0
x = list(np.arange(-20,21))
for i in x:
    count += i    

La première boucle itérera sur $i = 0, 1, 2, \ldots,99$. La seconde boucle sur les valeurs produites par la fonction linspace, qui renvoie un tableau avec $50$ points uniformément espacés compris entre $0$ et $500$ inclus. La dernière boucle sur $x$, un vecteur construit à partir d'un appel à liste(arange (-20,21)), qui produit une liste contenant la séquence $-20, -19, \ldots, 0, \ldots, 19,20$. Les range, tableaux et listes, sont itérables. La clé pour comprendre Le comportement en boucle est le suivant: il effectue toujours une itération sur les éléments de l'iterable dans l'ordre dans lequel ils sont présentés (c'est-à-dire iterable[0], iterable[1],...).

Les boucles peuvent aussi être imbriquées

In [ ]:
count = 0
for i in range(10):
    for j in range(10):
        count += j

ou peut contenir des variables de contrôle de flux

In [ ]:
returns = randn(100)
count = 0
for ret in returns:
    if ret<0:
        count += 1

Cette boucle for peut être exprimée de manière équivalente en utilisant range en tant qu'itérateur et len pour obtenir le nombre d'éléments contenus dans l'objet itérable.

In [ ]:
returns = randn(100)
count = 0
for i in range(len(returns)):
    if returns[i]<0:
        count += 1

Enfin, ces idées peuvent être combinées pour produire des boucles imbriquées avec contrôle de flux.

In [ ]:
x = zeros((10,10))
for i in range(size(x,0)):
    for j in range(size(x,1)):
        if i<j:
            x[i,j]=i+j;
        else:
            x[i,j]=i-j

ou des boucles contenant des boucles imbriquées exécutées en fonction d'une instruction de contrôle de flux.

In [ ]:
x = zeros((10,10))
for i in range(size(x,0)):
    if (i % 2) == 1:
        for j in range(size(x,1)):
            x[i,j] = i+j
    else:
        for j in range(int(i/2)):
            x[i,j] = i-j

Important: La variable itérable ne doit pas être réaffectée une fois dans la boucle. Considérons, par exemple,

In [ ]:
x = range(10)
for i in x:
    print(i)
    print('Length of x:', len(x))
    x = range(5)

Il n’est pas prudent de modifier l'objet itérable lorsqu’on boucle dessus. Cela signifie que l'itérable ne doit pas changer de taille, ce qui peut se produire lors de l'utilisation d'une liste et des fonctions pop(), insert() ou append() ou du mot clé del. La boucle ci-dessous ne se terminera jamais (sauf pour l’instruction if qui coupe la boucle) puisque $L$ est étendu à chaque itération.

In [ ]:
L = [1, 2]
for i in L:
    print(i)
    L.append(i+2)
    if i>5:
        break

Enfin, les boucles for peuvent être utilisées avec $2$ itérables encapsulés dans enumerate, ce qui permet d’accéder directement aux éléments de l’itérable, ainsi que leur indice dans l’itéré.

In [ ]:
x = np.linspace(0,100,11)
for i,y in enumerate(x):
    print('i is :', i)
    print('y is :', y)

5.3.1 Les espaces

Comme les blocs de contrôle de flux if...elif... else, les boucles for sont sensibles aux espaces. L'indentation de la ligne immédiatement en dessous de l'instruction for détermine l'indentation que doivent avoir toutes les instructions du bloc.

5.3.2 break

Une boucle peut être terminée tôt en utilisant break. break est généralement utilisé après une instruction if pour mettre fin à la boucle prématurément si une condition est remplie.

In [ ]:
x = randn(1000)
for i in x:
    print(i)
    if i > 2:
        break

Etant donné que les boucles for parcourent une valeur itérable de taille fixe, break est généralement plus utile dans les boucles while.

5.3.3 continue

continue peut être utilisé pour ignorer une itération d'une boucle et revenir immédiatement au début de la boucle en utilisant l'élément suivant dans iterable. continue est couramment utilisé pour éviter un niveau d'imbrication, comme dans les deux exemples suivants

In [ ]:
x = randn(10)
for i in x:
    if i < 0:
        print(i)
        
        
for i in x:
    if i >= 0:
        continue
    print(i)

Éviter les niveaux excessifs d'indentation est essentiel dans la programmation Python. 4 espaces est généralement considéré comme le niveau maximum raisonnable. continue est particulièrement utile dans la mesure où il peut être utilisé dans une boucle for pour éviter un creux d'indentation.

5.4 while

Les boucles while sont utiles lorsque le nombre d'itérations nécessaires dépend du résultat du contenu de la boucle. Lorsqu'une boucle ne doit s'arrêter que si certaines conditions sont remplies, par exemple lorsque la modification de certains paramètres est faible. La structure générique d'une boucle while est

while logical: Code to run Update logical

Deux choses sont importantes lors de l'utilisation d'une boucle while: premièrement, l'expression logical doit être évaluée à vraie lorsque la boucle commence (ou la boucle sera ignorée) et deuxièmement, les entrées de l'expression logicaldoivent être mises à jour dans la boucle. Si ce n'est pas le cas, la boucle continuera indéfiniment (appuyez sur CTRL + C pour interrompre une boucle interminable dans IPython).

In [ ]:
count = 0
i = 1
while i<10:
    count += i
    i += 1

qui produit le même résultat que

In [ ]:
count=0;
for i in range(0,10):
    count += i

Les boucles while doivent généralement être évitées lorsque les boucles for suffisent. Cependant, il existe des situations où aucun équivalent de boucle for n'existe.

In [ ]:
# randn generates a standard normal random number
mu = abs(100*randn(1))
index = 1
while abs(mu) > .0001:
    mu = (mu+randn(1))/index
    index=index+1

Dans le bloc ci-dessus, le nombre d'itérations requis n'est pas connu à l'avance et puisque randn est la réalisation d'une variable aléatoire normale, il peut prendre de nombreuses itérations jusqu'à ce que ce critère soit rempli.

5.4.1 break

break peut être utilisé dans une boucle while pour terminer immédiatement l'exécution. Normalement, break ne doit pas être utilisé dans une boucle while. Toutefois, break peut être utilisé pour éviter d'exécuter du code en dessous de l'instruction break, même si la condition logique est False

In [ ]:
condition = True
i = 0
x = randn(1000000)
while condition:
    if x[i] > 3.0:
        break # No printing if x[i] > 3
        
    print(x[i])
    i += 1

Il est préférable de mettre à jour l’instruction logique qui détermine si la boucle while doit être exécutée.

In [ ]:
i = 0
while x[i] <= 3:
    print(x[i])
    i += 1

5.4.2 continue

continue peut être utilisé dans une boucle while pour ignorer tout code restant dans la boucle et revenir immédiatement au début de la boucle, qui vérifie ensuite la condition while et exécute la boucle si elle est toujours vraie. Utiliser continue lorsque la condition logique dans la boucle while est False est identique à utiliser break

5.5 try ... except

La gestion des exceptions est une technique de programmation avancée qui peut être utilisée pour produire un code plus résistant (souvent au détriment de la vitesse). Les blocs de code try ... except sont utiles pour exécuter du code qui peut échouer pour des raisons hors du contrôle du programmeur. Dans la plupart des applications numériques, le code doit être déterministe et le code qui échoue peut généralement être évité. Par exemple, si vous ne pouvez pas lire les données d’une source de données qui n’est pas toujours disponible (par exemple, un site Web), try ... except peut être utilisé pour tenter d'exécuter le code, puis pour faire quelque chose si le code ne parvient pas à s'exécuter. La structure générique d'un bloc try ... except est

In [ ]:
try:
    Dangerous Code
except ExceptionType1:
    Code to run if ExceptionType1 is raised
except ExceptionType2:
    Code to run if ExceptionType1 is raised
except:
    Code to run if an unlisted exception type is raised

Un exemple simple de gestion des exceptions se produit lors de la tentative de conversion de texte en chiffres.

In [1]:
text = ('a','1','54.1','43.a')
for t in text:
    try:
        temp = float(t)
        print(temp)
    except ValueError:
        print('Not convertable to a float')
Not convertable to a float
1.0
54.1
Not convertable to a float

5.6 Compréhension des listes

La compréhension de liste est une méthode optimisée pour créer une liste qui peut simplifier le code lorsqu'un objet iterable est bouclé et que les résultats sont sauvegardés dans une liste, éventuellement conditionnée par un test logique. Une simple liste peut être utilisée pour convertir une boucle for qui inclut un ajout en une seule ligne.

In [ ]:
x = arange(5.0)
y = []
for i in range(len(x)):
    y.append(exp(x[i]))

print(y)

z = [exp(x[i]) for i in range(len(x))]
print(z)

On peut inclure une condition if

In [ ]:
x = arange(5.0)
y = []
for i in range(len(x)):
    if floor(i/2)==i/2:
        y.append(x[i]**2)
print(y)

z = [x[i]**2 for i in range(len(x)) if floor(i/2)==i/2]
print(z)

On peut aussi le faire avec des doubles boucles

In [ ]:
x1 = arange(5.0)
x2 = arange(3.0)
y = []
for i in range(len(x1)):
    for j in range(len(x2)):
        y.append(x1[i]*x2[j])
print(y)


z = [x1[i]*x2[j] for i in range(len(x1)) for j in range(len(x2))]
print(z)

# Only when i==j
v = [x1[i]*x2[j] for i in range(len(x1)) for j in range(len(x2)) if i==j]
print(v)

5.7 Compréhension des dictionnaires, tuples et ensembles

In [7]:
import numpy as np
from math import*
x = np.arange(-5.0,5.0)
z_set = {x[i]**2.0 for i in range(len(x))}
print(z_set)

z_dict = {i:exp(i) for i in x}
print(z_dict)

z_tuple = tuple(i**3 for i in x)
print(z_tuple)
{0.0, 1.0, 4.0, 9.0, 16.0, 25.0}
{-5.0: 0.006737946999085467, -4.0: 0.01831563888873418, -3.0: 0.049787068367863944, -2.0: 0.1353352832366127, -1.0: 0.36787944117144233, 0.0: 1.0, 1.0: 2.718281828459045, 2.0: 7.38905609893065, 3.0: 20.085536923187668, 4.0: 54.598150033144236}
(-125.0, -64.0, -27.0, -8.0, -1.0, 0.0, 1.0, 8.0, 27.0, 64.0)

5.8 Un peu d'entraînement

5.8.1

Trouver deux méthodes différentes pour utiliser une boucle for afin de remplir un tableau $5 \times 5$ avec $i \times j$, où $i$ est l'indice de la ligne et $j$ est l'indice de la colonne. L’une utilisera range comme itérable, et l’autre devrait directement itérer sur les lignes, puis les colonnes de la matrice.

5.8.2

Écrire une compréhension de liste qui itérera sur un tableau à une dimension et extraire les éléments négatifs dans une liste. Comment cela peut-il être fait en utilisant seulement des fonctions logiques (pas de boucle explicite), sans la compréhension de la liste (et en retournant un tableau)?