Affecter n'est pas égaler : trace, x = x + 1, = contre ==
1. On considère le programme ci-dessous. Dresser son tableau de trace — les valeurs de x et de y après chacune des cinq affectations —, puis prédire ce qu'il affiche.
x = 3
y = x + 2
x = x * y
y = x - y
x = x + 1
print(x, y)
2. En mathématiques, l'équation x = x + 1 ? Prédire l'affichage du programme suivant.
n = 10
n = n + 1
n = n * n
print(n)
3. Un élève veut ranger la valeur x et écrit le programme ci-dessous. Qu'affiche-t-il, et pourquoi ? Le corriger. Que répond Python si l'on écrit if x = 5: pour tester l'égalité ?
x = 2
x == 5
print(x)
4. Prédire l'affichage du programme suivant, puis expliquer pourquoi b ne « suit » pas a.
a = 4
b = a
a = 7
print(a, b)
Indices (3)
Exécute les lignes une par une : à chaque affectation, calcule d'abord le membre de droite avec les valeurs actuelles, puis range le résultat sous le nom écrit à gauche.
À la ligne 4 du premier programme, x ne vaut plus
= range une valeur ; == compare deux valeurs et produit True ou False sans rien modifier.
Correction détaillée
Le signe = de Python ne dit pas que deux choses sont égales : il ordonne de ranger une valeur. L'instruction x = expression s'exécute en deux temps, toujours dans cet ordre : Python calcule d'abord le membre de droite avec les valeurs actuelles des variables, puis il range le résultat sous le nom écrit à gauche, en écrasant la valeur qui s'y trouvait. On dit qu'on affecte la valeur à x.
Un programme est donc une suite d'états : après chaque ligne, chaque variable a une valeur précise, et la ligne suivante part de cet état-là, pas de l'état initial. Rien n'est « vrai en même temps » comme dans un système d'équations.
La comparaison, elle, s'écrit ==. Elle ne modifie rien : elle produit une valeur de vérité, True ou False, qu'on peut afficher ou tester.
👉 Pour prédire un programme, tu ne lis pas ses lignes comme des équations à résoudre ensemble : tu les exécutes une par une, en tenant à jour un tableau de trace. C'est le geste de base de tout ce chapitre.
On exécute les lignes dans l'ordre et on note l'état après chacune ; un tiret signale une variable pas encore créée.
| étape | x | y |
|---|---|---|
| avant | — | — |
| après la ligne 1 | 3 | — |
| après la ligne 2 | 3 | 5 |
| après la ligne 3 | 15 | 5 |
| après la ligne 4 | 15 | 10 |
| après la ligne 5 | 16 | 10 |
Deux lignes demandent de l'attention. À la ligne 3, x = x * y calcule y = x - y utilise donc le nouveau x :
x = 3
y = x + 2
x = x * y
y = x - y
x = x + 1
print(x, y)
16 10
Vérification. On fait afficher l'état après chaque ligne : les cinq lignes obtenues doivent reproduire les cinq dernières lignes du tableau.
x = 3
print(x)
y = x + 2
print(x, y)
x = x * y
print(x, y)
y = x - y
print(x, y)
x = x + 1
print(x, y)
3
3 5
15 5
15 10
16 10
Elles les reproduisent. C'est la méthode à garder pour contrôler une trace faite à la main : instrumenter le programme, c'est-à-dire insérer des print, puis comparer.
Lue comme une équation, x + 1 avec la valeur actuelle de x, puis range le résultat dans x ». Le nom ne change pas, la valeur augmente de x.
Dans le programme de la question, n vaut successivement
n = 10
n = n + 1
n = n * n
print(n)
121
Ce motif — une variable qui se met à jour à partir de sa propre valeur — est celui des compteurs (c = c + 1) et des accumulateurs (s = s + t) qu'on retrouve dans toutes les boucles du lot B. Python accepte l'abréviation n += 1, qui fait la même chose sur un nombre (sur une liste, t += [x] modifie la liste en place, comme append : voir l'exercice E6) ; ce chapitre garde la forme longue, qui montre l'ordre des opérations.
Vérification. Les deux mises à jour reviennent à calculer
print((10 + 1) ** 2)
121
Même résultat par un autre chemin.
La deuxième ligne du programme de l'élève est une comparaison : x == 5 calcule False (puisque x vaut
x = 2
x == 5
print(x)
2
Python ne signale rien : une expression seule sur une ligne est permise, elle est simplement calculée pour rien. La correction remplace la comparaison par une affectation ; on en profite pour montrer == à sa vraie place, dans une valeur qu'on affiche :
x = 2
x = 5
print(x)
print(x == 5)
5
True
Et dans l'autre sens ? Écrire if x = 5: pour tester l'égalité est refusé par Python avant toute exécution : il signale une erreur de syntaxe (SyntaxError), et aucune ligne du programme n'est exécutée, pas même celles qui précèdent le if. Un test attend une expression qui vaut vrai ou faux ; l'instruction d'affectation = n'en est pas une. (Les versions récentes de Python suggèrent aussi := dans leur message : cette « affectation-expression » serait acceptée, mais elle range 5 dans x au lieu de le comparer, et le test est toujours vrai — ce n'est pas la correction.) Le test correct s'écrit if x == 5:.
Vérification. La dernière ligne affichée, True, confirme que la valeur rangée est bien == qui fait le contrôle, sans rien changer à x.
À la ligne 2, b = a range dans b la valeur que a possède à ce moment-là, soit a. Aucune instruction n'a demandé de modifier b : il vaut toujours
| étape | a | b |
|---|---|---|
| avant | — | — |
| après la ligne 1 | 4 | — |
| après la ligne 2 | 4 | 4 |
| après la ligne 3 | 7 | 4 |
a = 4
b = a
a = 7
print(a, b)
print(b == 4)
7 4
True
En mathématiques, « posons b = a est une photographie : elle recopie une valeur à un instant donné, et la suite du programme peut faire évoluer a et b indépendamment.
⚠️ Réaffecter a ne modifie jamais b, quel que soit le type de la valeur. Les listes réservent une autre surprise, étudiée en E6 : quand deux noms désignent la même liste, modifier cette liste en place (a[0] = 7) se voit par les deux noms. Ce n'est pas une réaffectation, et c'est un autre mécanisme.
Vérification. La ligne True confirme que b a gardé la valeur a.
Affectation.
nom = expression: on évalue l'expression avec les valeurs actuelles, puis on range le résultat sous le nom, qui est créé s'il n'existait pas et écrasé sinon. Toujours la droite d'abord, la gauche ensuite.Comparaisons.
a == b(égal),a != b(différent),<,<=,>,>=: elles ne modifient rien et produisent un booléen,TrueouFalse.Trace. Un tableau avec une colonne par variable et une ligne par instruction exécutée ; on n'y écrit que les valeurs réellement calculées, et on le contrôle en insérant des
Affichage.
print(a, b)affiche les valeurs séparées par une espace, puis passe à la ligne.
⚠️ Utiliser une variable avant de l'avoir affectée. Python exécute les lignes dans l'ordre : une variable n'existe qu'à partir de la ligne qui l'affecte. Ici le programme s'arrête dès la première ligne, sans rien afficher :
y = x + 1
x = 3
print(y)
NameError: name 'x' is not defined
Il suffit d'échanger les deux premières lignes :
x = 3
y = x + 1
print(y)
4
⚠️ Écrire l'affectation à l'envers. 3 = x est refusé : à gauche du =, il faut un nom, pas un nombre ni un calcul. Le sens de lecture est « x reçoit x ».
⚠️ Lire = comme une égalité qui dure. Après b = a, rien ne relie b à a ; toute trace qui fait « suivre » une variable par une autre est fausse.
16 10 (trace : x vaut successivement y, créé à la ligne 2, vaut x = x + 1 calcule puis range, d'où 121 pour le deuxième ; x == 5 compare sans modifier, le programme de l'élève affiche 2 et se corrige en x = 5 ; if x = 5: est une erreur de syntaxe, le test s'écrit if x == 5: ; b = a copie la valeur du moment, d'où l'affichage 7 4.Échanger deux variables : le piège, la variable temporaire, l'affectation multiple
1. Pour échanger les contenus de a et de b, un élève écrit le programme ci-dessous. Qu'affiche-t-il ? Dresser sa trace et expliquer l'échec.
a = 3
b = 8
a = b
b = a
print(a, b)
2. Corriger ce programme à l'aide d'une troisième variable t, en trois affectations, et contrôler par une trace.
3. Python autorise l'écriture a, b = b, a. Prédire l'affichage du programme ci-dessous et expliquer pourquoi, cette fois, aucune valeur n'est perdue.
a = 3
b = 8
a, b = b, a
print(a, b)
4. On peut aussi échanger sans variable auxiliaire : a = a + b, puis b = a - b, puis a = a - b. Démontrer que cette méthode échange deux nombres réels, puis prédire ce qu'elle donne en Python pour a = 0.1 et b = 0.2.
Indices (3)
Après a = b, que reste-t-il de l'ancienne valeur de a ? Une affectation écrase.
Avant d'écraser a, mets sa valeur à l'abri dans t ; c'est t qui la rendra à b.
Pour la méthode arithmétique, suis les valeurs avec deux lettres fixes : pars de
Correction détaillée
Une affectation écrase : dès que a = b est exécutée, l'ancienne valeur de a est perdue, et aucune instruction ultérieure ne peut la retrouver. Échanger deux variables est donc un problème de sauvegarde : avant d'écraser une valeur dont on aura encore besoin, il faut la mettre à l'abri.
Trois solutions, trois idées différentes :
- sauvegarder dans une troisième variable,
t; - lire les deux valeurs avant de ranger quoi que ce soit, ce que fait
a, b = b, a; - coder l'information dans une somme — méthode exacte sur les entiers, et fausse sur les flottants.
👉 Devant toute suite d'affectations, pose-toi la question : quelle valeur cette ligne écrase-t-elle, et en aura-t-on encore besoin ?
| étape | a | b |
|---|---|---|
| avant | — | — |
| après la ligne 1 | 3 | — |
| après la ligne 2 | 3 | 8 |
| après la ligne 3 | 8 | 8 |
| après la ligne 4 | 8 | 8 |
La ligne 3 range a : le b la valeur de a, qui est déjà
a = 3
b = 8
a = b
b = a
print(a, b)
8 8
Vérification. Inverser l'ordre des deux dernières affectations ne répare rien : c'est alors le
a = 3
b = 8
b = a
a = b
print(a, b)
3 3
Les deux ordres échouent, symétriquement : avec deux affectations seulement, l'une des deux valeurs est forcément écrasée avant d'avoir été lue.
On sauvegarde la valeur de a dans t avant de l'écraser, puis t la rend à b :
a = 3
b = 8
t = a
a = b
b = t
print(a, b)
8 3
| étape | a | b | t |
|---|---|---|---|
| avant | — | — | — |
| après la ligne 1 | 3 | — | — |
| après la ligne 2 | 3 | 8 | — |
| après la ligne 3 | 3 | 8 | 3 |
| après la ligne 4 | 8 | 8 | 3 |
| après la ligne 5 | 8 | 3 | 3 |
À la ligne 4, a est bien écrasé — mais sa valeur survit dans t. C'est tout le secret.
Vérification. La méthode ne calcule rien, elle déplace des valeurs : elle marche donc pour n'importe quel type, y compris des chaînes de caractères, et même pour deux valeurs de types différents.
a = 'pile'
b = 2.5
t = a
a = b
b = t
print(a, b)
2.5 pile
Une affectation multiple s'exécute, elle aussi, droite d'abord : Python évalue tout le membre de droite, b, a, ce qui donne le couple a et b. Au moment où l'on range, les deux anciennes valeurs ont déjà été lues : rien ne peut être perdu. Le couple joue le rôle de la variable t.
a = 3
b = 8
a, b = b, a
print(a, b)
8 3
Il ne faut donc pas lire a, b = b, a comme « a = b puis b = a » : ce serait exactement l'échange naïf de la question 1. Beaucoup de langages n'ont pas cette écriture ; la variable temporaire reste la méthode universelle, celle qu'on écrit en pseudo-code.
Vérification. Un échange appliqué deux fois doit ramener au point de départ :
a = 3
b = 8
a, b = b, a
a, b = b, a
print(a, b)
3 8
Notons a et b, et suivons-les ligne par ligne.
- Après
a = a + b:avaut, bvaut. - Après
b = a - b:bvaut. - Après
a = a - b:avaut.
Les valeurs sont échangées, pour tous réels
En Python, les entiers sont exacts et n'ont pas de taille maximale : la démonstration s'y applique telle quelle, même pour de très grands nombres.
a = 10 ** 30
b = 7
a = a + b
b = a - b
a = a - b
print(a, b)
7 1000000000000000000000000000000
Vérification. Avec les valeurs de la question 1, on retrouve bien l'échange :
a = 3
b = 8
a = a + b
b = a - b
a = a - b
print(a, b)
8 3
La démonstration utilise 0.1 + 0.2 n'est pas calculé exactement (voir A4) : c'est le flottant 0.30000000000000004, et l'erreur d'arrondi de la somme ne s'annule pas quand on retranche.
a = 0.1
b = 0.2
a = a + b
b = a - b
a = a - b
print(a, b)
print(b == 0.1)
0.2 0.10000000000000003
False
a a bien reçu 0.2, mais b n'a pas récupéré 0.1 : l'échange est faux. Sur des nombres d'ordres de grandeur très différents, c'est pire. Au voisinage de
a = 1e16
b = 1.0
print(a + b == a)
a = a + b
b = a - b
a = a - b
print(a, b)
True
0.0 1e+16
Vérification. La première ligne affichée, True, montre la cause : a + b est égal à a, le t ou avec a, b = b, a.
Écrasement. Une affectation remplace l'ancienne valeur, qui n'est plus accessible ensuite.
Échange par variable temporaire :
t = a, puisa = b, puisb = t. Trois affectations, valables pour toutes les valeurs.Affectation multiple :
a, b = e1, e2évaluee1ete2avec les valeurs actuelles, puis range les résultats. D'où l'échangea, b = b, a.Entiers et flottants. Les entiers de Python sont exacts, sans taille maximale. Les flottants sont des approximations à
chiffres binaires significatifs : une identité vraie sur les réels peut devenir fausse sur les flottants.
⚠️ Échanger par deux affectations. a = b puis b = a produit deux copies de la même valeur, quel que soit l'ordre.
⚠️ Tester un échange sur deux valeurs égales. Avec a et b valant tous deux
a = 5
b = 5
a = b
b = a
print(a, b)
5 5
Le test passe et le programme est faux : un cas test où les deux valeurs sont égales ne peut pas le démasquer. Pour tester un échange, prends deux valeurs différentes, et regarde les deux.
⚠️ Croire qu'une démonstration sur les réels vaut pour les flottants. La méthode arithmétique est juste sur
8 8 : la ligne a = b écrase le t = a, a = b, b = t, on obtient 8 3. a, b = b, a évalue tout le membre de droite avant de ranger et affiche 8 3. La méthode arithmétique échange tous réels 0.1 et 0.2 elle affiche 0.2 0.10000000000000003 : l'arrondi de la somme ne s'annule pas.Division euclidienne en Python : quotient, reste, division décimale et négatifs
1. Prédire l'affichage du programme ci-dessous.
print(17 // 5, 17 % 5, 17 / 5)
print(-7 // 2, -7 % 2)
print(7 // -2, 7 % -2)
2. Vérifier, pour chacun des trois couples a == b * (a // b) + a % b. Quand a // b arrondit-elle
3. Écrire un programme qui affiche le chiffre des centaines, celui des dizaines et celui des unités de n = 4827, sans convertir n en chaîne de caractères.
4. Écrire un programme qui convertit une durée de s = 10000 secondes en heures, minutes et secondes.
Indices (3)
// donne le quotient entier, % le reste, / un flottant. Pour un dividende négatif et a == b * (a // b) + a % b.
Le chiffre des unités est le reste de la division par // efface ce chiffre.
Une heure fait s par
Correction détaillée
Python a trois divisions, et les confondre est la première source d'erreurs de calcul.
a / best la division décimale : elle renvoie toujours un flottant, même quand la division tombe juste.a // best le quotient entier.a % best le reste.
Les deux dernières sont liées par l'identité de la division euclidienne, a == b * (a // b) + a % b, que Python garantit pour tous les entiers
👉 La conséquence à retenir : quand a // b arrondit
print(17 // 5, 17 % 5, 17 / 5)
print(-7 // 2, -7 % 2)
print(7 // -2, 7 % -2)
3 2 3.4
-4 1
-4 -1
Ligne 1. 3.4.
Ligne 2.
Ligne 3. Le diviseur est négatif. Python garde l'identité et donne au reste le signe du diviseur :
Vérification. Le cas où la division tombe juste ne doit laisser aucun reste, et / doit malgré tout rendre un flottant :
print(-8 // 2, -8 % 2, 6 / 3)
-4 0 2.0
On la contrôle par des assert : une instruction assert condition ne fait rien si la condition est vraie, et arrête le programme sinon.
assert 17 == 5 * (17 // 5) + 17 % 5
assert -7 == 2 * (-7 // 2) + -7 % 2
assert 7 == -2 * (7 // -2) + 7 % -2
print('identité vérifiée')
identité vérifiée
Le programme atteint sa dernière ligne : les trois identités sont vraies.
Pourquoi c'est un arrondi vers le bas. Soit a // b et a % b, avec int appliqué à un flottant :
print(-7 // 2, int(-7 / 2))
-4 -3
Vérification. Sur un couple de positifs, les deux arrondis coïncident, et sur un couple négatif le quotient et le reste redonnent bien
a = -7
b = 2
q = a // b
r = a % b
print(q, r, b * q + r)
print(0 <= r < b)
print(17 // 5 == int(17 / 5))
-4 1 -7
True
True
Pour
n = 4827
u = n % 10
d = (n // 10) % 10
c = (n // 100) % 10
m = n // 1000
print(m, c, d, u)
print(1000*m + 100*c + 10*d + u)
4 8 2 7
4827
Pour n // 100 vaut
Vérification. La deuxième ligne affichée reconstruit % et // est l'algorithme qu'on répétera dans une boucle au lot B, pour traiter un entier de longueur quelconque.
On retire d'abord les heures entières, puis on découpe le reste en minutes et secondes.
s = 10000
h = s // 3600
reste = s % 3600
m = reste // 60
sec = reste % 60
print(h, m, sec)
print(3600 * h + 60 * m + sec)
2 46 40
10000
Vérification. La dernière ligne recalcule le nombre total de secondes à partir du résultat et retrouve
Division euclidienne. Pour
et , il existe un unique couple d'entiers tel que et ; alors . En Python, a // bvautet a % bvaut. (Voir le chapitre d'arithmétique.) Diviseur négatif. Python garde l'identité
a == b * (a // b) + a % bet donne au reste le signe du diviseur :7 % -2vaut-1.Division décimale.
a / brenvoie un flottant, même quanddivise . Chiffres. Pour
, n % 10est le chiffre des unités de, et n // 10efface ce chiffre.
⚠️ Croire que // tronque vers zéro. -7 // 2 vaut -4, pas -3 ; la troncature, c'est int(-7 / 2). D'autres langages, comme C ou Java, tronquent vers zéro : un algorithme recopié d'un autre langage peut changer de résultat sur les nombres négatifs.
⚠️ Utiliser / là où il faut //. Le résultat de / est un flottant ; or un indice de liste, un nombre de tours de boucle ou un nombre d'objets doit être un entier. Et sur de grands nombres, passer par un flottant fait perdre des chiffres :
n = 10 ** 17
print(n // 3)
print(int(n / 3))
33333333333333333
33333333333333332
La première ligne est exacte ; la seconde est fausse au dernier chiffre, parce que n / 3 a été arrondi à un flottant, qui ne garde que //, jamais int(… / …).
⚠️ Diviser par zéro. a // 0 et a % 0 arrêtent le programme sur une ZeroDivisionError, comme a / 0.
3 2 3.4, puis -4 1, puis -4 -1. L'identité a == b * (a // b) + a % b tient pour les trois couples, et quand a // b est -7 // 2 vaut -4, int(-7 / 2) vaut -3). Les chiffres de Les flottants ne sont pas des réels : 0,1 + 0,2, écriture binaire et tolérance
1. Prédire, puis constater en lançant le programme.
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(0.5 + 0.25 == 0.75)
2. Démontrer que le nombre
3. Proposer un test d'égalité « à une tolérance près » entre 0.1 + 0.2 et 0.3, et l'écrire en Python.
4. Prédire ce qu'affiche le programme suivant, qui ajoute dix fois
s = 0
for k in range(10):
s = s + 0.1
print(s)
print(s == 1)
Indices (3)
Un nombre à écriture binaire finie s'écrit
Deux flottants sont « égaux à une tolérance près » quand la valeur absolue de leur différence est petite : abs(x - y) < eps.
Correction détaillée
Un flottant est un nombre à virgule écrit en base 0.1 sous la forme
Python n'est pas en cause : la plupart des langages utilisent ces mêmes flottants et donnent les mêmes résultats.
👉 La conséquence pratique, à appliquer sans exception : on ne teste jamais l'égalité de deux flottants issus d'un calcul avec == ; on teste que leur écart est petit.
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(0.5 + 0.25 == 0.75)
0.30000000000000004
False
True
0.1 et 0.2 sont déjà des approximations ; leur somme, arrondie à son tour, tombe sur le flottant voisin de celui qui approche le mieux == sont différents : le test rend False. print affiche le plus court nombre décimal qui désigne sans ambiguïté un flottant ; c'est pourquoi 0.1 s'affiche 0.1, alors que la valeur rangée n'est pas exactement
Vérification. L'écart entre les deux flottants est minuscule, et c'est exactement
print(0.1 + 0.2 - 0.3)
print(0.1 + 0.2 - 0.3 == 2 ** -54)
5.551115123125783e-17
True
Une écriture binaire finie
C'est le même phénomène que 0.5 + 0.25 == 0.75 vaut True.
Vérification. On peut calculer les chiffres binaires de
p = 1
q = 10
for k in range(12):
p = 2 * p
print(p // q, end='')
p = p % q
print()
000110011001
On lit
On déclare deux flottants égaux quand leur écart est plus petit qu'une tolérance eps choisie à l'avance :
x = 0.1 + 0.2
y = 0.3
eps = 1e-9
print(abs(x - y) < eps)
print(abs(0.3 - 0.31) < eps)
True
False
eps doit être bien plus grand que les erreurs d'arrondi (ici de l'ordre de
Une tolérance absolue comme math.isclose, dont la tolérance relative vaut
import math
print(math.isclose(0.1 + 0.2, 0.3))
d = 0.1 + 0.2 - 0.3
print(math.isclose(d, 0))
True
False
Vérification. La dernière ligne montre la limite d'une tolérance purement relative : comparé à abs(d) < eps.
s = 0
for k in range(10):
s = s + 0.1
print(s)
print(s == 1)
0.9999999999999999
False
(La boucle for répète dix fois l'instruction décalée ; elle est détaillée au lot B.) Chaque addition arrondit son résultat, et les erreurs s'accumulent : après dix additions, la somme est juste en dessous de
Vérification. On fait afficher la somme après chaque addition :
s = 0
for k in range(10):
s = s + 0.1
print(k + 1, s)
1 0.1
2 0.2
3 0.30000000000000004
4 0.4
5 0.5
6 0.6
7 0.7
8 0.7999999999999999
9 0.8999999999999999
10 0.9999999999999999
L'écart apparaît dès la troisième addition. À la quatrième, les arrondis se compensent et la somme retombe sur le flottant le plus proche de 0.4. À partir de la huitième, la somme s'écarte de nouveau et ne revient plus. Quand la grandeur est décimale par nature (des centimes, des dixièmes), le remède est de compter en entiers : dix fois 10 / 10 vaut 1.0.
Flottant. Un
floatPython est un nombre de la formeavec entier d'au plus chiffres binaires ; les autres réels sont arrondis au plus proche, et chaque opération arrondit son résultat. Rationnels représentables. Un rationnel irréductible
a une écriture binaire finie si et seulement si est une puissance de : , , oui ; , , non. Comparer.
abs(x - y) < eps(tolérance absolue) oumath.isclose(x, y)(tolérance relative,par défaut). Jamais ==entre flottants calculés.Affichage.
0.1s'affiche0.1, bien que la valeur rangée ne soit pas.
⚠️ Arrêter une boucle sur une égalité de flottants. La boucle ci-dessous devait s'arrêter quand x atteint x passe de 0.9999999999999999 à 1.0999999999999999 sans jamais valoir exactement
x = 0
while x != 1:
x = x + 0.1
Remplacer != par < ne suffit pas : la boucle termine, mais elle fait un tour de trop.
x = 0
n = 0
while x < 1:
x = x + 0.1
n = n + 1
print(n, x)
11 1.0999999999999999
Après dix tours, x vaut 0.9999999999999999, qui est encore strictement inférieur à for k in range(10) — et le flottant n'intervient plus dans la décision d'arrêt.
⚠️ Choisir une tolérance sans regarder l'ordre de grandeur.
0.1 + 0.2 s'affiche 0.30000000000000004, et 0.1 + 0.2 == 0.3 vaut False, alors que 0.5 + 0.25 == 0.75 vaut True. Écrire abs(x - y) < 1e-9 ou math.isclose(x, y). Dix ajouts de 0.9999999999999999, et s == 1 vaut False.Priorités des opérateurs : traduire une formule sans la trahir
1. Prédire l'affichage du programme ci-dessous, ligne par ligne.
print(2 + 3 * 4)
print(-2 ** 2)
print(2 ** 3 ** 2)
print(7 - 3 - 2)
print(12 / 2 * 3)
2. Pour calculer la moyenne de a = 8 et b = 14, un élève écrit a + b / 2. Qu'obtient-il ? Corriger.
3. On cherche les racines du trinôme
import math
a = 2
b = -3
c = -5
d = b ** 2 - 4 * a * c
x1 = -b + math.sqrt(d) / 2 * a
print(d, x1)
4. Que valent 9 ** 1 / 2 et 9 ** (1 / 2) ? Laquelle des deux calcule
Indices (3)
Du plus prioritaire au moins prioritaire : parenthèses, puissance **, signe moins, puis * et /, puis + et -.
** se regroupe de droite à gauche ; - et / se regroupent de gauche à droite.
Une barre de fraction groupe tout son numérateur et tout son dénominateur : sur une ligne, il faut des parenthèses pour chacun.
Correction détaillée
Une expression Python est une formule écrite sur une seule ligne : plus de barre de fraction, plus d'exposant surélevé. C'est l'ordre des priorités qui décide alors de la structure. Il prolonge celui des mathématiques : les parenthèses d'abord, puis la puissance **, puis le signe moins placé devant un nombre, puis *, /, //, %, puis + et -. À priorité égale, on regroupe de gauche à droite — sauf pour **, qui se regroupe de droite à gauche.
Deux conséquences surprennent : la puissance passe avant le signe moins écrit devant elle, et 2 ** 3 ** 2 se lit
👉 La règle pratique : dès qu'une barre de fraction ou un exposant disparaît en passant sur une ligne, tu remets les parenthèses que la barre rendait inutiles.
print(2 + 3 * 4)
print(-2 ** 2)
print(2 ** 3 ** 2)
print(7 - 3 - 2)
print(12 / 2 * 3)
14
-4
512
2
18.0
| expression | lecture de Python | valeur |
|---|---|---|
2 + 3 * 4 |
14 |
|
-2 ** 2 |
-4 |
|
2 ** 3 ** 2 |
512 |
|
7 - 3 - 2 |
2 |
|
12 / 2 * 3 |
18.0 |
La dernière ligne rend un flottant, 18.0, parce que / rend toujours un flottant (voir A3).
Vérification. Si les priorités ont été bien lues, placer les parenthèses autrement doit changer chaque valeur :
print((-2) ** 2)
print((2 ** 3) ** 2)
print(7 - (3 - 2))
print(12 / (2 * 3))
4
64
6
2.0
Les quatre valeurs changent : les parenthèses ne sont pas décoratives.
a + b / 2 se lit b est divisé.
a = 8
b = 14
print(a + b / 2)
15.0
La moyenne
a = 8
b = 14
m = (a + b) / 2
print(m)
print(min(a, b) <= m <= max(a, b))
11.0
True
Vérification. Une moyenne est toujours comprise entre la plus petite et la plus grande valeur : la dernière ligne le confirme pour
import math
a = 2
b = -3
c = -5
d = b ** 2 - 4 * a * c
x1 = -b + math.sqrt(d) / 2 * a
print(d, x1)
49 10.0
La formule
import math
a = 2
b = -3
c = -5
d = b ** 2 - 4 * a * c
r = math.sqrt(d)
x1 = (-b + r) / (2 * a)
x2 = (-b - r) / (2 * a)
print(d, x1, x2)
print(a*x1**2 + b*x1 + c)
print(a*x2**2 + b*x2 + c)
49 2.5 -1.0
0.0
0.0
Vérification. Les deux dernières lignes réinjectent les racines dans le trinôme : a*x1**2, la puissance passe avant la multiplication : c'est bien
Une subtilité au passage : b ** 2 vaut b contient -3 ** 2 donne
b = -3
print(b ** 2, -3 ** 2)
9 -9
print(9 ** 1 / 2)
print(9 ** (1 / 2))
4.5
3.0
9 ** 1 / 2 se lit 9 ** (1 / 2) calcule math.sqrt, ou x ** 0.5 quand x est positif ou nul.
Vérification. Par deux autres chemins : la racine obtenue, élevée au carré, redonne math.sqrt donne la même valeur.
import math
r = 9 ** (1 / 2)
print(r ** 2, math.sqrt(9))
9.0 3.0
Priorités, de la plus forte à la plus faible : parenthèses ;
**; signe-placé devant ;*,/,//,%;+,-; puis les comparaisons,not,and,or(voir A6).Regroupement.
**se regroupe de droite à gauche :2 ** 3 ** 2vaut. Tous les autres opérateurs arithmétiques se regroupent de gauche à droite : 7 - 3 - 2vaut. Traduire une fraction.
s'écrit (a + b) / (c + d);s'écrit (-b + math.sqrt(d)) / (2 * a).
⚠️ Oublier les parenthèses du dénominateur. x / 2 * a divise par 12 / 2 * 3 vaut 18.0, pas 2.0. Le dénominateur (2 * a).
⚠️ Confondre -2 ** 2 et (-2) ** 2. Le premier vaut
⚠️ Écrire ^ pour la puissance, par habitude de la calculatrice ou du tableur. En Python, ^ est une autre opération sur les entiers (le « ou exclusif » bit à bit) : le programme tourne, sans aucun message, et donne un résultat faux.
x = 2
print(x ^ 3)
print(x ** 3)
1
8
La première ligne devait afficher **.
14, -4, 512, 2, 18.0 : la multiplication passe avant l'addition, la puissance avant le signe moins, regroupée de droite à gauche ; - et / se regroupent de gauche à droite. a + b / 2 vaut 15.0, la moyenne (a + b) / 2 vaut 11.0. Le discriminant vaut 10.0, la bonne, (-b + r) / (2 * a), donne les racines 9 ** 1 / 2 vaut 4.5, 9 ** (1 / 2) vaut 3.0.Booléens : and, or, not, comparaisons enchaînées et évaluation paresseuse
1. Prédire l'affichage du programme ci-dessous.
x = 0.5
print(0 < x < 1)
print(0 < x and x < 1)
print(not x > 0 or x == 1)
print(x > 1 or x < 0)
2. Quand x vaut
b1 = x != 0 and 1 / x > 2
b2 = 1 / x > 2 and x != 0
3. Écrire, sans utiliser not, une condition vraie exactement quand x n'appartient pas à l'intervalle
4. Trois longueurs
Indices (3)
Les comparaisons se calculent avant not, not avant and, and avant or.
A and B ne calcule pas B quand A est faux : le résultat est déjà connu.
La négation d'un « et » est un « ou » : c'est l'une des lois de Morgan.
Correction détaillée
Une condition est une expression comme une autre : elle se calcule, et sa valeur est un booléen, True ou False. Les comparaisons (<, ==, !=…) fabriquent des booléens ; and, or et not les combinent, avec les tables de vérité des connecteurs « et », « ou » (inclusif) et « non » de la logique.
Deux traits sont propres à Python. On peut enchaîner les comparaisons comme en mathématiques : 0 < x < 1 signifie 0 < x and x < 1. Et and, or sont paresseux : ils ne calculent leur second membre que si le premier ne suffit pas à conclure.
👉 En logique, « and compte dès que le second peut échouer. Tu places d'abord la condition qui protège l'autre — « x non nul » avant la division par x.
x = 0.5
print(0 < x < 1)
print(0 < x and x < 1)
print(not x > 0 or x == 1)
print(x > 1 or x < 0)
True
True
False
False
Les deux premières lignes disent la même chose, l'une en notation enchaînée, l'autre avec and : (not (x > 0)) or (x == 1) :
Vérification. Pour s'assurer que not s'applique avant or, on choisit une valeur qui sépare les deux lectures possibles de la troisième ligne affichée — avec False, donc ce test ne prouverait rien. Avec
x = 1
print(not x > 0 or x == 1)
print((not x > 0) or x == 1)
print(not (x > 0 or x == 1))
True
True
False
La ligne sans parenthèses coïncide avec la première lecture, pas avec la seconde : Python applique bien not avant or.
Avec x valant x != 0 and 1 / x > 2, le premier membre x != 0 est faux ; un « et » dont un membre est faux est faux, donc Python conclut False sans calculer 1 / x.
x = 0
b1 = x != 0 and 1 / x > 2
print(b1)
False
Dans l'ordre inverse, Python commence par 1 / x et s'arrête sur une division par zéro :
x = 0
b2 = 1 / x > 2 and x != 0
print(b2)
ZeroDivisionError: division by zero
Même principe pour or : dans A or B, si A est vrai, le résultat est vrai et B n'est pas calculé.
x = 0
print(x == 0 or 1 / x > 2)
True
Vérification. Quand
x = 0.25
print(x != 0 and 1 / x > 2)
print(1 / x > 2 and x != 0)
True
True
0 <= x <= 1, c'est-à-dire 0 <= x and x <= 1. Sa négation, par la loi de Morgan « non (
h = x < 0 or x > 1
On la teste contre la condition initiale sur les cinq valeurs demandées ; l'assert arrêterait le programme si les deux conditions n'étaient pas contraires.
for x in [-1, 0, 0.5, 1, 2]:
d = 0 <= x <= 1
h = x < 0 or x > 1
assert h == (not d)
print(x, d, h)
-1 False True
0 True False
0.5 True False
1 True False
2 False True
Vérification. Les deux colonnes sont contraires sur toutes les lignes, y compris aux bornes
On écrit les trois inégalités strictes, reliées par and. Pour la tester sur trois triplets sans recopier la condition, on la place dans une fonction (les fonctions sont étudiées au lot E) :
def triangle(a, b, c):
t1 = a < b + c
t2 = b < a + c
t3 = c < a + b
return t1 and t2 and t3
print(triangle(3, 4, 5))
print(triangle(1, 2, 3))
print(triangle(1, 1, 5))
True
False
False
Vérification. Le cas aplati est le seul qui distingue les inégalités strictes des inégalités larges : avec <=, il serait accepté.
print(3 < 1 + 2, 3 <= 1 + 2)
False True
Booléens.
TrueetFalse; les comparaisons==,!=,<,<=,>,>=en produisent.Connecteurs.
A and Best vrai si les deux le sont ;A or Best vrai si l'un au moins l'est ;not Aéchange vrai et faux. Priorités : les comparaisons, puisnot, puisand, puisor.Évaluation paresseuse.
A and Bne calcule pasBsiAest faux ;A or Bne calcule pasBsiAest vrai.Comparaisons enchaînées.
a < b < csignifiea < b and b < c.Lois de Morgan.
not (A and B)équivaut ànot A or not B, etnot (A or B)ànot A and not B.
⚠️ Écrire x == 1 or 2 pour « x vaut (x == 1) or 2. Quand x == 1 est faux, or renvoie son second membre, le nombre if considère comme vrai (tout nombre non nul compte comme vrai). Le test passe donc toujours :
x = 3
print(x == 1 or 2)
print(x == 1 or x == 2)
2
False
La bonne écriture répète la comparaison : x == 1 or x == 2.
⚠️ Nier un « et » en gardant « et ». Pour « x < 0 and x > 1 n'est jamais vraie : aucun nombre n'est à la fois négatif et plus grand que
x = 2
print(x < 0 and x > 1)
False
True, True, False, False : les comparaisons passent avant not, not avant and, and avant or. Avec x valant x != 0 and 1 / x > 2 vaut False sans calculer la division, tandis que l'ordre inverse lève ZeroDivisionError. Par Morgan, la négation de 0 <= x <= 1 est x < 0 or x > 1. La condition de triangle a < b + c and b < a + c and c < a + b est vraie pour Année bissextile : l'ordre des tests
Dans le calendrier grégorien, une année est bissextile si elle est divisible par
1. Écrire une fonction bissextile(a) qui renvoie un booléen à l'aide d'un seul if … elif … else, et donner ses réponses pour
2. Un élève a écrit la fonction ci-dessous. Que renvoie-t-elle pour
def bissextile(a):
if a % 4 == 0:
return True
elif a % 100 == 0:
return False
elif a % 400 == 0:
return True
else:
return False
3. Proposer quatre années de test qui font passer la bonne fonction par ses quatre branches, et vérifier que ce jeu démasque la fonction de l'élève.
4. Combien d'années bissextiles de
Indices (3)
Un if … elif … else examine ses conditions dans l'ordre et s'arrête à la première vraie : la condition d'un elif n'est lue que si toutes les précédentes sont fausses.
Toute année divisible par
Range les tests du plus particulier au plus général : for suffit.
Correction détaillée
Un if … elif … else est une suite de filtres essayés dans l'ordre. Python évalue la première condition ; si elle est vraie, il exécute son bloc et saute tout le reste ; sinon il passe à l'elif suivant. Chaque branche ne reçoit donc que les cas que les branches précédentes ont laissé passer : la condition d'un elif porte, sans l'écrire, « et aucune des conditions précédentes n'était vraie ».
C'est pourquoi l'ordre des tests fait partie de l'algorithme. Quand les conditions s'emboîtent — ici, divisible par
👉 Ce qu'on retient : avant d'écrire une cascade de tests, se demander quelle condition en contient une autre, et mettre la plus étroite en premier. Puis vérifier avec un jeu de tests qui passe par chaque branche.
On teste
def bissextile(a):
if a % 400 == 0:
return True
elif a % 100 == 0:
return False
elif a % 4 == 0:
return True
else:
return False
print(bissextile(2024))
print(bissextile(2023))
print(bissextile(1900))
print(bissextile(2000))
Il affiche :
True
False
False
True
Lecture branche par branche. else.
Une autre écriture. La règle est une formule booléenne ; on peut la renvoyer directement, sans if :
def bis2(a):
b4 = a % 4 == 0
b100 = a % 100 == 0
b400 = a % 400 == 0
return b4 and (not b100 or b400)
for a in [2024, 2023, 1900, 2000]:
print(a, bis2(a))
2024 True
2023 False
1900 False
2000 True
Mêmes réponses. La cascade de tests garde un avantage pédagogique : chaque branche correspond à une ligne de la règle, et se teste isolément.
On relance la fonction de l'élève sur les deux années :
def bissextile(a):
if a % 4 == 0:
return True
elif a % 100 == 0:
return False
elif a % 400 == 0:
return True
else:
return False
print(bissextile(1900))
print(bissextile(2000))
True
True
Le mécanisme. True sans jamais regarder la divisibilité par elif a % 100 == 0 et elif a % 400 == 0 sont mortes : on pourrait les effacer sans rien changer, et la fonction se réduit à « divisible par
Correction. Garder les trois tests mais inverser leur ordre (
Quatre branches, donc quatre années au moins, chacune choisie pour atterrir dans une branche différente de la bonne fonction :
| année | divisible par 400 | par 100 | par 4 | branche | attendu |
|---|---|---|---|---|---|
| 2000 | oui | oui | oui | 1re | True |
| 1900 | non | oui | oui | 2e | False |
| 2024 | non | non | oui | 3e | True |
| 2023 | non | non | non | else |
False |
On écrit ces attendus sous forme d'assert : assert c ne fait rien si c est vrai, et arrête le programme sinon. Un programme dont tous les assert passent n'affiche rien et se termine normalement — c'est le signe que les tests réussissent.
def bissextile(a):
if a % 400 == 0:
return True
elif a % 100 == 0:
return False
elif a % 4 == 0:
return True
else:
return False
assert bissextile(2000)
assert not bissextile(1900)
assert bissextile(2024)
assert not bissextile(2023)
Le même jeu de tests, appliqué à la fonction de l'élève, s'arrête sur l'année
def bissextile(a):
if a % 4 == 0:
return True
elif a % 100 == 0:
return False
elif a % 400 == 0:
return True
else:
return False
assert bissextile(2000)
assert not bissextile(1900)
assert bissextile(2024)
assert not bissextile(2023)
AssertionError
Le jeu de tests démasque l'erreur parce qu'il contient une année de la deuxième branche. Un jeu réduit à
On compte avec un compteur dans une boucle for :
def bissextile(a):
if a % 400 == 0:
return True
elif a % 100 == 0:
return False
elif a % 4 == 0:
return True
else:
return False
n = 0
for a in range(1601, 2001):
if bissextile(a):
n = n + 1
print(n)
97
Recoupement par le calcul. De
Durée moyenne. Quatre siècles comptent
j = 400 * 365 + 97
print(j, j / 400, j % 7, j // 7)
146097 365.2425 0 20871
Conditionnelle.
if C1: … elif C2: … else: …exécute le bloc de la première condition vraie, et un seul ; leelsereçoit tout ce que les conditions ont laissé passer. La brancheelif C2s'exécute si et seulement siC1est fausse etC2vraie.Ordre des tests. Si une condition en entraîne une autre (ici
), la plus particulière doit venir en premier ; sinon les suivantes sont inatteignables. Tester une fonction. Un jeu de tests doit faire passer le programme par chaque branche, et viser les cas limites.
assert conditionne fait rien si la condition est vraie et arrête le programme sur uneAssertionErrorsinon.
⚠️ Écrire les tests dans l'ordre où on lit la règle. La phrase « divisible par elif, elle produit exactement la fonction de l'élève. Une règle à exceptions se programme en commençant par l'exception la plus rare.
⚠️ Mélanger deux styles. Avec des if indépendants qui modifient une variable, c'est le dernier test vrai qui l'emporte, et l'ordre juste s'inverse : du plus général au plus particulier.
def bis3(a):
r = False
if a % 4 == 0:
r = True
if a % 100 == 0:
r = False
if a % 400 == 0:
r = True
return r
for a in [2024, 2023, 1900, 2000]:
print(a, bis3(a))
2024 True
2023 False
1900 False
2000 True
Écrire ces trois if dans l'ordre elif dans l'ordre
⚠️ Ne tester que des années « faciles ».
True, False, False, True pour True pour elif sont morts. Jeu de tests : Racines d'un trinôme : trois cas et une garde
On cherche les racines réelles de
1. Écrire une fonction racines(a, b, c) qui renvoie la liste des racines réelles dans l'ordre croissant — deux, une ou aucune selon le signe du discriminant math.sqrt.
2. Donner ce qu'elle renvoie pour
3. Que se passe-t-il pour racines(0, 2, -4) ? Ajouter une garde qui traite le cas
4. Pourquoi exige-t-on des coefficients entiers ? Examiner le trinôme
Indices (3)
Trois cas qui s'excluent : un if, un elif, un else. Pour l'ordre croissant, demande-toi lequel des deux quotients
Reporter une racine a * x * x + b * x + c : attends-toi à un résultat très petit, pas forcément nul.
Avec des entiers, b * b - 4 * a * c est calculé exactement et le test d == 0 est fiable. Avec des flottants,
Correction détaillée
Trois cas qui s'excluent, donc un if, un elif, un else. Le signe du discriminant else n'a pas besoin de condition : il reçoit
Deux détails transforment la formule du cours en programme correct. D'abord une garde : la formule divise par d == 0 : il n'est fiable que si
👉 Ce qu'on retient : un programme hérite des hypothèses de la formule qu'il code (assert, ou dans la description de la fonction.
On écrit les racines [s - h, s + h] est toujours rangée dans l'ordre croissant, même si abs(a).
import math
def racines(a, b, c):
d = b * b - 4 * a * c
s = -b / (2 * a)
if d > 0:
r = math.sqrt(d)
h = r / (2 * abs(a))
return [s - h, s + h]
elif d == 0:
return [s]
else:
return []
print(racines(1, -3, 2))
print(racines(1, -2, 1))
print(racines(1, 1, 1))
print(racines(-1, 3, -2))
[1.0, 2.0]
[1.0]
[]
[1.0, 2.0]
Vérification des cas.
x1 = (-3 - 1) / (-2)
x2 = (-3 + 1) / (-2)
print(x1, x2)
2.0 1.0
import math
def racines(a, b, c):
d = b * b - 4 * a * c
s = -b / (2 * a)
if d > 0:
r = math.sqrt(d)
h = r / (2 * abs(a))
return [s - h, s + h]
elif d == 0:
return [s]
else:
return []
for x in racines(1, 0, -2):
print(x)
print(x * x - 2)
-1.4142135623730951
4.440892098500626e-16
1.4142135623730951
4.440892098500626e-16
Le report ne donne pas == (A4). Le contrôle sûr s'écrit ainsi :
x = 1.4142135623730951
print(abs(x * x - 2) < 1e-12)
True
Le seuil
Pour racines(0, 2, -4), le calcul de s divise par 2 * a, qui vaut
import math
def racines(a, b, c):
d = b * b - 4 * a * c
s = -b / (2 * a)
if d > 0:
r = math.sqrt(d)
h = r / (2 * abs(a))
return [s - h, s + h]
elif d == 0:
return [s]
else:
return []
print(racines(0, 2, -4))
ZeroDivisionError: division by zero
Le programme s'arrête sur une exception : le print n'est jamais exécuté. Pourtant la question a un sens :
On ajoute une garde en tête de fonction : si assert, qui arrête le programme sur une AssertionError désignant la précondition violée, au lieu d'une division par zéro qui n'en est qu'un symptôme.
import math
def racines(a, b, c):
if a == 0:
assert b != 0
return [-c / b]
d = b * b - 4 * a * c
s = -b / (2 * a)
if d > 0:
r = math.sqrt(d)
h = r / (2 * abs(a))
return [s - h, s + h]
elif d == 0:
return [s]
else:
return []
print(racines(0, 2, -4))
print(racines(1, -3, 2))
[2.0]
[1.0, 2.0]
La garde est placée avant le calcul de s : placée après, elle arriverait trop tard. Et le comportement pour
Le trinôme
b = -0.2
c = 0.01
d = b * b - 4 * c
print(d)
print(d == 0)
6.938893903907228e-18
False
Mathématiquement d == 0 échoue, et la fonction voit deux racines distinctes là où il n'y en a qu'une. Multiplier le trinôme par
import math
def racines(a, b, c):
d = b * b - 4 * a * c
s = -b / (2 * a)
if d > 0:
r = math.sqrt(d)
h = r / (2 * abs(a))
return [s - h, s + h]
elif d == 0:
return [s]
else:
return []
for x in racines(1, -0.2, 0.01):
print(x)
print(racines(100, -20, 1))
0.09999999868291098
0.10000000131708903
[0.1]
Avec les flottants, deux « racines » écartées d'environ
👉 Avec des entiers, le test d == 0 est exact ; avec des flottants, un « zéro » peut sortir positif ou négatif selon les arrondis. Si les coefficients sont décimaux, on les multiplie par une puissance de d == 0 par abs(d) < eps, en choisissant eps en connaissance de cause.
Trinôme. Pour
, . Si : deux racines ; si : une racine double ; si : aucune racine réelle. Somme des racines , produit . En Python.
math.sqrt(x)exigex >= 0(sinonValueError) : raison de plus pour ne l'appeler que dans la branche. Les entiers ( int) sont exacts et sans limite de taille ; les flottants (float) sont arrondis, et0.1n'est pas exactement. Garde. Tester les cas où la formule n'a pas de sens avant de l'appliquer ; refuser par
assertce que la fonction ne sait pas traiter.
⚠️ Mettre la garde après le calcul. Si l'on calcule s = -b / (2 * a) en première ligne et qu'on teste a == 0 ensuite, la division a déjà eu lieu : le programme s'arrête avant d'atteindre la garde. L'ordre des instructions compte autant que l'ordre des tests.
⚠️ Tester d >= 0 avant d == 0. Dans une cascade, le premier test vrai l'emporte : avec if d >= 0: en tête, le cas d > 0, puis d == 0, puis else.
⚠️ Appeler math.sqrt(d) avant de connaître le signe de d. Pour math.sqrt lève une ValueError : la racine carrée ne se calcule que dans la branche où elle existe.
[1.0, 2.0], [1.0], [], et [1.0, 2.0] pour racines(0, 2, -4) lève ZeroDivisionError ; avec la garde, [2.0]. Avec [0.1].Boucle for et range : compter les tours
1. Quels entiers parcourt la variable i dans for i in range(5), puis avec range(2, 6), range(1, 10, 3), range(10, 0, -2) et range(5, 2) ? Combien de tours fait for i in range(a, b) lorsque
2. Écrire une boucle qui calcule
3. Pour calculer
s = 0
for k in range(1, n):
s = s + k * k
Qu'obtient-il pour
4. Vérifier par programme que la boucle corrigée et la formule coïncident pour tous les
Indices (3)
range(a, b) commence à a et s'arrête avant b ; le troisième argument est le pas, qui peut être négatif.
Pour un produit, l'accumulateur part de
Compare le dernier terme ajouté par la boucle de l'élève au dernier terme de la somme voulue.
Correction détaillée
range(a, b) parcourt les entiers de range(n), qui abrège range(0, n), donne les range(1, n + 1), et c'est là que se glisse l'erreur la plus fréquente des boucles : oublier le dernier terme.
La convention a une vertu : range(a, b) fait exactement range(a, b) et range(b, c) se recollent sans chevauchement en range(a, c).
👉 Ce qu'on retient : avant d'écrire un range, se demander quel est le dernier entier voulu, et mettre son successeur comme borne de fin. Puis vérifier sur un petit
On fait afficher chaque range converti en liste par list :
print(list(range(5)))
print(list(range(2, 6)))
print(list(range(1, 10, 3)))
print(list(range(10, 0, -2)))
print(list(range(5, 2)))
print(list(range(1, 11, 3)))
[0, 1, 2, 3, 4]
[2, 3, 4, 5]
[1, 4, 7]
[10, 8, 6, 4, 2]
[]
[1, 4, 7, 10]
range(5) donne les range(2, 6) en donne range(5, 2) est vide : ce n'est pas une erreur, la boucle fait simplement zéro tour.
Compter les tours. On le vérifie avec un compteur, sur un cas où la réponse n'est pas évidente à l'œil :
t = 0
for i in range(3, 20):
t = t + 1
print(t)
17
On trouve bien range(a, b, p) parcourt les entiers range(1, 11, 3), parcourt
Un produit s'accumule dans une variable qui part de
n = 5
f = 1
for i in range(1, n + 1):
f = f * i
print(f)
120
Tableau de trace pour
| étape | i | f |
|---|---|---|
| avant | — | 1 |
| après le tour 1 | 1 | 1 |
| après le tour 2 | 2 | 2 |
| après le tour 3 | 3 | 6 |
| après le tour 4 | 4 | 24 |
| après le tour 5 | 5 | 120 |
Avant la boucle, i n'existe pas encore : la variable d'une boucle for est créée au premier tour. La boucle fait f vaut f vaut f vaut donc
Recoupement sur plusieurs valeurs, dont le cas limite
for n in [0, 1, 5, 10]:
f = 1
for i in range(1, n + 1):
f = f * i
print(n, f)
0 1
1 1
5 120
10 3628800
Pour range(1, 1) est vide : zéro tour, et f reste à
On lance la boucle de l'élève pour
n = 10
s = 0
for k in range(1, n):
s = s + k * k
print(s)
285
La formule donne range(1, n) parcourt
Correction : la borne de fin est le successeur du dernier entier voulu.
n = 10
s = 0
for k in range(1, n + 1):
s = s + k * k
f = n * (n + 1) * (2 * n + 1) // 6
print(s, f)
385 385
On calcule la formule avec // : le produit s sans arrondi.
Un exemple ne prouve pas l'égalité, mais cent et un exemples réussis forment un bon contrôle — et un seul échec suffirait à réfuter. On reprend la boucle corrigée pour chaque assert :
for n in range(0, 101):
s = 0
for k in range(1, n + 1):
s = s + k * k
g = n * (n + 1) * (2 * n + 1)
assert s == g // 6
print('101 valeurs : ok')
101 valeurs : ok
Le print final n'est atteint que si aucun assert n'a échoué. Pour range(1, 1) est vide, s reste à
Un contrôle doit savoir échouer. Le même programme avec la boucle de l'élève, et n ajouté à l'assert pour qu'il affiche la première valeur fautive :
for n in range(0, 101):
s = 0
for k in range(1, n):
s = s + k * k
g = n * (n + 1) * (2 * n + 1)
assert s == g // 6, n
print('101 valeurs : ok')
AssertionError: 1
Il s'arrête dès range(1, 1) est vide, la somme vaut
La fonction range.
range(b)parcourt; range(a, b)parcourt( entiers si , aucun sinon) ; range(a, b, p)avance deet s'arrête avant d'atteindre ou de dépasser , un pas négatif faisant descendre. La borne de fin est toujours exclue. Accumulateur. Une somme part de
, un produit de ; à chaque tour, s = s + termeouf = f * terme. Pour, la boucle est for k in range(1, n + 1).Trace. Le tableau de trace donne l'état des variables avant la boucle, puis après chaque tour : c'est l'outil pour vérifier une boucle sur un petit cas.
⚠️ Le décalage d'une unité aux bornes. range(1, n) oublie range(n) commence à range(1, n + 1). L'erreur ne produit ni plantage ni message, seulement une somme fausse ; seuls un calcul de contrôle ou une trace la révèlent.
⚠️ Initialiser un produit à f = 0 puis f = f * i donne
⚠️ Modifier la variable de boucle dans le corps. Écrire i = i + 1 dans une boucle for ne fait sauter aucun tour : au tour suivant, i reprend la valeur suivante de range.
t = 0
for i in range(5):
i = i + 1
t = t + 1
print(t)
5
Cinq tours, comme sans la ligne i = i + 1. Pour contrôler soi-même l'avancée d'un indice, on utilise une boucle while (B4).
range(a, b) parcourt range(1, 10, 3) donne range(10, 0, -2) donne range(5, 2) est vide. Factorielle : f part de range(1, n) affiche range(1, n + 1), elle donne Algorithme de seuil : la boucle while
Un capital de
1. Écrire un programme qui affiche le premier rang
2. Un élève recopie la condition de l'énoncé et écrit while u >= 2000:. Qu'affiche son programme ?
3. Pour while v < 8: et avec while v <= 8:. Lequel répond à « premier rang où
4. Expliquer pourquoi cette boucle ne s'arrête jamais :
u = 1000
n = 0
while u < 2000:
n = n + 1
5. Retrouver le rang de la question 1 par le logarithme.
Indices (3)
La condition d'un while est celle qui fait continuer : pour s'arrêter au premier
Fais avancer n au même tour que u : après chaque tour, u doit contenir n.
Une boucle while ne s'arrête que si sa condition finit par devenir fausse : quelle instruction du corps fait évoluer u ?
Correction détaillée
Une boucle while répète son corps tant que sa condition est vraie ; on l'utilise quand on ne sait pas d'avance combien de tours il faudra. C'est la situation d'un algorithme de seuil : on calcule les termes un par un et on s'arrête au premier qui franchit le seuil, sans connaître ce rang à l'avance.
Deux réflexes font un algorithme de seuil correct. D'abord, la condition du while est la négation de la condition d'arrêt : on veut s'arrêter dès que u doit contenir n. Si ce lien est tenu, la valeur de n en sortie de boucle est le rang cherché, sans «
👉 Ce qu'on retient : écrire la condition de continuation, faire évoluer à chaque tour la variable qui y figure, et vérifier le résultat par un autre chemin — ici, le logarithme.
La boucle continue tant que le seuil n'est pas atteint :
u = 1000
n = 0
while u < 2000:
u = 1.05 * u
n = n + 1
print(n, u)
15 2078.9281794113685
Le capital dépasse
Trace pour le seuil
u = 1000
n = 0
while u < 1200:
u = 1.05 * u
n = n + 1
print(n, u)
4 1215.5062500000001
| étape | n | u |
|---|---|---|
| avant | 0 | 1000 |
| après le tour 1 | 1 | 1050.0 |
| après le tour 2 | 2 | 1102.5 |
| après le tour 3 | 3 | 1157.625 |
| après le tour 4 | 4 | 1215.5062500000001 |
Au test suivant, la condition u < 1200 est fausse : la boucle s'arrête avec u vaut 1215.5062500000001 et non 1215.50625 :
Avec la condition de l'énoncé à la place de sa négation :
u = 1000
n = 0
while u >= 2000:
u = 1.05 * u
n = n + 1
print(n, u)
0 1000
Au premier test, u >= 2000 est faux : le corps n'est jamais exécuté, et le programme affiche le rang while peut faire zéro tour ; c'est même le premier cas à vérifier. La condition d'un while dit quand continuer, pas quand s'arrêter : on y écrit la négation de « u < 2000.
Tant qu'aucun terme ne tombe pile sur le seuil, < et <= donnent le même rang ; la différence apparaît dans le cas d'égalité. Avec
v = 1
n = 0
while v < 8:
v = 2 * v
n = n + 1
print(n, v)
v = 1
n = 0
while v <= 8:
v = 2 * v
n = n + 1
print(n, v)
3 8
4 16
Avec while v < 8, la boucle s'arrête dès que while v <= 8, elle continue quand
Le programme de la question 4 ne termine pas : le banc de ce chapitre l'a lancé et l'a trouvé encore en train de tourner au bout de deux secondes.
u = 1000
n = 0
while u < 2000:
n = n + 1
La cause : le corps ne modifie jamais u. La condition u < 2000 est vraie au premier test, et comme rien ne change u, elle reste vraie à tous les tests suivants ; seul n augmente, indéfiniment. La règle : chaque tour doit rapprocher la condition de devenir fausse.
Il ne suffit pas que la variable change : il faut qu'elle finisse par franchir le seuil. Second piège, plus sournois — un seuil que la suite n'atteint jamais :
u = 1000
n = 0
while u < 2000:
u = 0.5 * u + 900
n = n + 1
Ici u change à chaque tour, mais la suite
import math
q = math.log(2) / math.log(1.05)
print(q)
print(1000 * 1.05 ** 14)
print(1000 * 1.05 ** 15)
14.206699082890461
1979.9315994393987
2078.9281794113685
Le quotient vaut environ 1000 * 1.05 ** 15 coïncide ici avec celle de la boucle.
La boucle garde un avantage sur le logarithme : elle marche aussi pour des suites dont on ne connaît pas de formule explicite, comme
Boucle while.
while condition:teste la condition avant chaque tour ; si elle est fausse dès le départ, le corps n'est jamais exécuté. On l'emploie quand le nombre de tours n'est pas connu d'avance.Algorithme de seuil. Pour le premier rang
tel que : initialiser uàet nà; tant que u < S, remplacerupar le terme suivant et augmenternde. En sortie, nest le rang cherché etuvaut. Terminaison. La boucle s'arrête si la suite franchit effectivement le seuil (par exemple si
) ; si la variable de la condition ne change pas, ou si la suite converge sans atteindre le seuil, elle tourne sans fin.
⚠️ Écrire la condition d'arrêt au lieu de la condition de continuation. while u >= 2000 en voulant « s'arrêter quand while not P, ou par la négation écrite en clair.
⚠️ Corriger le rang « à la main ». Afficher n - 1 ou n + 1 parce que « ça semble décalé » masque une erreur au lieu de la corriger. Si u et n avancent ensemble dans le corps, n est juste en sortie ; la trace sur un petit seuil le montre.
⚠️ Oublier de mettre à jour la variable testée. Une boucle while dont le corps ne modifie pas la variable de sa condition ne s'arrête jamais (question 4). Même chose si l'on écrit 1.05 * u sans l'affecter à u.
while u >= 2000 fait zéro tour et affiche 0 1000. Pour < donne le rang <= le rang u, la boucle ne s'arrête pas. Logarithme : Compteurs et accumulateurs : diviseurs et nombres parfaits
1. Écrire un programme qui, pour
2. Un entier somme_div(n) qui renvoie cette somme, et chercher les nombres parfaits inférieurs à
3. Un élève compte les diviseurs de
n = 36
for d in range(1, n + 1):
c = 0
if n % d == 0:
c = c + 1
Que vaut c à la fin ? Pourquoi ? Et pour
4. Retrouver le nombre et la somme des diviseurs de
Indices (3)
Un compteur augmente de
Pour les diviseurs autres que range ?
Regarde quelle instruction s'exécute à chaque tour, et ce qui reste dans c après le dernier tour.
Correction détaillée
Un compteur et un accumulateur sont des variables qui résument ce que la boucle a vu jusqu'ici. Le compteur c répond à « combien de fois ? » : il part de s répond à « combien en tout ? » : il part de
L'initialisation a lieu une seule fois, avant le premier tour. Placée dans la boucle, elle efface à chaque tour ce que les tours précédents avaient accumulé, et la variable ne résume plus que le dernier tour.
👉 Ce qu'on retient : pour chaque variable de résumé, se demander « que doit-elle valoir avant le premier tour ? » — la réponse pour « aucun élément vu » — et écrire cette valeur au-dessus de la boucle.
On parcourt les candidats n % d est nul.
n = 36
c = 0
s = 0
for d in range(1, n + 1):
if n % d == 0:
c = c + 1
s = s + d
print(c, s)
9 91
if : on compte et on ajoute exactement les mêmes
Contrôle par la liste. Pour vérifier, on construit la liste des diviseurs :
n = 36
t = []
for d in range(1, n + 1):
if n % d == 0:
t.append(d)
print(t)
print(len(t), sum(t))
[1, 2, 3, 4, 6, 9, 12, 18, 36]
9 91
Les diviseurs vont par paires
Pour les diviseurs stricts (autres que range(1, n). On enferme le calcul dans une fonction, puis on l'appelle pour chaque candidat :
def somme_div(n):
s = 0
for d in range(1, n):
if n % d == 0:
s = s + d
return s
for n in range(2, 1000):
if somme_div(n) == n:
print(n)
6
28
496
Trois nombres parfaits sous
def somme_div(n):
s = 0
for d in range(1, n):
if n % d == 0:
s = s + d
return s
print(somme_div(8128))
8128
Coût. La recherche sous n % d == 0 une fois pour chaque couple range(1, n // 2 + 1) suffit et divise le travail par deux.
On lance le programme de l'élève, en affichant c à la fin, pour
for n in [36, 35]:
for d in range(1, n + 1):
c = 0
if n % d == 0:
c = c + 1
print(n, c)
36 1
35 1
La ligne c = 0 est dans la boucle : elle s'exécute à chaque tour, avant le test. Au tour c repart de c ne dit donc qu'une chose : si le dernier candidat,
Correction : sortir c = 0 de la boucle et le placer au-dessus du for — c'est le programme de la question 1.
Un diviseur positif de
c = 0
s = 0
for i in range(3):
for j in range(3):
c = c + 1
s = s + 2 ** i * 3 ** j
print(c, s)
9 91
La même méthode donne, pour
Compteur.
c = 0avant la boucle ;c = c + 1à chaque cas favorable ; en sortie,cest le nombre de cas favorables.Accumulateur.
s = 0(somme) oup = 1(produit) avant la boucle ;s = s + xoup = p * xà chaque tour concerné.Divisibilité en Python. Pour
dnon nul,ddivisensi et seulement sin % d == 0. Les diviseurs positifs desont parmi ; ses diviseurs stricts parmi , et même parmi .
⚠️ Initialiser dans la boucle. c = 0 écrit sous le for remet le compteur à zéro à chaque tour : le résultat ne reflète que le dernier tour (question 3). La valeur initiale se pose une fois, avant la boucle.
⚠️ Oublier d'initialiser. Sans c = 0 avant la boucle, la première exécution de c = c + 1 lève une NameError : Python ne sait pas ce que vaut c.
⚠️ Compter range(1, n + 1) dans somme_div, la somme contient somme_div(n) == n ne réussit plus jamais, et la recherche ne trouve aucun nombre parfait.
if. Nombres parfaits sous somme_div(8128). Avec c = 0 dans la boucle, le programme affiche Boucles imbriquées : compter les exécutions
1. Qu'affiche ce programme ? Combien de fois la ligne ligne.append(i * j) est-elle exécutée ?
for i in range(1, 6):
ligne = []
for j in range(1, 6):
ligne.append(i * j)
print(ligne)
2. Combien de tours fait la boucle intérieure de ce programme, au total, pour
c = 0
for i in range(n):
for j in range(i + 1, n):
c = c + 1
3. Afficher les triplets pythagoriciens
4. Réduire ce nombre de tests en supprimant la boucle sur
Indices (3)
Le corps de la boucle intérieure s'exécute une fois pour chaque couple de valeurs parcouru : on multiplie les nombres de tours si la boucle intérieure ne dépend pas de i, on additionne sinon.
Pour range(i + 1, n) fait
Trois boucles emboîtées : c, puis b de a de
Correction détaillée
Dans deux boucles emboîtées, le corps intérieur s'exécute une fois pour chaque couple de valeurs des deux variables. La boucle extérieure fixe i ; pour ce i, la boucle intérieure fait tous ses tours ; puis on passe au i suivant, et la boucle intérieure recommence depuis le début.
Compter les exécutions du corps intérieur, c'est donc compter des couples. Si la boucle intérieure ne dépend pas de i — i. Ce nombre mesure le coût du programme : doubler
👉 Ce qu'on retient : pour des boucles imbriquées, écrire le nombre de tours intérieurs en fonction de la variable extérieure, puis sommer — et vérifier la somme avec un compteur.
On déroule les deux boucles :
for i in range(1, 6):
ligne = []
for j in range(1, 6):
ligne.append(i * j)
print(ligne)
[1, 2, 3, 4, 5]
[2, 4, 6, 8, 10]
[3, 6, 9, 12, 15]
[4, 8, 12, 16, 20]
[5, 10, 15, 20, 25]
Chaque tour de la boucle extérieure construit une ligne : ligne repart de la liste vide au début du tour — ici c'est voulu, contrairement au compteur de B5, car on veut une ligne neuve pour chaque i. La boucle intérieure la remplit avec les print l'affiche. La boucle extérieure fait ligne.append(i * j) est exécutée print
On le vérifie en comptant :
c = 0
for i in range(1, 6):
for j in range(1, 6):
c = c + 1
print(c)
25
Pour range(i + 1, n) parcourt
for n in [10, 20, 40]:
c = 0
for i in range(n):
for j in range(i + 1, n):
c = c + 1
print(n, c, n * (n - 1) // 2)
10 45 45
20 190 190
40 780 780
Pour
Variante. Avec range(i, n) au lieu de range(i + 1, n), on autorise
Trois boucles emboîtées énumèrent les triplets t compte les tests effectués.
t = 0
for c in range(1, 31):
for b in range(1, c):
for a in range(1, b):
t = t + 1
if a*a + b*b == c*c:
print(a, b, c)
print('tests :', t)
3 4 5
6 8 10
5 12 13
9 12 15
8 15 17
12 16 20
15 20 25
7 24 25
10 24 26
20 21 29
18 24 30
tests : 4060
On obtient c est la variable de la boucle extérieure. Cinq sont primitifs (sans diviseur commun aux trois nombres) :
Le test est effectué une fois par triplet t. ✓ Pour
b s'arrête à
import math
t = 0
for b in range(1, 30):
for a in range(1, b):
t = t + 1
s = a * a + b * b
c = round(math.sqrt(s))
if c * c == s and c <= 30:
print(a, b, c)
print('tests :', t)
3 4 5
6 8 10
5 12 13
9 12 15
8 15 17
12 16 20
15 20 25
20 21 29
7 24 25
10 24 26
18 24 30
tests : 406
Les mêmes
Boucles imbriquées. Le corps intérieur s'exécute une fois pour chaque combinaison des variables. Boucles indépendantes : on multiplie les nombres de tours (
). Boucle intérieure dépendant de la variable extérieure : on additionne, pour chaque valeur extérieure, le nombre de tours intérieurs. Sommes à connaître.
couples ; triplets . Coût. Une boucle sur
valeurs : de l'ordre de opérations ; deux boucles emboîtées : de l'ordre de ; trois : de l'ordre de . Diminuer le nombre de boucles emboîtées est le premier levier pour accélérer un programme.
⚠️ Additionner au lieu de multiplier. Deux boucles de
⚠️ Compter chaque couple deux fois. Avec for j in range(n) au lieu de range(i + 1, n), la paire range, plutôt que par un test qui laisserait la boucle parcourir les cas inutiles.
⚠️ Mal placer une initialisation. ligne = [] doit être entre les deux boucles pour repartir d'une ligne vide à chaque i ; au-dessus des deux boucles, on obtiendrait une seule liste qui s'allonge à chaque affichage, jusqu'à
[1, 2, 3, 4, 5] à [5, 10, 15, 20, 25], et append exécuté Indices de 0 à n−1 : lire, parcourir, modifier
On considère le programme suivant.
t = [7, 3, 9, 4, 6]
n = len(t)
print(t[0], t[n - 1], t[-1])
1. Qu'affiche-t-il ? Quels sont l'indice du premier élément et celui du dernier, pour une liste de longueur
2. Que se passe-t-il si l'on ajoute la ligne print(t[n]) ? Quels indices négatifs sont permis ?
3. On veut doubler chaque élément de t. Lequel des deux parcours for x in t: x = 2 * x et for i in range(n): t[i] = 2 * t[i] modifie réellement la liste ?
4. Écrire une boucle qui affiche chaque indice suivi de l'élément correspondant, puis calculer la somme des éléments d'indice pair.
Indices (3)
Une liste de longueur
L'indice négatif t[-1] est le dernier élément. Cherche pour quels
Dans for x in t, x est un nom qui désigne tour à tour chaque élément : le réaffecter change ce que désigne x, pas le contenu de la case.
Correction détaillée
Un indice n'est pas un rang, c'est un décalage depuis le début. Le premier élément est à distance t[-k] désigne t[n - k].
Deux façons de parcourir une liste en découlent. Par valeur (for x in t) : on lit les éléments un à un, sans savoir à quelle position on se trouve. Par indice (for i in range(len(t))) : on connaît la position i, et l'on peut écrire dans la case t[i]. Seul le second parcours donne directement la case à modifier.
👉 Retiens le couple « range(n) parcourt exactement les bonnes cases et pourquoi t[n] sort du tableau.
On relance le programme de l'énoncé :
t = [7, 3, 9, 4, 6]
n = len(t)
print(t[0], t[n - 1], t[-1])
7 6 6
Ici t[0], qui vaut 7, et le dernier est t[4], qui vaut 6. L'écriture t[n - 1] désigne le dernier élément quelle que soit la longueur de la liste, ce qui la rend préférable à un t[4] écrit en dur. L'indice t[-k] comme t[n - k], donc t[-1] est t[4].
Vérification. On contrôle la règle t[-k] == t[n - k] pour tous les assert, puis on lit deux indices négatifs :
t = [7, 3, 9, 4, 6]
n = len(t)
for k in range(1, n + 1):
assert t[-k] == t[n - k]
print(t[-5], t[-2])
7 4
Les cinq assert passent sans rien dire — sinon le programme s'arrêterait sur une AssertionError —, t[-5] est bien t[0] et t[-2] l'avant-dernier élément.
Ajoutons la ligne print(t[n]) après une lecture correcte du dernier élément :
t = [7, 3, 9, 4, 6]
n = len(t)
print(t[n - 1])
print(t[n])
6
IndexError: list index out of range
La première lecture affiche 6 normalement ; la seconde demande la case d'indice 5, qui n'existe pas dans une liste de 5 éléments. Python ne renvoie pas une valeur par défaut : il interrompt le programme sur une IndexError (« indice de liste hors limites »), et la dernière ligne de son message est celle qu'on lit ci-dessus.
Côté négatif, la règle t[-k] == t[n - k] n'a de sens que si t[-6] lèverait la même erreur. Au total, un indice
Vérification. Les deux bornes ont été éprouvées : t[-5] a fonctionné au bloc précédent (c'est la plus petite valeur permise), et t[5] échoue ici (c'est la première valeur interdite à droite).
On essaie les deux boucles l'une après l'autre sur la même liste :
t = [7, 3, 9, 4, 6]
for x in t:
x = 2 * x
print(t)
for i in range(len(t)):
t[i] = 2 * t[i]
print(t)
print(sum(t))
[7, 3, 9, 4, 6]
[14, 6, 18, 8, 12]
58
La première boucle ne change rien : à chaque tour, x désigne un élément, puis x = 2 * x fabrique un nouveau nombre et y attache le nom x. La case de la liste n'a jamais été visée. La seconde boucle écrit dans la case t[i] elle-même : c'est une affectation dans la liste, et elle seule double les éléments.
Vérification. La somme de départ vaut sum(t) affiche
Le parcours par indice donne la position et la valeur à chaque tour. Pour la somme, on écrit deux versions :
t = [7, 3, 9, 4, 6]
n = len(t)
for i in range(n):
print(i, t[i])
s = 0
for i in range(0, n, 2):
s = s + t[i]
s2 = 0
for i in range(n):
if i % 2 == 0:
s2 = s2 + t[i]
print(s, s2)
0 7
1 3
2 9
3 4
4 6
22 22
La somme porte sur les indices range(0, n, 2) (3 tours) ; la seconde parcourt tout et filtre par i % 2 == 0 (5 tours, dont 3 retenus).
Vérification. Les deux versions affichent 22. ⚠️ « Indice pair » n'est pas « élément pair » : les éléments pairs de t sont 4 et 6, de somme 10. On somme ici selon la position, pas selon la valeur.
Indices. Une liste
tde longueurn = len(t)a pour indices. t[0]est le premier élément,t[n - 1](out[-1]) le dernier. Un indice négatif, pour , désigne t[n - k]. Tout autre indice lèveIndexError.Parcours.
for x in t:lit les valeurs (réaffecterxne modifie past) ;for i in range(len(t)):donne les positions et permet d'écriret[i] = ….range(n)produit exactement lesindices valides positifs ou nuls. Modifier une case :
t[i] = valeur. Réaffecter la variable de bouclexne modifie pas la liste.
⚠️ Écrire t[len(t)] pour le dernier élément. C'est l'erreur du « un de trop » : la longueur compte les cases, le dernier indice vaut un de moins. Le bon réflexe : t[len(t) - 1] ou t[-1].
⚠️ Boucler sur range(1, n + 1) en pensant « de la première à la dernière case » : tu sautes t[0] et tu finis sur t[n], donc sur une IndexError. Les positions vont de range(n).
⚠️ Croire que for x in t: x = 2 * x double la liste. Aucun message ne prévient : le programme tourne, et la liste reste intacte. Pour modifier, passe par les indices.
7 6 6 : les indices vont de t[-1] désigne t[n - 1]. t[n] lève IndexError: list index out of range ; les indices valides vont de t, qui devient [14, 6, 18, 8, 12] (somme Maximum d'une liste et position du maximum
On cherche le plus grand élément d'une liste t non vide, et un indice où il est atteint.
t = [3, 8, 2, 8, 5]
m = t[0]
p = 0
for i in range(1, len(t)):
if t[i] > m:
m = t[i]
p = i
print(m, p)
1. Dresser le tableau de trace des variables i, m et p, et donner l'affichage.
2. Un élève écrit m = 0 au lieu de m = t[0]. Qu'affiche alors son programme pour t = [-5, -2, -9, -3] ? Pourquoi m = t[0] est-il le bon choix ?
3. On remplace t[i] > m par t[i] >= m. Quelle occurrence du maximum chaque version repère-t-elle ?
4. Combien de comparaisons le programme effectue-t-il pour une liste de longueur position_max(t) qui renvoie l'indice du maximum.
Indices (3)
Pour la trace, écris une ligne « avant » (l'état après les deux affectations initiales), puis une ligne par tour de boucle : i prend les valeurs 1, 2, 3, 4.
Avec m = 0 et une liste de nombres négatifs, le test t[i] > m peut-il être vrai une seule fois ?
Avec >=, une égalité suffit à mettre p à jour : regarde le tour où t[i] vaut 8 pour la deuxième fois.
Correction détaillée
L'algorithme du maximum est un accumulateur qui garde « le meilleur vu jusqu'ici ». Après le tour d'indice m est le plus grand des éléments t[0], …, t[i], et p un indice où il est atteint. Avant tout tour, le seul élément vu est t[0] : c'est donc la seule valeur légitime pour m. Chaque tour compare un nouvel élément au meilleur courant, et le remplace s'il fait mieux. À la fin, tout a été vu : m est le maximum de la liste.
👉 Deux décisions font toute la différence : l'initialisation (prendre un élément de la liste, jamais une valeur inventée comme 0) et l'inégalité (> garde la première occurrence, >= la dernière). Aucune des deux n'est un détail : chacune change l'affichage.
On relance le programme et on suit les trois variables. La ligne « avant » donne l'état juste avant la boucle : i n'existe pas encore.
t = [3, 8, 2, 8, 5]
m = t[0]
p = 0
for i in range(1, len(t)):
if t[i] > m:
m = t[i]
p = i
print(m, p)
8 1
| Étape | i | m | p |
|---|---|---|---|
| avant | — | 3 | 0 |
| après le tour 1 | 1 | 8 | 1 |
| après le tour 2 | 2 | 8 | 1 |
| après le tour 3 | 3 | 8 | 1 |
| après le tour 4 | 4 | 8 | 1 |
Au tour 1, t[1] vaut 8 et m et p changent ensemble. Aux tours 2 et 4, t[i] vaut 2 puis 5, plus petits que 8 : rien ne bouge. Le tour 3 est le seul délicat : t[3] vaut 8, et le test p reste à 1. La boucle fait range(1, 5) produit 1, 2, 3, 4.
Vérification. Le maximum se lit sur la liste : 8, présent aux indices 1 et 3. On a bien t[p] égal à m, et le programme a retenu la première des deux occurrences.
Le programme de l'élève, sur une liste entièrement négative :
t = [-5, -2, -9, -3]
m = 0
p = 0
for i in range(1, len(t)):
if t[i] > m:
m = t[i]
p = i
print(m, p)
0 0
Il annonce 0 comme maximum, alors que 0 n'est même pas dans la liste, et l'indice 0, qui désigne t[i] > m compare à chaque tour un nombre négatif à 0 : il est toujours faux, et les valeurs initiales survivent jusqu'au bout. Le défaut ne se voit pas seulement sur les listes entièrement négatives. Comme la boucle part de l'indice t[0] n'est jamais comparé : dès que le maximum n'est atteint qu'en tête et qu'il est non nul, le programme le manque ([9, 1, 2] donne 2 2), et dès que les éléments d'indice 0 0 ([5, -1] donne 0 0). Sur [3, 8, 2, 8, 5], le maximum est positif et n'est pas en tête : m = 0 donne la bonne réponse, ce qui rend l'erreur invisible sur un essai « ordinaire ».
Avec m = t[0], la valeur de départ appartient à la liste, donc le résultat aussi, quels que soient les signes :
t = [-5, -2, -9, -3]
m = t[0]
p = 0
for i in range(1, len(t)):
if t[i] > m:
m = t[i]
p = i
print(m, p)
print(max(t))
-2 1
-2
Vérification. La fonction max de Python donne le même t[1] vaut bien [], la case t[0] n'existe pas.
On remplace > par >= :
t = [3, 8, 2, 8, 5]
m = t[0]
p = 0
for i in range(1, len(t)):
if t[i] >= m:
m = t[i]
p = i
print(m, p)
8 3
| Étape | i | m | p |
|---|---|---|---|
| avant | — | 3 | 0 |
| après le tour 1 | 1 | 8 | 1 |
| après le tour 2 | 2 | 8 | 1 |
| après le tour 3 | 3 | 8 | 3 |
| après le tour 4 | 4 | 8 | 3 |
Seul le tour 3 diffère : p passe à 3. Avec >, un élément égal au maximum courant ne le détrône pas : on garde la première occurrence. Avec >=, il le remplace : on garde la dernière. Le maximum m est le même dans les deux versions ; seule la position change, et seulement quand le maximum apparaît plusieurs fois.
Vérification. Sur une liste dont le maximum est unique, les deux versions doivent s'accorder. On les fait tourner côte à côte :
t = [3, 9, 2]
p1 = 0
p2 = 0
for i in range(1, len(t)):
if t[i] > t[p1]:
p1 = i
if t[i] >= t[p2]:
p2 = i
print(p1, p2)
1 1
Même indice : la différence ne se voit qu'avec des ex æquo, d'où l'intérêt d'un jeu de test qui en contienne.
La boucle parcourt les indices
t = [4, 1, 7, 7, 2, 9, 3]
c = 0
p = 0
for i in range(1, len(t)):
c = c + 1
if t[i] > t[p]:
p = i
print(len(t), c, p)
7 6 5
On lit m : t[p] est le maximum courant, et la fonction renvoie sa position.
def position_max(t):
p = 0
for i in range(1, len(t)):
if t[i] > t[p]:
p = i
return p
t = [3, 8, 2, 8, 5]
p = position_max(t)
print(p, t[p])
print(position_max([-5, -2, -9]))
print(position_max([4]))
1 8
1
0
Vérification. La première ligne redonne l'indice 1 et la valeur 8 du tableau de trace ; la liste de négatifs donne l'indice 1, celui de range(1, 1) est vide.
Maximum d'une liste non vide. Initialiser le maximum courant avec
t[0]et sa position avec 0, puis parcourir les indices de 1 àlen(t) - 1en remplaçant le maximum courant dès qu'un élément le dépasse. Coût :comparaisons. Occurrence retenue. Avec
>, on garde la première position du maximum ; avec>=, la dernière.Minimum. Même algorithme en renversant l'inégalité.
⚠️ Initialiser avec 0, ou avec une « très petite » valeur choisie au hasard comme t[0], et le résultat est faux exactement quand t[0] ne vaut pas cette valeur et que, soit le maximum n'est atteint qu'en tête, soit les autres éléments sont tous inférieurs ou égaux à cette valeur. Aucune constante finie ne convient à toutes les listes : on initialise avec un élément de la liste, et avec m = t[0], partir de l'indice
⚠️ Oublier de mettre à jour p en même temps que m, ou placer p = i hors du if : p finit alors sur le dernier indice parcouru, pas sur celui du maximum. Les deux affectations vont ensemble, dans le même bloc.
m passe de 3 à 8 au tour 1, puis ne bouge plus ; affichage 8 1. Avec m = 0, la liste [-5, -2, -9, -3] donne 0 0, un résultat faux ; avec m = t[0], on obtient -2 1. > garde la première occurrence du maximum (indice 1), >= la dernière (indice 3). Coût : position_max renvoie l'indice, ici 1.Recherche séquentielle et comptage
On travaille sur la liste t = [4, 7, 1, 7, 9, 7].
1. Écrire une fonction indice(t, x) qui renvoie le premier indice où x apparaît dans t, et -1 si x n'y est pas. La tester avec x = 7, x = 5 et x = 4.
2. Combien de comparaisons t[i] == x sont effectuées dans chacun de ces cas ? Et au pire, pour une liste de longueur
3. Un élève propose la version suivante. Que renvoie-t-elle pour x = 7 ? Où est l'erreur ?
def indice(t, x):
for i in range(len(t)):
if t[i] == x:
return i
else:
return -1
4. Écrire une fonction compte(t, x) qui renvoie le nombre d'occurrences de x. Pourquoi ne peut-elle pas s'arrêter à la première occurrence trouvée ?
Indices (3)
Un return placé dans la boucle arrête la fonction immédiatement : c'est exactement la « sortie anticipée » voulue quand on trouve x.
Le return -1 ne doit être atteint qu'une fois toutes les cases examinées : où faut-il donc le placer par rapport à la boucle ?
Pour compter les comparaisons, ajoute un compteur c augmenté de 1 juste avant chaque test t[i] == x.
Correction détaillée
Chercher, c'est parcourir jusqu'à trouver ; compter, c'est parcourir jusqu'au bout. La recherche séquentielle examine les cases dans l'ordre et s'arrête dès qu'elle trouve : l'instruction return sort de la fonction au milieu de la boucle, et c'est voulu. Elle ne peut conclure « absent » qu'après avoir tout examiné : le return -1 se place donc après la boucle, pas dans un else. Le comptage, lui, ne peut jamais s'arrêter en route, puisqu'une occurrence plus loin changerait le résultat.
👉 Retiens l'emplacement des deux return : l'un dans la boucle (« trouvé »), l'autre après (« pas trouvé »). Ce schéma revient dans toute fonction qui cherche un témoin : premier diviseur d'un entier, premier terme d'une suite qui dépasse un seuil…
On écrit la fonction et on la teste, y compris sur une liste vide :
def indice(t, x):
for i in range(len(t)):
if t[i] == x:
return i
return -1
t = [4, 7, 1, 7, 9, 7]
print(indice(t, 7))
print(indice(t, 5))
print(indice(t, 4))
print(indice([], 7))
1
-1
0
-1
Pour x = 7, la boucle s'arrête à i = 1 : les occurrences suivantes, aux indices 3 et 5, ne sont jamais regardées. Pour x = 5, aucune case ne convient, la boucle se termine normalement et l'on atteint return -1. Pour x = 4, l'élément est en tête : réponse 0, qui est un indice valide — d'où le choix de
Vérification. On relit t : t[0] vaut 4, différent de 7, et t[1] vaut 7, donc 1 est bien le premier indice de 7 ; quant à 5, il n'apparaît dans aucune des six cases.
On ajoute un compteur, et la fonction renvoie deux valeurs, récupérées par r, c = … :
def indice_c(t, x):
c = 0
for i in range(len(t)):
c = c + 1
if t[i] == x:
return i, c
return -1, c
t = [4, 7, 1, 7, 9, 7]
for x in [7, 5, 4, 9]:
r, c = indice_c(t, x)
print(x, r, c)
7 1 2
5 -1 6
4 0 1
9 4 5
On lit une règle simple : si x est trouvé à l'indice x est absent ou seulement présent en dernière position ; le meilleur, 1 comparaison, quand x est en tête.
Vérification. Les quatre lignes respectent la règle :
On fait tourner la version de l'élève :
def indice(t, x):
for i in range(len(t)):
if t[i] == x:
return i
else:
return -1
t = [4, 7, 1, 7, 9, 7]
print(indice(t, 7))
print(indice([], 7))
-1
None
Elle renvoie x = 7, qui est pourtant dans la liste. Au premier tour, t[0] vaut 4 : le test échoue, la branche else s'exécute, et return -1 termine la fonction dès le premier tour. Cette version ne regarde donc jamais que t[0]. Deuxième défaut, plus sournois : sur une liste vide, la boucle ne fait aucun tour, aucun return n'est atteint, et la fonction renvoie None — ni un indice, ni
La correction consiste à supprimer le else et à placer return -1 après la boucle.
Vérification. La version corrigée, celle du bloc « La fonction et ses tests », renvoie bien 1 pour 7 et
Pour compter, aucune sortie anticipée : on parcourt tout, et l'on renvoie le compteur après la boucle.
def compte(t, x):
c = 0
for y in t:
if y == x:
c = c + 1
return c
t = [4, 7, 1, 7, 9, 7]
print(compte(t, 7), compte(t, 5))
s = 0
for x in [4, 7, 1, 9]:
s = s + compte(t, x)
print(s, len(t))
3 0
6 6
compte ne contient aucun return dans la boucle : elle examine les
Vérification. Chaque case contient exactement une valeur : la somme des nombres d'occurrences des valeurs distinctes 4, 7, 1, 9 doit redonner la longueur de la liste. La dernière ligne affiche 6 et 6.
Recherche séquentielle. Parcourir
t; dès quet[i] == x, renvoyeri. Après la boucle, renvoyer une valeur conventionnelle d'absence, ici. Coût : de 1 à comparaisons, quand xest absent.Comptage. Un compteur initialisé à 0 avant la boucle, augmenté de 1 à chaque occurrence ; aucune sortie anticipée n'est possible : toujours
comparaisons.
returntermine l'appel immédiatement, même au milieu d'une boucle. Une fonction qui se termine sans rencontrer dereturnrenvoieNone.
⚠️ Le return -1 dans un else : la fonction décide « absent » dès la première case différente de x. Un test sur un élément placé en tête passe quand même, ce qui masque l'erreur : teste toujours un élément placé ailleurs qu'en première position.
⚠️ Utiliser le résultat comme indice sans le contrôler. t[-1] est le dernier élément. Le programme suivant ne lève aucune erreur, et affiche un élément de la liste pour une valeur qui n'y figure pas :
def indice(t, x):
for i in range(len(t)):
if t[i] == x:
return i
return -1
t = [4, 7, 1, 7, 9, 5]
print(t[indice(t, 8)])
5
8 est absent, indice renvoie t[-1] vaut 5. Avant d'écrire t[r], teste if r != -1:.
indice renvoie 1, else: return -1 s'arrête au premier tour (None sur une liste vide. compte(t, 7) vaut 3 et parcourt toujours les Construire une liste : carrés, filtre, moyenne et variance
Les notes d'un groupe de six étudiants sont rangées dans la liste notes = [9, 14, 11, 8, 15, 15].
1. Construire, avec une boucle et append, la liste des carrés des notes, puis la liste des notes supérieures ou égales à 10.
2. Écrire une fonction moyenne(t) qui calcule la moyenne d'une liste non vide sans utiliser sum, et l'appliquer à notes.
3. Écrire une fonction variance(t) qui calcule la variance
4. Recouper la variance par la formule de Koenig
Indices (3)
Une liste se construit comme une somme : on part de la liste vide [] et on ajoute un élément par tour avec append.
La variance demande deux passages : d'abord la moyenne variance peut appeler moyenne.
Pour Koenig, la liste des carrés de la question 1 donne directement
Correction détaillée
Une liste se construit comme une somme se calcule. Pour une somme, on part de s = 0 et on ajoute un terme par tour ; pour une liste, on part de t = [] et on ajoute un élément par tour avec t.append(…). Filtrer, c'est n'ajouter que sous condition. La moyenne et la variance sont ensuite deux accumulateurs numériques : la moyenne se calcule en un parcours ; la variance en demande deux par sa définition, puisqu'il faut connaître
👉 Retiens la division par len(t) : c'est la variance de la série statistique, celle du chapitre de statistique descriptive. Et vérifie toujours une variance par un second chemin, la formule de Koenig, qui ne demande que la moyenne des carrés.
Les deux listes se remplissent dans la même boucle :
notes = [9, 14, 11, 8, 15, 15]
carres = []
bonnes = []
for x in notes:
carres.append(x * x)
if x >= 10:
bonnes.append(x)
print(carres)
print(bonnes)
print(len(carres), len(bonnes))
[81, 196, 121, 64, 225, 225]
[14, 11, 15, 15]
6 4
carres reçoit un élément à chaque tour, bonnes seulement quand le test x >= 10 est vrai. L'ordre d'origine est conservé, puisque append ajoute toujours en fin de liste.
Vérification. carres a autant d'éléments que notes, soit 6 ; bonnes en a 4, et les deux notes écartées, 9 et 8, complètent bien le compte :
On accumule la somme dans s, puis on divise par la longueur :
def moyenne(t):
s = 0
for x in t:
s = s + x
return s / len(t)
notes = [9, 14, 11, 8, 15, 15]
print(moyenne(notes))
print(sum(notes) / len(notes))
print(moyenne([10]))
12.0
12.0
10.0
La somme vaut 12.0 et non 12 : l'opérateur / renvoie toujours un flottant, même quand la division tombe juste. L'opérateur // donnerait l'entier 12 ici, mais il arrondirait à l'entier inférieur une moyenne non entière comme 12,5 : ce n'est pas le bon outil.
Vérification. Le calcul direct sum(notes) / len(notes) redonne 12.0, et la moyenne d'une liste à un seul élément est bien cet élément.
La fonction variance calcule d'abord la moyenne, puis accumule les carrés des écarts :
import math
def moyenne(t):
s = 0
for x in t:
s = s + x
return s / len(t)
def variance(t):
m = moyenne(t)
s = 0
for x in t:
s = s + (x - m) ** 2
return s / len(t)
notes = [9, 14, 11, 8, 15, 15]
v = variance(notes)
print(v)
print(round(math.sqrt(v), 2))
print(variance([12, 12, 12]))
8.0
2.83
0.0
Les écarts à la moyenne sont
Le programme affiche 8.0 puis 2.83, l'écart-type étant arrondi au centième par round(…, 2). On remarque que variance appelle moyenne une seule fois, avant la boucle : la placer dans la boucle referait le même calcul à chaque tour.
Vérification. Une série constante n'a aucune dispersion : variance([12, 12, 12]) affiche bien 0.0.
La liste des carrés de la question 1 donne la moyenne des carrés :
La formule de Koenig donne alors
notes = [9, 14, 11, 8, 15, 15]
n = len(notes)
s1 = 0
s2 = 0
for x in notes:
s1 = s1 + x
s2 = s2 + x * x
m = s1 / n
print(s2, s2 / n, m * m)
print(s2 / n - m * m)
912 152.0 144.0
8.0
Vérification. Les deux chemins, par les écarts et par Koenig, donnent 8. Si l'on avait divisé la somme des carrés des écarts par
Construire une liste.
t = []avant la boucle, puist.append(v)à chaque tour : ajout en fin de liste. Filtrer :if condition:puist.append(v).Moyenne.
; en Python, s / len(t)après une boucle d'accumulation. Le résultat de/est un flottant.Variance (en
). (Koenig) ; écart-type .
⚠️ Confondre la moyenne des carrés et le carré de la moyenne. Ici s2 / n à la place de s2 / n - m * m donnerait 152 au lieu de 8.
⚠️ Appeler moyenne sur une liste vide. len(t) vaut 0, et la division échoue :
def moyenne(t):
s = 0
for x in t:
s = s + x
return s / len(t)
print(moyenne([]))
ZeroDivisionError: division by zero
Une moyenne n'a de sens que sur une série non vide : tu peux le rendre explicite par assert len(t) > 0 en tête de fonction, qui arrête le programme dès l'entrée, sur la ligne de la condition violée, plutôt qu'au milieu d'un calcul.
[81, 196, 121, 64, 225, 225] ; notes supérieures ou égales à 10 : [14, 11, 15, 15]. Moyenne 12 (Python affiche 12.0, car / renvoie un flottant). Variance ZeroDivisionError.Fonctions : renvoyer n'est pas afficher, et un test de primalité
1. Qu'affiche le programme suivant ? Pourquoi sa dernière ligne n'affiche-t-elle pas 9 ?
def carre(x):
print(x * x)
y = carre(3)
print(y)
2. Une fonction double calcule y = 2 * x, puis renvoie y. On exécute l'instruction double(5) seule, sans ranger son résultat, puis print(y). Que se passe-t-il ?
3. Écrire une fonction booléenne est_premier(n) qui cherche un diviseur d à partir de 2 tant que d * d <= n. Justifier que cette borne suffit, et expliquer pourquoi <= ne peut pas être remplacé par <.
4. En déduire la liste des nombres premiers inférieurs à 30, puis le nombre de divisions effectuées pour tester
Indices (3)
print écrit à l'écran ; return renvoie une valeur à l'appelant. Que renvoie une fonction qui ne contient aucun return ?
Si
Pour le <=, essaie
Correction détaillée
Une fonction renvoie une valeur ; elle n'affiche rien, sauf si on le lui demande. return v termine l'appel et remplace l'expression f(…) par v, qu'on peut ranger dans une variable, additionner, tester. print(v) écrit v à l'écran pour un lecteur humain, et c'est tout : le programme, lui, ne récupère rien. Une fonction sans return renvoie la valeur spéciale None. Enfin, les variables créées dans une fonction sont locales : elles naissent à l'appel et disparaissent à la fin de l'appel.
👉 Retiens le critère : si le résultat doit servir — dans un calcul, dans un if, dans une liste —, il faut return. Une fonction booléenne comme est_premier en est l'exemple type : elle renvoie True ou False, et c'est un if qui s'en sert.
On lance le programme de l'énoncé :
def carre(x):
print(x * x)
y = carre(3)
print(y)
9
None
L'appel carre(3) exécute le corps de la fonction, qui affiche 9 : c'est la première ligne. Puis la fonction se termine sans rencontrer de return, et renvoie donc None : c'est cette valeur que reçoit y, et la seconde ligne affiche None. Le 9 est passé à l'écran, pas dans le programme. Toute utilisation de y dans un calcul, comme y + 1, provoquerait une TypeError.
La version correcte renvoie la valeur et laisse l'appelant décider de l'afficher :
def carre(x):
return x * x
y = carre(3)
print(y, y + 1)
print(carre(carre(2)))
9 10
16
Vérification. y + 1 vaut bien 10, et l'on peut même composer :
On appelle double(5) sans ranger le résultat, puis on lit y :
def double(x):
y = 2 * x
return y
double(5)
print(y)
NameError: name 'y' is not defined
L'appel crée bien un y valant 10, mais dans la fonction ; à la sortie, ce y disparaît, et la valeur renvoyée n'a été rangée nulle part puisque l'appel n'est pas affecté. Au niveau principal, aucun y n'existe : NameError, « le nom y n'est pas défini ». La bonne écriture récupère la valeur : y = double(5).
Un paramètre est lui aussi local : le réaffecter dans la fonction (x = 2 * x) ne change pas la variable de l'appelant, même si elle porte le même nom.
def double(x):
x = 2 * x
return x
x = 5
print(double(x), x)
10 5
Vérification. La fonction a renvoyé 10, et le x principal vaut toujours 5 : ce sont deux variables distinctes qui portent le même nom.
En revanche, une liste reçue en paramètre et modifiée sur place l'est aussi pour l'appelant, car le paramètre désigne la même liste (exercice E6) :
def f(t):
t[0] = 2 * t[0]
u = [5, 6]
f(u)
print(u)
[10, 6]
La borne. Soit
def est_premier(n):
if n < 2:
return False
d = 2
while d * d <= n:
if n % d == 0:
return False
d = d + 1
return True
for n in [1, 2, 9, 97]:
print(n, est_premier(n))
1 False
2 True
9 False
97 True
1 n'est pas premier, cas traité à part par n < 2 ; pour 2, la boucle ne fait aucun tour puisque d * d <= n reste dans les entiers : pas de math.sqrt, donc aucun arrondi de flottant à craindre.
Le <= est indispensable. Quand <, il n'est jamais testé :
def est_premier(n):
if n < 2:
return False
d = 2
while d * d < n:
if n % d == 0:
return False
d = d + 1
return True
for n in [9, 25, 49]:
print(n, est_premier(n))
9 True
25 True
49 True
9, 25 et 49 sont déclarés premiers à tort, comme tout carré d'un nombre premier, à commencer par 4.
Vérification. On compare est_premier à une version lente, qui essaie tous les
def lent(n):
if n < 2:
return False
for d in range(2, n):
if n % d == 0:
return False
return True
def est_premier(n):
if n < 2:
return False
d = 2
while d * d <= n:
if n % d == 0:
return False
d = d + 1
return True
ok = True
for n in range(1000):
if lent(n) != est_premier(n):
ok = False
print(ok)
True
Les deux versions s'accordent sur
On construit la liste avec append, comme une accumulation filtrée :
def est_premier(n):
if n < 2:
return False
d = 2
while d * d <= n:
if n % d == 0:
return False
d = d + 1
return True
p = []
for n in range(30):
if est_premier(n):
p.append(n)
print(p)
print(len(p))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
10
Pour
n = 97
c = 0
d = 2
while d * d <= n:
c = c + 1
d = d + 1
print(c, d)
8 10
Elle teste
Vérification.
returncontrereturn vrenvoievà l'appelant et termine l'appel ;print(v)affichevet ne renvoie rien. Sansreturn, une fonction renvoieNone.Portée. Les paramètres et les variables affectées dans une fonction sont locaux : invisibles hors de l'appel, distincts des variables de même nom du programme principal.
Primalité par essais de divisions. Un entier
est premier si et seulement si aucun entier tel que ne le divise. En Python : while d * d <= n.
⚠️ Mettre un print là où il faut un return. À l'écran, tout semble juste — le bon nombre s'affiche —, mais la valeur renvoyée est None, et l'erreur n'éclate que plus loin, quand on s'en sert. Une fonction qui calcule doit renvoyer ; c'est l'appelant qui décide d'afficher.
⚠️ Écrire return True dans la boucle de est_premier, dès qu'un d ne divise pas
⚠️ Oublier le cas
def est_premier(n):
d = 2
while d * d <= n:
if n % d == 0:
return False
d = d + 1
return True
for n in [0, 1]:
print(n, est_premier(n))
0 True
1 True
9 puis None : carre affiche au lieu de renvoyer, et sans return une fonction renvoie None. Après double(5) non affecté, print(y) lève NameError : y était local. est_premier teste les <, les carrés de nombres premiers, comme 4, 9, 25 et 49, passeraient pour premiers. Premiers inférieurs à 30 : 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ; tester 97 coûte 8 divisions.Aliasing : deux noms pour une même liste
1. Qu'affiche le programme suivant ? Expliquer.
a = [1, 2, 3]
b = a
b[0] = 99
print(a)
2. Comment obtenir une liste b indépendante de a, qu'on puisse modifier sans toucher à a ?
3. On définit def f(t): t.append(0) et def g(n): n = n + 1. Après u = [5], k = 5, f(u) et g(k), que valent u et k ?
4. Dans une fonction qui reçoit une liste t, comparer t.append(x) et t = t + [x] : lequel modifie la liste de l'appelant ?
Indices (3)
Une affectation b = a ne fabrique aucune liste : elle donne un second nom à la liste qui existe déjà.
list(a) fabrique une nouvelle liste qui contient les mêmes éléments que a.
Distingue modifier un objet (t.append(…), t[i] = …) et réaffecter un nom (t = …, n = …) : seul le premier se voit de l'extérieur.
Correction détaillée
En Python, une variable est une étiquette posée sur un objet, pas une boîte qui le contient. a = [1, 2, 3] fabrique une liste et y colle l'étiquette a ; b = a colle une seconde étiquette sur la même liste. Il n'y a qu'une liste : la modifier par b se voit par a. C'est l'aliasing, deux noms pour un même objet.
Deux gestes sont à distinguer. Modifier l'objet (b[0] = 99, t.append(x)) : tous les noms qui le désignent voient le changement. Réaffecter un nom (t = t + [x], n = n + 1) : on fabrique un nouvel objet et on y déplace une étiquette ; les autres noms continuent de désigner l'ancien objet.
👉 Retiens : une liste passée à une fonction est partagée avec l'appelant, et la fonction peut la modifier ; un entier ne se modifie pas, il se remplace, donc une fonction ne peut pas changer l'entier de l'appelant.
On lance le programme de l'énoncé, en ajoutant un test d'identité :
a = [1, 2, 3]
b = a
b[0] = 99
print(a)
print(a is b)
[99, 2, 3]
True
b[0] = 99 modifie la liste désignée par b, qui est aussi celle désignée par a : a affiche [99, 2, 3]. Pour qui croyait avoir fait une copie, le programme est fautif, et pourtant il ne lève aucune erreur : rien ne signale l'aliasing.
Vérification. L'opérateur is teste si deux noms désignent le même objet ; a is b vaut True : il n'y a bien qu'une liste en mémoire.
list(a) construit une nouvelle liste contenant les mêmes éléments :
a = [1, 2, 3]
b = list(a)
print(a == b, a is b)
b[0] = 99
print(a, b)
True False
[1, 2, 3] [99, 2, 3]
Juste après la copie, a == b est vrai — mêmes valeurs, dans le même ordre — mais a is b est faux : ce sont deux objets distincts. Modifier b laisse donc a intact.
Vérification. La première ligne sépare les deux tests : == compare les contenus, is compare les identités. La seconde montre a inchangé à côté de b modifiée.
On fait tourner les deux fonctions de l'énoncé :
def f(t):
t.append(0)
def g(n):
n = n + 1
u = [5]
k = 5
f(u)
g(k)
print(u, k)
[5, 0] 5
À l'appel f(u), le paramètre t devient une étiquette de plus sur la liste de u : t.append(0) modifie cette liste, et u voit [5, 0]. À l'appel g(k), le paramètre n désigne l'entier 5 ; n = n + 1 fabrique l'entier 6 et y déplace l'étiquette locale n, sans toucher à k, qui vaut toujours 5. Un entier ne se modifie jamais sur place : il n'existe pas d'équivalent de append pour lui. Pour qu'une fonction « change » un entier, elle doit renvoyer la nouvelle valeur, que l'appelant réaffecte.
Vérification. L'affichage [5, 0] 5 montre les deux comportements côte à côte : la liste a changé, l'entier non.
On compare les deux écritures dans une fonction :
def ajoute1(t, x):
t.append(x)
def ajoute2(t, x):
t = t + [x]
a = [1, 2]
ajoute1(a, 3)
print(a)
ajoute2(a, 4)
print(a)
[1, 2, 3]
[1, 2, 3]
ajoute1 modifie la liste reçue : a devient [1, 2, 3]. ajoute2 calcule t + [x], qui est une nouvelle liste [1, 2, 3, 4], et y déplace l'étiquette locale t ; la liste de l'appelant n'est pas touchée, et la nouvelle liste est perdue à la fin de l'appel. a reste [1, 2, 3].
Le même contraste se voit hors de toute fonction, dès qu'une liste a deux noms :
a = [1, 2]
b = a
a = a + [3]
print(a, b)
c = [1, 2]
d = c
c.append(3)
print(c, d)
[1, 2, 3] [1, 2]
[1, 2, 3] [1, 2, 3]
a = a + [3] a fabriqué une nouvelle liste pour a seul ; c.append(3) a modifié la liste que c et d partagent. Autre différence : t + [x] recopie tous les éléments de t dans une nouvelle liste, alors que append ajoute en place, en temps constant en moyenne : Python réserve de la place d'avance et ne recopie la liste que de loin en loin.
Si l'on tient à t + [x] dans une fonction, il faut renvoyer la nouvelle liste et la récupérer :
def ajoute2(t, x):
t = t + [x]
return t
a = [1, 2]
a = ajoute2(a, 4)
print(a)
[1, 2, 4]
Vérification. Cette fois a contient bien 4 : la nouvelle liste a été renvoyée, puis l'étiquette a y a été déplacée par l'appelant lui-même.
Affectation d'une liste.
b = ane copie pas :aetbdésignent la même liste, eta is bvautTrue. Copie :b = list(a), une nouvelle liste (indépendante tant qu'elle contient des nombres).Modifier ou réaffecter.
t[i] = vett.append(v)modifient la liste, et tous ses noms le voient ;t = …réaffecte seulement le nomt.Passage à une fonction. Le paramètre désigne l'objet de l'appelant : une liste peut être modifiée par la fonction, un entier non. Pour « changer » un entier, la fonction renvoie la nouvelle valeur, et l'appelant la réaffecte.
⚠️ Croire que b = a copie la liste. Le cas typique : tu veux garder l'état initial d'une liste avant de la modifier, tu écris sauve = t, puis tu modifies t… et sauve a changé avec elle. Il fallait sauve = list(t).
⚠️ Une fonction qui modifie sa liste sans le dire. Une fonction qui écrit dans son paramètre modifie la liste de l'appelant, même quand ce n'était pas le but :
def dernier_double(t):
t[-1] = 2 * t[-1]
return t[-1]
notes = [8, 12, 15]
print(dernier_double(notes))
print(notes)
30
[8, 12, 30]
La fonction devait seulement calculer le double de la dernière note ; elle a réécrit la liste notes. Si la liste ne doit pas changer, calcule dans une expression (return 2 * t[-1]) ou travaille sur une copie.
b = a ne copie pas : a et b désignent la même liste, donc a affiche [99, 2, 3], et a is b vaut True. Copie : b = list(a), une nouvelle liste (indépendante tant qu'elle contient des nombres). Après f(u) et g(k), u vaut [5, 0] (liste modifiée sur place) et k vaut 5 (un entier ne se modifie pas). t.append(x) modifie la liste de l'appelant ; t = t + [x] fabrique une nouvelle liste locale, perdue si on ne la renvoie pas.