-
- 1.1. Introduction
- 1.2. Les structures de selection
- 1.2.1. Syntaxe d'une structure conditionnelle
- 1.2.2. Syntaxe à plusieurs choix
- 1.2.3. Expression conditionnelle
- 1.2.4. Correspondance : match - case
- 1.3. Structure de répétition
- 1.3.1. Boucle for
- 1.3.2. Boucle while
- 1.3.3. Comparaison entre for et while
- 1.3.4. Boucles imbriquées
- 1.3.5. Expressions de compréhension
- 1.4. Structure de saut (structure d’interruption)
- 1.5. Structure de gestion d'exceptions
- 1.5.1. try/except
- 1.5.2.
raiseetassert
1.1. Introduction¶
Dans les sections précédentes, nous avons vu les types de variables (ou objets) disponibles en Python pour stocker et organiser des données. Nous avons détaillé le type logique (bool) et le type numérique (int, float, complex) dans la partie 1, ensuite les types de collections (str, list, tuple, range, dict et set) dans les parties 2 à 5.
Nous avons aussi vu et réalisé des codes python comportant des instructions (ou commandes) simples comme des affectations et des calculs divers, des expressions, des print, ... etc.
Tous les codes que nous avons vu jusqu'à présent ne comportaient que des commandes exécutées une à une et ligne par ligne. Ce genre de code est à structure séquentielle. Les structures séquentielles forment la base naturelle d’exécution d'un programme selon un flux constant, elles n’introduisent aucun choix, aucun calcul répétitif (boucle) et aucun saut.
Dans cette partie, nous allons nous intéressés aux structures de contrôle. Ces structures sont dites actives ; elles permettent de diriger, de répéter ou d'interrompre le flux d'exécution selon des conditions.
L'organigramme suivant donne une schématisation des structures et des composantes d'un programme Python. Les points 1 et 2 sont traités dans les sections précédentes, nous entamons donc le point 3.
"""
Structure conceptuelle d'un programme Python (par catégories)
A. STRUCTURES DE BASE (Les fondamentaux)
├── Variables et noms
| └── Assignation, typage dynamique, annotations
|
├── Structures de données natives
| ├── Immuables (int, float, str, tuple, frozenset)
| └── Mutables (list, dict, set, bytearray)
|
└── Expressions et opérations
├── Opérateurs (+, -, *, /, //, **, %)
├── Comparaisons (==, !=, <, >, <=, >=)
└── Logiques (and, or, not)
B. STRUCTURES DE CONTRÔLE (contrôlent le flux d'exécution)
├── Séquentiel (par défaut)
| └── Instructions exécutées linéairement
|
├── Branchements conditionnels
| ├── if / elif / else
| └── match / case (Python 3.10+)
|
├── Itérations (boucles)
| ├── for ... in ... (parcours de séquence)
| ├── while ... (répétition conditionnelle)
| └── Compréhensions ([x for x in ...])
|
├── Interruptions de boucle
| ├── break (sort de la boucle)
| └── continue (passe à l'itération suivante)
|
└── Gestion d'exceptions
├── try / except / else / finally
├── raise (lève une exception)
└── assert (vérification avec AssertionError)
C. STRUCTURES D'ORGANISATION DU CODE (modularité)
├── Fonctions de base
│ ├── def (définition)
│ ├── return (valeur de retour)
│ └── lambda (fonction anonyme)
│
├── Modules
│ ├── import ... as ... (module entier)
│ └── from ... import ... (éléments du module)
│
├── Fonctions avancées
│ ├── yield (générateur)
│ ├── yield from (délégation)
│ └── @decorator (métaprogrammation)
│
└── Espaces de noms
├── global (modifie une variable globale)
└── nonlocal (modifie une variable de closure)
D. STRUCTURES DE COMMUNICATION (entrées/sorties)
├── Console
| ├── input() et print()
| └── argparse (arguments en ligne de commande)
|
├── Fichiers
| ├── open(), read(), write()
| └── with ... as ... : gestion automatique
| des ressources
└── Autres sources
└── API, base de données, réseau
E. PROGRAMMATION ORIENTÉE OBJET (paradigme)
├── Définition des classes
| ├── class (déclaration)
| └── __init__ (constructeur)
|
└── Concepts avancés
├── @decorator : méthodes de classe, propriétés
├── Héritage
├── Polymorphisme
└── Encapsulation
"""
''
Brièvement
Les structure de choix permettent de tester des conditions et d'executer des bloc de code selon des cas.
Les structures itératives (ou répétitives) : permettent de répéter un bloc d’instructions tant qu’une condition est remplie.
Les structures de saut : modifient le déroulement normal d’un programme à l’aide d’instructions comme break, continue ou return.
Les structures de gestion d’exceptions : assurent le traitement contrôlé des erreurs lors de l’exécution (try, except, finally).
Ces différentes structures permettent d’écrire des programmes dynamiques, adaptatifs et robustes, capables de réagir à des conditions variées et d’éviter les erreurs d’exécution.
1.2. Les structures de selection¶
Nommées aussi structure conditionnelles
Les structures de selection (ou structures conditionnelles) permettent de choisir entre plusieurs chemins d’exécution selon qu’une condition soit vraie ou fausse.
Ces structures servent à traiter des situations où un bloc d'instructions ne doit être exécuté que si une certaine condition logique est remplie. La condition est une expression booléenne qui peut être vraie ou fausse. Si la condition est vraie, le bloc est exécuté. Sinon, Python continue simplement son exécution de la suite du code en ignorant ce bloc.
1.2.1. Syntaxe d'une structure conditionnelle¶
Une structure conditionnelle avec la syntaxe la plus simple se présente comme suit :
if condition:
bloc_de_commandes
..........
suite_du_code
.............
La commande
if condition:sert à tester si une condition est vraieLes deux points (:) sont obligatoires et font partie intégrante de la syntaxe, ils signalent le début du bloc conditionnel.
L’indentation (généralement 4 espaces au début de ligne) délimite le code qui dépend du if.
- Ce bloc indenté ne sera exécuté que si la condition est évaluée à True.
- Si elle est fausse (False), Python ignore ce bloc et continue l’exécution du programme à la suite du if.
Une fois le bloc conditionnel terminé, on revient à l'alignement normal pour la suite du programme
Exemple:
# Exemple de test if simple
Re = 3000
if Re < 2300:
print(f'Écoulement Laminaire, {Re = }')
# On fait des calculs sous hypothèses
# découlement laminaire
print('Suite du code')
Suite du code
# Exemple 2 de test if simple
# si i < n : on incrément i et on décrémente j
i, j, n = 0, 10, 10
if (i < n):
i += 1
j -= 1
print(f'{i = }, {j = } , {n = }')
i = 1, j = 9 , n = 10
1.2.2. Syntaxe à plusieurs choix¶
Afin de traiter plusieurs choix sous conditions diverses, la structure conditionnelle permet d'imbriquer plusieurs blocs de code avec des tests if, elif et else qui correspondent à si, sinon si, et sinon (ou autrement).
if expression_1: # 1 er test si (obligatoire)
# bloc de commandes 1
...
elif expression_2: # 2 ème test sinon si (optionnel)
# bloc de commandes 2
...
elif ....
...
elif expression_n: # n ème test sinon si (optionnel)
# bloc de commandes n
...
else: # dernier test sinon ou autrement (optionnel)
# bloc de commandes n+1
#suite du code
- si
expression_1est vraie, alors exécuter les commandes dubloc de commandes 1 - sinon si
expression_2est vraie, alors exécuter les commandes du `bloc de commandes 2 - sinon continue les tests si jusqu'au nème
elif - autrement, si aucune des expressions précédentes du
ifet deselifn'est vraie, alors exécuter les commandes dubloc de commandes n+1souselse.
- tous les énoncés d'un bloc doivent être indentés d'un nombre fixe de colonnes (habituellement 4).
- c'est l'indentation qui détermine la fin d'un bloc.
- le nombre de clauses
elifest illimité. - la clause
elseest unique et optionnelle. - les clauses
elifetelsesont optionnelles, dans le sens qu'elles peuvent être omises. - au plus un seul bloc de commandes sera exécuté, celui qui est associé à la première condition vraie même si une des condition qui va suivre est aussi vraie.
- à la fin de ce bloc, Python poursuit avec les énoncés suivants, situés au même niveau d'indentation que les différentes clauses du
if.
# Exemple de test if-elif-else
Re = 5000
if Re < 2300:
print(f'Écoulement Laminaire, {Re = } < 2300')
# On fait des calculs sous hypothèses découlement laminaire
elif Re > 3000:
print(f'Écoulement Turbulent, {Re = } > 3000')
# On fait des calculs sous hypothèses découlement turbulent
else:
print(f'Écoulement Transitoire , 2300 <= {Re = } <= 3000')
# On fait des calculs sous hypothèses découlement turbulent
print('Suite du code')
Écoulement Turbulent, Re = 5000 > 3000 Suite du code
1.2.3. Expression conditionnelle¶
forme ternaire
Python permet de compacter la structure if/else pour écrire une expression conditionnelle, connue aussi sous le nom de forme ternaire (ou opérateur ternaire). Les deux codes suivants sont équivalent mais le 1er est plus compact.
# Exemple d'une variable b dont la valeur dépend de celle de a
a = 6
# Expression conditionnelle
b = 1 if a > 5 and a < 10 else 2
print(f'{b = }')
# Structure conditionnelle équivalente
if a > 5 and a < 10:
b = 1
else:
b = 2
print(f'{b = }')
b = 1 b = 1
# Un autre exemple de test de signe d'un nombre
x = -2
signe = "positif" if x > 0 else "négatif ou nul"
print(x, "est", signe)
# En une seule commande
print(f"{x} est positif" if x > 0 else f"{x} est négatif ou nul")
-2 est négatif ou nul -2 est négatif ou nul
A noter que elif n’est pas autorisé dans une expression conditionnelle.
Dans l'exemple précédent, afin de traiter le cas où x est nul, il faut imbriquer deux expressions conditionnelles.
x = 2.5
signe = "positif" if x > 0 else "nul" if x == 0 else "négatif"
print(f'{x = } est {signe}')
x = 2.5 est positif
En outre, dans les 2 exemples précédents, on a supposé que x est toujours un nombre (variable de type numérique). Si on donne une valeur autre que numérique, le code va crasher et soulève une erreur de comparaison non supportée entre un nombre (int ici à cause de >0 et non pas >0.0) et une chaîne str ou un complexe.
Il faut donc tester si le type de x est un float ou un entier : type(x) in (int, float).
x = -1
non_num = not (type(x) in (int, float))
signe = 'non supporté' if non_num else 'positif' if x > 0 else 'nul' if x == 0 else 'négatif'
print(f'avec expression conditionnelle : {x = } est {signe}')
avec expression conditionnelle : x = -1 est négatif
# Equivalent avec une structure conditionnelle
x = -1
if not (type(x) in (int, float)): # on teste si ce n'est pas un int ou un float
signe = 'non supporté'
elif x > 0:
signe = 'positif'
elif x == 0:
signe = 'nul'
else:
signe = 'négatif'
print(f'{x = } est {signe}')
x = -1 est négatif
# Equivalent avec une structure conditionnelle et inversion de test
x = -1
if type(x) in (int, float): # on teste si c'est un int ou un float
if x > 0:
signe = 'positif'
elif x == 0:
signe = 'nul'
else:
signe = 'négatif'
else:
signe = 'non supporté'
print(f'{x = } est {signe}')
x = -1 est négatif
Exemple pour TP¶
x = 4
ch = 'pair' if x %2 == 0 else 'impair'
print(f'{x} est {ch}')
# en une seul commande
print(f'{x} est pair' if x%2==0 else f'{x} est impair' )
4 est pair 4 est pair
Ici aussi on a supposé que x est un entier. Si on donne une valeur réelle, les deux commandes affichent x est impair. Et si on donne une valeur d'un autre type que numérique, le code va crasher et soulever une erreur de formatage de str. On affine l'expression conditionnelle en ajoutant un test si x n'est pas entier.
# On affine la commande précédente en ajoutant un test si x n'est pas entier
x = 4
ch = "n'est pas de type int" if (not type(x) is int) else 'est pair' if x %2 == 0 else 'est impair'
print(f'{x = } {ch}')
x = 4 est pair
Remarque Il est parfois possible d'écrire une structure conditionnelle sur une seule ligne contenant un if sans else. Cette pratique, bien que correcte sur le plan syntaxique, n'est pas toujours recommandée pour des raisons de lisibilité. Il ne faut pas la confondre avec l'expression conditionnelle (ou expression ternaire), qui doit impérativement inclure un else.
Python autorise donc une instruction conditionnelle en une seule ligne lorsque la partie else n'est pas nécessaire. Par exemple :
# Exmple 1 : tri de deux nombres
a, b = 2.5, -5.2
if a>b: a,b = b,a
print(a,b)
# L'équivalent en expression ternaire est
r, s = 2.5, -5.2
r,s = (r,s) if r<s else (s, r)
print(r,s)
#L'équivalent en structure conditionnelle
u, v = 2.5, -5.2
if u>v:
u , v = v ,u
print(u,v)
-5.2 2.5 -5.2 2.5 -5.2 2.5
# Exemple 2 : limites d'une quantité
n = 15
if n>10: n = 10
if n< 0: n = 0
print(n)
# Equivalent des 2 if en expression ternaire
m = 15
m = 10 if m>10 else 0 if m<0 else m
print(m)
10 10
Chacun des deux exemples précédent montrent un même effet logique entre le if mono-ligne et l'expression ternaire. Le if est plus clair pour une seule action, tandis que le ternaire est utile pour des attributions (ou définition de variables) courtes.
1.2.4. Correspondance : match - case¶
Le match-case est une instruction de contrôle de choix, qui permet de vérifier plusieurs possibilités (ou "cas") pour une variable donnée, de manière plus concise et plus lisible que les séries de if et elif.
L'instruction match...case est introduite à partir de Python 3.10.
Chaque bloc case représente une condition spécifique, et l'exécution s'arrête dès qu'un cas correspond à la valeur (ou au motif) testé dans l'instruction match.
Cette instruction repose sur la notion de correspondance de motifs (pattern matching) : chaque bloc case définit un motif à comparer avec la valeur testée dans le match.
# Exemple
# Sélection d'une constante physique selon un code
# 'c' = vitesse de la lumière
# 'h' = constante de Planck
# 'G' = constante de gravitation universelle
# 'e' = charge élémentaire
# 'k' = constante de Boltzmann
const = 'G'
match const:
case 'c':
valeur = 2.99792458e8
unite = "m/s"
nom = "vitesse de la lumière (c)"
case 'h':
valeur = 6.62607015e-34
unite = "J.s"
nom = "constante de Planck (h)"
case 'G':
valeur = 6.67430e-11
unite = "m^3/kg/s^2"
nom = "constante de gravitation (G)"
case 'e':
valeur = 1.602176634e-19
unite = "C"
nom = "charge élémentaire (e)"
case 'k':
valeur = 1.380649e-23
unite = "J/K"
nom = "constante de Boltzmann (k)"
case _:
valeur = None
unite = "-"
nom = "constante inconnue"
print(f"{nom} = {valeur} {unite}")
constante de gravitation (G) = 6.6743e-11 m^3/kg/s^2
Exemple de calcul du rayon hydraulique¶
Dans cet exemple, on calcule le rayon hydraulique de trois type de sections : rectangulaire, circulaire (en charge) et triangulaire. On définit d'abord les sections avec les dimensions requises en utilisant un dictionnaire. Ensuite on réalise les calculs en fonction d'un choix de section en utilisant une structure de contrôle match/case.
(pour rappel, voir par exemple : https://fr.wikipedia.org/wiki/Diam%C3%A8tre_hydraulique)
# Définition des sections dans un dict
sections = {
'r' : {'type' : 'rectangulaire', 'b' : 2.0, 'h' : 1.0},
't' : {'type' : 'triangulaire' , 'h' : 1.0, 'm' : 1.0},
'c' : {'type' : 'circulaire' , 'd' : 1.2}
}
# Choix du type de section et tests de correspondance.
section = 't'
match section:
case 'r': # cas de section rectangulaire
b = sections['r']['b'] # largeur (m)
h = sections['r']['h'] # hauteur d'eau (m)
A = b * h
P = b + 2 * h
Rh = A / P
print(f"Section {sections['r']['type']} : Rh = {Rh:.3f} m")
case 'c': # cas de section circulaire
pi = 3.14159265
D = sections['c']['d'] # diamètre (m)
A = pi * (D**2) / 4
P = pi * D
Rh = A / P
print(f"Section {sections['c']['type']} : Rh = {Rh:.3f} m")
case 't': # cas de section triangulaire
h = sections['t']['h'] # hauteur d'eau
m = sections['t']['m'] # talus
A = m * h**2
P = 2 * h * (1 + m**2)**(1/2)
Rh = A / P
print(f"Section {sections['t']['type']} : Rh = {Rh:.3f} m")
case _: # cas par défaut (autres cas)
print("Type de section inconnu.")
Section triangulaire : Rh = 0.354 m
Remarque
Le mot-clé case permet de définir une variable locale sur laquelle on peut appliquer un test if qui sera utilisé comme motif de correspondance pour la variable de match. La condition if est appelée garde
Dans l'exemple suivant, on choisi un diamètre nominale (DN) d'une conduite en fonction du débit quelle véhicule.
Chaque bloc case est exécuté lorsque la valeur de la variable du match (debit) correspond au motif et satisfait la condition définie avec un variable locale q et un test if.
La variable locale peut être appelée différemment d'un bloc de case à l'autre, mais pour des raison de lisibilité, il vaut mieux garder le même nom pour tous les if.
# Exemple 2 : Choix d'un diamètre en fonction du débit
debit = 5 # Débit en m^3/h
match debit:
case q if q < 10:
DN = 25
usage = "Petit branchement domestique"
case var if var < 100:
DN = 80
usage = "Réseau de distribution local"
case q if q < 500:
DN = 150
usage = "Conduite principale de quartier"
case _:
DN = 300
usage = "Grande adduction ou conduite maîtresse"
print(f'{usage :s}, diamètre nominal choisi : {DN = } mm')
Petit branchement domestique, diamètre nominal choisi : DN = 25 mm
1.3. Structure de répétition¶
Une structure de répétition (ou boucle) permet d’exécuter plusieurs fois un bloc d’instructions (commandes). Elle évite la redondance de commandes et rend les codes plus simples, compacts et efficaces.
Il existe deux manières de réaliser des boucles :
- avec le mot-clé
for, qui parcourt les éléments d’une variable itérable (liste, chaîne, range, etc.)- dans ce cas, le nombre des répétitions est déterminé à l’avance par le nombre d'éléments à parcourir.
- avec le mot-clé
while, qui répète un bloc tant qu’une condition logique est vraie.- dans ce cas, le nombre des répétitions est non déterminé mais dépend de la véracité de la condition logique.
Les syntaxes générales de ces deux structures de répétition sont présentées ci-dessous :
1.3.1. Boucle for¶
La boucle for s'écrit généralement de la manière suivante:
for elem in sequence:
instruction 1
instruction 2
...
#suite du programme
Elle se traduit comme suit: pour toute variable elem dans la variable sequence: execute l'ensembles des instructions indentées (précédées par des indentations : 4 espaces en général).
Les deux points qui suivent la commande for sont indispensables et font partie de la commande.
Dans l'exemple suivant, on parcours les entiers 0 à 5 (séquence : range(6)) et on calcul leurs somme.
# exemple 1 de boucle for
s = 0
for i in range(6):
s += i
print(f'iteration {i}, somme : {s}')
# on affiche la somme finale
print(f'la somme de 0 à {i} est : {s}' )
iteration 0, somme : 0 iteration 1, somme : 1 iteration 2, somme : 3 iteration 3, somme : 6 iteration 4, somme : 10 iteration 5, somme : 15 la somme de 0 à 5 est : 15
1.3.2. Boucle while¶
La boucle while s'écrit de la manière suivante:
while condition:
instruction 1
instruction 2
...
#suite du programme
Elle se traduit comme suit: tant que la condition logique condition reste vraie: execute l'ensembles des instructions indentées.
Dans ce cas aussi, les deux points sont indispensables et font partie de la commande while.
On refait l'exemple précédent avec la boucle while.
# exemple 1 de while
i = s = 0 # on doit initialiser aussi i
while i < 6:
s += i # calcul de s comme dans for
print(f'iteration {i}, somme : {s}')
i += 1 # incrémentation de i indispensable
# on affiche la somme finale
print(f'la somme de 0 à {i} est : {s}' )
iteration 0, somme : 0 iteration 1, somme : 1 iteration 2, somme : 3 iteration 3, somme : 6 iteration 4, somme : 10 iteration 5, somme : 15 la somme de 0 à 6 est : 15
1.3.3. Comparaison entre for et while¶
Le tableau suivant récapitule les points de comparaison essentiels entre for et while
| Critère | Boucle for |
Boucle while |
|---|---|---|
| Principe | Parcourt une séquence d’éléments ou une plage de valeurs |
Répète un bloc d'instructions tant qu'une condition est vraie |
| Nombre d’itérations | Connu à l’avance égale au nombre d'éléments |
Inconnu à l’avance, dépend de la condition |
| Syntaxe de base | for var in sequence: |
while condition: |
| Variable de contrôle | Créée automatiquement par la séquence |
Doit être initialisée et mise à jour manuellement |
| Condition d’arrêt | Fin de la séquence | La condition logique devient fausse |
| Utilisation typique | Parcourir des listes, tableaux, chaînes ou intervalles |
Répéter jusqu’à ce qu'une condition soit remplie (ex. convergence, limite atteinte …) |
| Risque de boucle infinie | Faible (géré par la séquence) | Élevé si la condition reste toujours vraie |
1.3.4. Boucles imbriquées¶
Dans de nombreuses situations, il est nécessaire d’imbriquer deux ou plusieurs boucles. Cela permet soit d’itérer sur une structure de données composée (par exemple une liste de listes), soit d’effectuer plusieurs calculs itératifs dépendant de conditions multiples et interdépendantes.
Un cas typique est celui d’une liste contenant d’autres listes, où chaque boucle parcourt un niveau de la structure.
# Liste de listes représentant un tableau 2D
tableau = [
[1.1, 1.2, 1.3],
[2.1, 2.2, 2.3],
[3.1, 3.2, 3.3]
]
for ligne in tableau: # chaque ligne du tableau (boucle extérieure)
for elem in ligne: # chaque élément de la ligne (boucle intérieure)
print(elem, end='\t')
print() # Retour à la ligne après chaque ligne du tableau
1.1 1.2 1.3 2.1 2.2 2.3 3.1 3.2 3.3
Le principe de l'exemple précédent s’applique à de nombreux cas en calcul numérique : matrices, tableaux multidimensionnels, grilles de calculs.
L'exemple suivant montre comment imbriquer trois boucles pour réaliser un produit de deux matrices
# Calcul d'un produit matriciel C = A*B
A = [[ 2 , 1 , -3 ],
[ 1 , 4 , 6 ],
[-3 , 6 , 2 ] ]
B = [[ 4 , -2 , 0],
[-2 , 1 , -1],
[ 0 , -1 , 5]]
C = [] # initialisation de C
for i in range(3):
l = [] # initialisation de la ligne de C
for j in range(3):
s = 0 # initialisation de la somme des produits
for k in range(3):
s += A[i][k] * B[k][j]
l.append(s) # stockage du produit dans la liste l
C.append(l) # stockage de la ligne dans C
print(f'Produit matriciel : {C =}')
Produit matriciel : C =[[6, 0, -16], [-4, -4, 26], [-24, 10, 4]]
1.3.5. Expressions de compréhension¶
De la même manière que pour les expressions conditionnelles que nous avons vu dans la section précédente, python permet aussi de créer une variable séquence (list, dict, set) ou un générateur à partir d’une ou plusieurs boucles for, directement dans une seule expression. C'est une façon compacte, lisible et efficace de générer des tableaux, matrices, ou séquences calculées.
Compréhension de list¶
Voici quelques exemples de génération de listes avec une expression de compréhension
nombres = [i for i in range(10)]
print('nombres 0 à 9 :', nombres)
carres = [i*i for i in nombres]
print('nombres carrés :',carres)
pairs = [i for i in nombres if i%2==0]
print('nombres pairs :',pairs)
carres_pairs = [i*i for i in nombres if i%2==0]
print('pairs carrés :', carres_pairs)
nombres 0 à 9 : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] nombres carrés : [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] nombres pairs : [0, 2, 4, 6, 8] pairs carrés : [0, 4, 16, 36, 64]
Dans l'exemple précédent, nous avons généré la liste nombres à partir de range, ensuite nous l'avons utilisé pour générer d'autres listes. A noter que les listes pairs et carres_pairs incluent un if qui permet de sélectionner uniquement les nombres pairs.
Voici un autre exemple de compréhension de liste à double boucles permettant de reproduire la matrice A de l'exemple de la section 1.3.4 et de l'afficher en seulement deux lignes de code.
- La première ligne crée la matrice avec double range(1,4) qui génèrent double les nombres 1,2 et 3 (le dernier n'est pas compris).
- La deuxième commande fais un print des lignes avec "\n".join() en utilisant une variable l itérée sur les listes internes de la liste globale A (A étant une liste de listes).
- La deuxième commande peut peut être remplacer par
[print(l) for l in A]qui affichera exactement la même chose
A = [[i+0.1*j for j in range(1,4)] for i in range(1,4)]
print("\n".join(str(l) for l in A)) #[print(l) for l in A]
[1.1, 1.2, 1.3] [2.1, 2.2, 2.3] [3.1, 3.2, 3.3]
Compréhension de set¶
On rappelle que les ensembles (set) sont des collections non ordonnées d'éléments uniques, c'est-à-dire qu'ils éliminent automatiquement les doublons. Les expressions de compréhension permettent de créer de tels ensembles à partir d'autres structures de données, comme des listes ou des chaînes de caractères.
Voici deux exemples illustrant comment utiliser une compréhension d'ensemble pour générer un set à partir d'une liste ou d'un texte.
# Extraction de l'ensemble de nombres impairs
# contenus dans une liste (les doublons sont éliminés)
l = [7, 1, 8, 2, 6, 1, 3, 2, 7]
s = {i for i in l if i%2==1}
print(s)
# Extraction de l'ensemble de caractères utilisées
# dans un texte
caracteres = {c for c in 'un texte'}
print(caracteres)
{1, 3, 7}
{'n', 'u', ' ', 'x', 't', 'e'}
Compréhension de dict¶
Une compréhension de dictionnaire permet de créer un nouveau dictionnaire à partir d'un itérable, tout en définissant simultanément la clé et la valeur selon une expression.
La syntaxe avec if optionnel est :
d = {clé: valeur for variable in iterable if condition}
Voici quelques exemples :
# 1) Carrés des nombres pairs
carres_pairs = {n: n**2 for n in range(10) if n % 2 == 0}
print(" ".join(str(d) for d in carres_pairs.items()))
# 2) Fréquences d'apparition d'un caractère dans un texte
# remarquer set(text) pour assurer l'unicité
texte = "un texte"
freqs = {c: texte.count(c) for c in set(texte)}
print(freqs)
(0, 0) (2, 4) (4, 16) (6, 36) (8, 64)
{'n': 1, 'u': 1, ' ': 1, 'x': 1, 't': 2, 'e': 2}
Compréhension de générateur¶
Une compréhension génératrice (on dit aussi expression génératrice) est une forme compacte de générateur (un objet itérable) qui produit ses valeurs à la demande, sans les stocker toutes en mémoire.
Elle ressemble beaucoup à une compréhension de liste, mais utilise des parenthèses () au lieu des crochets [].
Les générateurs sont particulièrement utiles pour les grands ensembles de données car ils ne conservent qu'un élément à la fois. (Rappel : zip dans le cours des tuples)
Les générateurs ne sont pas indexés.
L'utilisation de l'opérateur
[]pour tenter d'accéder aux éléments d'un générateur soulève une erreur :TypeError: 'generator' object is not subscriptableOn accède à ses éléments :
- par la fonction
next():next(gen)(gen est un générateur) - par un parcourt d'éléments avec une boucle :
for elem in gen: - un accès à un élément induit sa consommation et son retrait du générateur
- par la fonction
# Exemple 1 : Générateur simple (1 indice)
gen_carres = (n**2 for n in range(5))
print(gen_carres) # affiche l'objet : <generator object .... >
print(list(gen_carres)) # transforme en liste, vide l'objet et affiche la liste
print(list(gen_carres)) # produit une liste vide : utilisation unique du générateur
<generator object <genexpr> at 0x000002980C3C0930> [0, 1, 4, 9, 16] []
# Exemple 2 : Générateur simple avec condition
gen_carres_pairs = (n**2 for n in range(10) if n % 2 == 0)
#print(gen_carres_pairs[0]) # cette commande soulève une erreur
# Ces deux commandes affichent et consomment les 2 premiers éléments
print('element 1 :', next(gen_carres_pairs))
print('element 2 :', next(gen_carres_pairs))
# Cette commande affiche cette liste un par un
for elem in gen_carres_pairs:
print(elem)
element 1 : 0 element 2 : 4 16 36 64
# Exemple 3 : Générateur aplati à 2 indices
n = 3
gen_tab = (10*i + j for i in range(1,n+1) for j in range(1,n+1))
print('1er affichage :')
for i in range(n):
for j in range(n):
print(f'{next(gen_tab):4d}', end=' ')
print()
print('\n2èm affichage :')
gen_tab = (10*i + j for i in range(1,n+1) for j in range(1,n+1))
for i in range(1,n+1):
print (' '.join(f'{next(gen_tab):4d}' for j in range(1,n+1)))
1er affichage : 11 12 13 21 22 23 31 32 33 2èm affichage : 11 12 13 21 22 23 31 32 33
Recapitulation des expressions de compréhensions¶
D'une façon générale une expression de compréhension (comprehension expression) est une commande python permettant de générer une nouvelle séquence à partir d'un itérable existant selon une syntaxe compacte intégrant la clause for et éventuellement une ou plusieurs conditions if.
| Type de compréhension | Syntaxe générale | Type d’objet créé | Exemple / Résultat |
|---|---|---|---|
| Liste | [expr for ...] |
list |
[x**2 for x in range(5)] [0, 1, 4, 9, 16] |
| Ensemble (set) | {expr for ...} |
set (valeurs uniques) |
{a for a in 'text'} {'x', 't', 'e'} |
| Dictionnaire | {key: val for ...} |
dict |
{x: x**2 for x in range(4)} {0:0, 1:1, 2:4, 3:9} |
| Générateur | (expr for ... ) |
iterator (évalué à la demande) |
(x**2 for x in range(5)) <generator object ...> |
1.4. Structure de saut (structure d’interruption)¶
Les structures de saut (continue, break,return et pass) permettent d'interrompre l'execution d'un bloc de code.
continue- Ignore l'execution du reste de la boucle mais continue les itérations et passe à l'itération suivante.
- Il saute à l'itération suivante en ignorant le reste de l'itération en cours
break- Interrompt l'execution de la boucle et arrête les itérations.
- Il casse la boucle.
return- Permet d'arrêter l'exécution d'un fonction et retourne le résultat (ou rien).
- Saute hors de la fonction et revient à la commande qui suit la commende d'appel de la fonction
passSaute tout simplement à la commande suivante
listj = []
for i in range(5):
j = 2*i+1
if i == 2:
break # Intervertir entre continue, break et pass
# pour voire la différence.
# return provoquera une erreur, elle
# s'applique aux fonctions.
print(f'{i = }, {j = }')
listj.append(j)
print(f'{listj = }')
i = 0, j = 1 i = 1, j = 3 listj = [1, 3]
1.5. Structure de gestion d'exceptions¶
Une exception en programmation est une erreur qui se produit pendant l'exécution d'un programme. La gestion des exceptions permet d'éviter l'arrêt brutal du programme lorsqu'une erreur survient, en la détectant et en la traitant proprement.
1.5.1. try/except¶
La syntaxe d'une structure de gestion d'exceptions en python (try/except) se présente comme suit :
try:
# Bloc de code à vérifier : tentative d'execution
# (code susceptible de provoquer une erreur)
except NomErreur:
# Bloc exécuté si une erreur du type spécifié
# par NomErreur survient
except AutreErreur:
# Bloc exécuté si une erreur d'un
# autre type que le 1er survient
else:
# Bloc exécuté si aucune erreur ne s'est produite
# Ce bloc est facultatif
finally:
# Bloc toujours exécuté, qu'il y ait une erreur ou non
Les mots-clés composant la stricture d'exception sont:
try: contient le code qui peut potentiellement causer une erreur.exceptpermet de capturer et traiter une erreur spécifique.- on peut insérer autant de blocs except que d'erreurs possible qu'on voudrait traiter
else: s'exécute uniquement si aucune exception n'a été levée dans le bloc try.- ce bloc est facultatif
finally: s'exécute dans tous les cas, utile pour libérer des ressources (fermer un fichier, une connexion, etc.).- ce bloc aussi est facultatif
Exemple :
# expérimentez les cas :
# valeurs non numérique comme caractères
# section nulle
try:
Q = float(input("Entrez le débit Q (m3/s) : "))
A = float(input("Entrez la section A (m2) : "))
V = Q / A
print(f"La vitesse de l’eau est : {V:.2f} m/s")
except ValueError:
print("Erreur : veuillez entrer des valeurs numériques valides.")
except ZeroDivisionError:
print("Erreur : la section A ne peut pas être nulle.")
else:
print("Calcul effectué sans erreur.")
finally:
print("Fin du calcul.")
La vitesse de l’eau est : 4.00 m/s Calcul effectué sans erreur. Fin du calcul.
En python, il existe de nombreux types d’erreurs (ou d'exceptions), certaines sont très courantes et apparaissent souvent lors de l’exécution du code.
Le tableau suivant liste les types d’erreurs les plus fréquents.
| Type d’erreur | Description | Exemple |
|---|---|---|
ValueError |
Une fonction reçoit une valeur du mauvais type (mais de type correct). | int("abc") : impossible de convertir une chaîne en nombre. |
TypeError |
Opération entre types incompatibles. | "3" + 2 : concaténation impossible entre str et int. |
ZeroDivisionError |
Division par zéro. | 10 / 0 |
NameError |
Utilisation d’une variable non définie. | print(x) sans avoir défini x. |
IndexError |
Accès à un indice inexistant dans une liste ou un tuple. | ma_liste[10] alors que la liste a 5 éléments. |
KeyError |
Clé absente dans un dictionnaire. | d = {"a": 1}; print(d["b"]) |
AttributeError |
Utilisation d’un attribut ou d’une méthode inexistante. | "texte".append("a") : les chaînes n’ont pas de méthode append(). |
FileNotFoundError |
Fichier introuvable lors d’une tentative d’ouverture. | open("fichier.txt") alors qu’il n’existe pas. |
ImportError |
Erreur lors de l’importation d’un module inexistant. | import mathh |
ModuleNotFoundError |
Erreur lors de l’importation d’un module inexistant. | import mathh |
IndentationError |
Mauvaise indentation du code. | Mauvais alignement des espaces ou tabulations. |
SyntaxError |
Erreur de syntaxe Python. | if x = 5: au lieu de if x == 5: |
RuntimeError |
Erreur générale à l’exécution (non spécifique). | Utilisée par certains modules internes. |
RecursionError |
Appels récursifs trop profonds (boucle infinie de fonctions). | Une fonction qui s’appelle elle-même sans condition d’arrêt. |
EOFError |
Fin de fichier atteinte de manière inattendue (souvent avec input()). |
Appel de input() sans entrée disponible. |
1.5.2. raise et assert¶
En Python, il existe deux manières courantes de provoquer volontairement une erreur : raise et assert. Ces deux commandes se ressemblent, mais n'ont pas le même rôle.
raisesert à lever explicitement une exception lorsque les valeurs fournies au programme sont invalides ou incohérentes.- Elle signale une vraie erreur et ne peut être désactivée.
- Elle est utile pour contrôler les valeurs d'entrée de l'utilisateur
assertsert uniquement à vérifier une hypothèse interne au programme- Elle soulève une erreur sous hypothèse mais peut être désactivée au lancement de python pr l'option -O.
- Elles est utile pour tester le code du développeur.
Exemple : Le code suivant calcule le débit d'écoulement à travers un orifice simple avec la loi de Torricelli. Une hauteur négative (donnée h) provoquera une erreur de calcul de la racine dans l'expression du débit : $Q = C_d A \sqrt{2gh}$
On teste alors si si h < 0, puis on soulève une erreur et on provoque l'arrêt du programme.
# Paramètres (exemples)
Cd = 0.65 # coefficient de débit (adimensionnel)
A = 113e-6 # aire de l'orifice en m^2 (approx : cercle de 12mm diam)
g = 9.81 # gravité en m/s^2
h = 2 # hauteur de charge en m (tester une valeur h<0)
# Vérification di signe de h pour éviter la racine d'un nombre négatif
if h < 0:
raise ValueError("Hauteur h invalide : doit être >= 0 (vérifiez vos entrées).")
# Calcul de Q en utilisant l'exponentiation pour la racine (x**0.5)
Q = Cd * A * (2 * g * h) ** 0.5
print("Débit Q = {:.6f} l/s".format(1000*Q))
Débit Q = 0.460104 l/s
Sans la vérification du signe de h, le code donnera un nombre complexe comme débit sans provoquer d'erreur, ce qui est physiquement impossible.
Tester le code en mettant en commentaire les deux lignes du test if :
# if h < 0:
# raise ValueError("Hauteur h invalide : doit être >= 0 (vérifiez vos entrées).")
En plus des vérifications nécessaires, on peut chercher à se prémunir des bugs logiques ou des cas d'invalidité des plages des paramètres physiques. On utilise alors des tests d'hypothèses avec assert pour écrire un code plus rigoureux. Par exemple on peut ajouter au code précédent la commande suivante juste avant le calcul du débit :
# vérifications contre les bugs logiques
assert A > 0 and h >= 0 and 0 < Cd <= 1, "Revoir les plages des paramètres d'entrée"
# Méthode de Dichotomie
# Si la lecture se fait à partir d'une saisie utilisateur,
# on peut ajouter ce test :
# if xl > xr: xl, xr = xr, xl # On normalise [xl xr]
a, b, c, d = 2, -9, 7, 6
xl, xr = -1, 1
fl = ((a * xl + b) * xl + c) * xl + d
fr = ((a * xr + b) * xr + c) * xr + d
#fl = a * xl**3 + b * xl**2 + c * xl + d
#fr = a * xr**3 + b * xr**2 + c * xr + d
# remarque :
# il est préférable d'écrire les commandes
# en suivant la méthode Horner :
# f = ((a * x + b) * x + c) * x + d
# (https://fr.wikipedia.org/wiki/M%C3%A9thode_de_Ruffini-Horner)
eps_machine = 1e-14
# Une ou les deux extrémités de l'intervalle est une solution
# On évite le teste (variable == 0) à cause des erreurs machines.
if abs(fl) <= eps_machine or abs(fr) <= eps_machine :
msg = "Solution de bords de l'intervale"
if abs(fl) <= eps_machine :
print('Solution coïncide avec le choix : xl =', xl)
if abs(fr) <= eps_machine:
print('Solution coïncide avec le choix : xr =', xr)
elif fr*fl > 0:
print("Modifier l'intervalle de recherche")
print(f"Il y'a 0 solution ou un nombre pair de solutions dans : [{xl}, {xr}]")
msg = "Calcul non effectués, aucune solution trouvée"
else:
it, maxit = 0, 500
tol_sol = 1e-10
err_sol = abs(xr-xl)
while err_sol > tol_sol and it < maxit :
it += 1
x = (xl+xr)/2
f = ((a * x + b) * x + c) * x + d
if abs(f) < eps_machine: # on tombe exactement sur la solution
err_sol = 0
break
if f*fl < 0: # on teste le sous-intervalle [x, xl]
xr, fr = x, f
else:
xl, fl = x, f
err_sol = max(abs(xr-xl), abs(f))
if (it >= maxit):
msg = "Nombre d'itérations max atteint, la solution n'a pas convergé"
else:
msg = "Fin des calculs avec convergence"
print(f"Nombre d'iterations : {it}, tolérance : {err_sol:.2e}")
print(f"Racine calculée : {x = :.10g}")
print(msg)
Nombre d'iterations : 2, tolérance : 0.00e+00 Racine calculée : x = -0.5 Fin des calculs avec convergence
2.2. Tirant d'eau en écoulement à surface libre – formule de Manning¶
Pour déterminer le tirant d'eau $h$ correspondant à un débit imposé $Q$ dans un canal rectangulaire, on utilise la formule de Manning et on construit la fonction
$$F(h) =\frac{1}{n} A(h) \left(R_H(h)\right)^{2/3} \sqrt{S} - Q.$$
avec $S$ est la pente du canal, $n$ est le coefficient de Manning. $A(h)$ et $R_H(h)$ sont la surface mouillée et le rayon hydraulique exprimées par :
$$ A(h) = b\, h \quad; \quad R(h) = \frac{b\,h}{b + 2h} $$
On peut donc determiner $h$ en résolvant cette fonction $F(h)$ avec la méthode de dichotomie ou la méthode de Newton-Raphson.
Il est aussi possible d'utiliser la méthode des tirs (shooting method) avec interpolation linéaire (méthode sécante). C'est ce que nous allons implementer dans ce 2ème exemple. On cherche des valeurs de $h$ en commençant par zero et la hauteur critique $h_c$. On sait que ces deux valuers font partie du problème physique.
Pour $h = 0$, $A=0$ et donc $F(0)=-Q$, on prend cette valeur comme premier tir : $F_1 = F(h_1)=-Q$
On prend comme deuxième valeur la hauteur critique $h_2 = h_c = \sqrt[3]{Q^2/(gb^2)}$ : $F_2=F(h_2)$
On initialise $F_{cible} = F_2$
On calcule ensuite itérativement par la boucle while et tant que $F_{cible}< tolérance$ :
- un nouveau $h_{cible}$ par : interpolation $h_{cible} = h_1 - F_1 (h_2 - h_1)/(F_2 - F_1)$
- une nouvelle valeur de $F_{cible} = F(h_{cible})$
- les nouvelles valeurs des point $h_1$ $F_1$, et $h_2$, $F_2$ avec les mises à jours :
- $h_1$, $F_1$ = $h_2$ , $F_2$
- $h_2$, $F_2$ = $h_{cible}$ , $F_{cible}$
Lorsque le critère de convergence est atteint et que la boucle est rompu, ona affiche le résultat final
On se protège du cas de boucle infinie en introduisant une condition supplémentaire sur le nombre max d'itérations à ne pas dépasser.
Le code avec les données, les initialisations et la boucle se présente comme suit :
# Paramètres du canal
b = 1.0 # largeur (m)
Q = 2.5 # débit (m3/s)
S = 0.0005 # pente
n = 0.015 # coefficient Manning
g = 9.81 # gravité
tol = 1e-12 # Tolérance
itmax = 50 # nombre max d'itérations
hc = (Q**2 / (g * b**2))**(1/3) # Hauteur critique
h1 = 0.0 # 1er tir : point initial 1
F1 = -Q
h2 = hc # 2ème tir : pint initial 2
A = b * h2
R = (b * h2) / (b + 2*h2)
F2 = A * (1/n) * R**(2/3) * (S**0.5) - Q
print("Points initiaux :")
print(f"\t{h1 = :.6f}, {F1 = :.3e}")
print(f"\t{h2 = :.6f}, {F2 = :.3e}")
it = 0 # initialisation du compteur de boucle
F_cible = F2 # initialisation F_cible
# Boucle while basée sur la tolérance et le garde-fou
while abs(F_cible) > tol and it < itmax:
# Interpolation linéaire (méthode sécante)
h_cible = h2 - F2 * (h2 - h1) / (F2 - F1)
# Calcul de F(h_cible)
A = b * h_cible
R = (b * h_cible) / (b + 2*h_cible)
F_cible = A * (1/n) * R**(2/3) * (S**0.5) - Q
it += 1
print(f"It {it}: h = {h_cible:.8f}, F(h) = {F_cible:.3e}")
# Mise à jour des points pour l'interpolation
h1, F1 = h2, F2
h2, F2 = h_cible, F_cible
print(f"\nTirant d'eau par la méthode du tir : h = {h_cible:.12f} m")
Points initiaux : h1 = 0.000000, F1 = -2.500e+00 h2 = 0.860473, F2 = -1.905e+00 It 1: h = 3.61300123, F(h) = 6.121e-01 It 2: h = 2.94357672, F(h) = -1.023e-02 It 3: h = 2.95457991, F(h) = -2.029e-05 It 4: h = 2.95460178, F(h) = 8.061e-10 It 5: h = 2.95460177, F(h) = 0.000e+00 Tirant d'eau par la méthode du tir : h = 2.954601774343 m
Aucun commentaire:
Enregistrer un commentaire