Programmation structurée (Python) · L1 · Section 1/11
Variables
Progression
#Variables et types
Les variables permettent de nommer des valeurs pour les réutiliser et les composer. En Python, un nom de variable référence un objet qui porte à la fois une valeur et un type, plutôt qu'un emplacement mémoire fixe comme dans d'autres langages. Cette distinction a des conséquences importantes sur l'aliasing, la différence entre identité et égalité, et la portée des noms selon la règle LEGB.
#Prérequis
Aucun. C'est le point d'entrée du module.
#Objectifs d'apprentissage
- Comprendre que les noms référencent des objets plutôt que des emplacements mémoire.
- Manipuler les types de base (nombres, chaînes, booléens) et leurs conversions.
- Prévoir les effets d'aliasing entre objets mutables, et savoir copier correctement.
- Expliquer la résolution des noms selon la règle LEGB.
#Premiers pas
1x = 32y = 2.53nom = "Ada"4vrai = True5print(x + y, nom.upper(), vrai)Sortie :
15.5 ADA TrueLes noms de variables doivent être explicites et suivre la convention snake_case. L'instruction a = b ne copie pas un objet : elle lie le nom a au même objet que b. La section suivante montre pourquoi c'est décisif.
#Identité vs égalité, mutabilité, aliasing
L'opérateur == compare les valeurs de deux objets. L'opérateur is vérifie s'il s'agit du même objet en mémoire. Pour les listes, [1,2] == [1,2] est vrai (mêmes valeurs) mais [1,2] is [1,2] est faux (deux objets distincts).
Les types immuables (int, str, tuple) ne changent jamais après création : toute opération construit un nouvel objet. Les types mutables (list, dict, set) se modifient en place. Conséquence directe, l'aliasing :
1xs = [1, 2]2ys = xs # ys est un autre nom pour LE MÊME objet3ys.append(3)4print(xs) # [1, 2, 3] : xs a changé aussi !5 6zs = xs.copy() # copie superficielle : nouvel objet liste7zs.append(9)8print(xs, zs) # xs intact, zs divergeSorties :
1[1, 2, 3]2[1, 2, 3] [1, 2, 3, 9]Sur le premier append, un seul objet liste existe, vu par deux noms : la mutation par ys est visible via xs. La copie crée un second objet : les deux trajectoires se séparent. Pour des structures imbriquées ([[1,2],[3,4]]), .copy() ne copie que la liste externe : les sous-listes restent partagées, d'où copy.deepcopy quand il faut une indépendance totale.
#Portée des noms (LEGB)
La résolution d'un nom suit l'ordre Local (fonction courante), Enclosing (fonctions englobantes), Global (module), Builtins (len, print). Python prend le premier niveau qui contient le nom.
1x = "global"2 3def externe():4 x = "englobante"5 def interne():6 x = "locale"7 print(x) # locale8 interne()9 print(x) # englobante10 11externe()12print(x) # globalSorties :
1locale2englobante3globalUne affectation à l'intérieur d'une fonction crée une variable locale, sauf déclaration global ou nonlocal (à éviter : elle rend le flot de données difficile à suivre). Préférez toujours passer des paramètres et retourner des valeurs.
#Nombres et opérateurs
Les opérateurs arithmétiques : addition +, soustraction -, multiplication *, division / (toujours flottante), division entière //, modulo %, exponentiation ** :
1a, b = 7, 32print(a // b, a % b, a ** b)3print(a / b, round(a / b, 4))Sorties :
12 1 34322.3333333333333335 2.3333Les comparaisons (==, !=, <, <=, >, >=) retournent des booléens, composables avec and, or, not. Attention aux flottants : 0.1 + 0.2 == 0.3 est faux (voir représentation).
#Booléens et tables de vérité
Le type bool ne prend que deux valeurs, True et False, et les opérateurs logiques and, or, not les combinent. and et or sont paresseux (évaluation court-circuitée) : a and b n'évalue b que si a est vrai, a or b n'évalue b que si a est faux. C'est ce qui rend sûre l'expression if xs and xs[0] > 0: — si xs est vide, le second test n'est jamais exécuté.
Une table de vérité énumère, pour chaque combinaison des entrées, la valeur de la sortie. Les examens en demandent régulièrement, sous la forme : « concevoir la ou les formules logiques qui… et donner la fonction de vérité ».
Exemple issu d'une épreuve. Trois chats A, B et C peuvent entrer dans une maison par une chatière. Concevoir les formules logiques qui comptent combien de chats sont présents à l'intérieur.
Le résultat est un nombre de 0 à 3, donc un nombre sur 2 bits x1 x0. On énumère les huit cas :
A | B | C | nombre | x1 | x0 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 1 | 0 | 1 |
| 0 | 1 | 0 | 1 | 0 | 1 |
| 0 | 1 | 1 | 2 | 1 | 0 |
| 1 | 0 | 0 | 1 | 0 | 1 |
| 1 | 0 | 1 | 2 | 1 | 0 |
| 1 | 1 | 0 | 2 | 1 | 0 |
| 1 | 1 | 1 | 3 | 1 | 1 |
Deux lectures directes de la table:
x1vaut 1 dès qu'au moins deux chats sont présents — c'est la majorité:x1 = (A and B) or (A and C) or (B and C).x0vaut 1 quand le nombre de chats est impair — c'est la parité:x0 = A xor B xor C.
En Python:
1def compter_chats(A: bool, B: bool, C: bool) -> int:2 """Nombre de chats presents, de 0 a 3."""3 return A + B + C # True vaut 1, False vaut 04 5def sortie_x1(A: bool, B: bool, C: bool) -> bool:6 """Bit de poids fort : au moins deux chats."""7 return (A and B) or (A and C) or (B and C)8 9def sortie_x0(A: bool, B: bool, C: bool) -> bool:10 """Bit de poids faible : nombre impair de chats."""11 return (A != B) != C # equivalent a A xor B xor C12 13for A in (False, True):14 for B in (False, True):L'assertion finale vérifie que les deux formules reconstruisent bien le compte pour les huit combinaisons: c'est exactement ce que demande une « fonction de vérité ».
#Chaînes de caractères
Les chaînes sont immuables : s.upper() retourne une nouvelle chaîne, sans modifier s. Les f-strings (f"{expr}") interpolent toute expression Python et sont la manière idiomatique de construire du texte :
1prenom, age = 'Ada', 362msg = f"{prenom} a {age} ans"3print(msg)4print(msg.split())5print('-'.join(['a', 'b', 'c']))6print(f"{1234.5678:.2f}")Sorties :
1Ada a 36 ans2['Ada', 'a', '36', 'ans']3a-b-c41234.57Méthodes essentielles : .strip() (espaces aux extrémités), .split(sep) (découpe en liste), .join(liste) (assemble), .upper() et .lower(), .startswith() et .endswith(), .replace(a, b).
#Playground
#Exercices
- Convertissez un nombre de secondes en format
HH:MM:SS(//et%suffisent). - Demandez un nom et affichez un message de bienvenue en majuscules.
- Calculez l'IMC à partir d'un poids et d'une taille, puis affichez la catégorie approximative.
- Calculez le nombre de jours entre deux dates avec
datetime. - Prédisez la sortie avant d'exécuter : que valent
xsetzsaprèszs = xs.copy(); zs[0] = 99? Et aprèsa = [1,[2,3]]; b = a.copy(); b[1].append(4), que vauta?
#Solutions
Conversion secondes vers HH:MM:SS
1sec = 36712h = sec // 36003m = (sec % 3600) // 604s = sec % 605print(f"{h:02d}:{m:02d}:{s:02d}")Sortie : 01:01:11.
Manipulation de dates et heures
1from datetime import date2 3date1 = date(2023, 1, 1)4date2 = date(2023, 12, 31)5diff = date2 - date16print(f"Nombre de jours entre les deux dates: {diff.days}")Sortie : Nombre de jours entre les deux dates: 364. La soustraction de deux date donne un timedelta, dont .days est le nombre de jours.
Aliasing : prédictions vérifiées
1xs = [1, 2]2zs = xs.copy()3zs[0] = 994print(xs, zs) # [1, 2] [99, 2] : la copie protège5 6a = [1, [2, 3]]7b = a.copy() # copie superficielle8b[1].append(4) # la sous-liste est partagée9print(a) # [1, [2, 3, 4]] : a a changé aussi.copy() recrée la liste externe mais partage les objets internes. Seule copy.deepcopy duplique aussi les niveaux imbriqués.
#Quiz rapide
#Animation : portée et résolution des noms (LEGB)
len seulementx = "global" # Global
def outer():
# (pas de x ici)
def inner():
x = "local"
print(x) # LEGB
inner()
outer()