NSI, Première • Exercices corrigés à Montréal

Fiche de révision : Python, types, contrôle et tableaux (NSI Première)

Cette fiche ne reprend pas le cours de Python : vous l'avez déjà dans vos notes. Elle traite ce que le cours ne dit jamais et qui décide pourtant de la note, à savoir le bug exact qui coûte deux points sur une copie, la boucle à choisir selon ce que l'énoncé demande, et la preuve de terminaison que le correcteur attend.

Elle est écrite pour les élèves de Première qui suivent la spécialité numérique et sciences informatiques du programme français, au lycée en France comme dans le réseau AEFE. Une fois la fiche lue, la série d'exercices corrigés du même chapitre met chaque réflexe à l'épreuve, de la trace d'exécution jusqu'au traitement d'un relevé de températures.

Le fil du chapitre

Python fait exactement ce qu'on écrit, jamais ce qu'on voulait écrire. La moitié des points perdus vient de trois gestes : une borne de droite qu'on croit incluse, deux flottants comparés avec ====, et une affectation qui duplique une référence au lieu de l'objet.

Ce chapitre fait partie de NSI en Première

Avant ce chapitre

Cette fiche suppose ces notions acquises. Si une méthode ci-dessous reste opaque, c'est presque toujours l'une d'elles qui manque, pas la fiche.

Remonter plus loin : la chaîne complète (4 chapitres) ↓

Le chemin de remédiation, du plus ancien au plus proche. Un élève qui reprend ce chapitre de zéro le reprend dans cet ordre.

  1. 1Algorithmique et PythonSeconde, Mathématiques
  2. 2Algorithmique et ScratchCinquième, Mathématiques
  3. 3Algorithmique et ScratchQuatrième, Mathématiques
  4. 4Algorithmique et ScratchTroisième, Mathématiques

L'essentiel

Les bornes : ce que range et les tranches incluent vraiment

  • range(a, b) parcourt aa, a+1a+1, ..., b1b-1 : la borne de droite est EXCLUE. range(n) donne donc 0,1,,n10, 1, \ldots, n-1, soit nn valeurs.
  • Même règle pour les tranches : t[i:j] contient les indices ii à j1j-1. Le nombre d'éléments vaut jij-i, ce qui est la seule chose à retenir.
  • Les indices d'une liste de longueur nn vont de 00 à n1n-1. Le dernier élément est t[n - 1], ou t[-1], jamais t[n].
  • Une tranche crée une NOUVELLE liste : t[::-1] renvoie la liste renversée sans toucher à t, et t[:] en fabrique une copie.
abcdefgh01234567indicest = [a, b, c, d, e, f, g, h]range(2, 6) et t[2:6] : 2, 3, 4, 5la borne 6 est EXCLUE
L'accolade couvre exactement les quatre cases d'indices 22 à 55. La case 66 est dessinée, elle existe, et pourtant ni range(2, 6) ni t[2:6] ne l'atteignent.

Le réflexe qui évite l'erreur d'un cran : compter jij-i éléments plutôt que d'énumérer. range(2, 6) contient 62=46-2=4 valeurs, et cela se vérifie sans écrire la liste.

Ce que Python évalue, et dans quel ordre

  • a / b renvoie TOUJOURS un flottant, même sur deux entiers. a // b est le quotient entier, arrondi vers le BAS, et a % b le reste.
  • Arrondi vers le bas veut dire vers moins l'infini : 7 // 2-7\ //\ 2 vaut 4-4, pas 3-3. C'est le piège des entiers négatifs.
  • Ne jamais tester l'égalité de deux flottants avec ==== : on compare avec une tolérance, abs(a - b) < 1e-9.
  • and et or s'évaluent en COURT-CIRCUIT, de gauche à droite : dans a != 0 and b / a > 1, la division n'est jamais tentée si a vaut 00. L'ordre des tests est donc porteur de sens.

De Morgan, à savoir dans les deux sens : not (a and b) équivaut à (not a) or (not b), et not (a or b) équivaut à (not a) and (not b). Le not se distribue en RETOURNANT le connecteur.

Une variable ne contient pas un objet, elle le désigne

  • Une affectation copie une RÉFÉRENCE. Après b = a sur une liste, les deux noms désignent le même objet : modifier b modifie ce que voit a.
  • Sur les types immuables, entiers, flottants, chaînes et tuples, la question ne se pose pas : l'objet ne peut pas changer, donc le partage est invisible.
  • b = a[:] ou b = list(a) fabriquent une vraie copie, suffisante pour une liste de nombres. Pour une liste de listes, les sous-listes restent partagées.
  • Une fonction reçoit elle aussi la référence : elle peut modifier une liste passée en argument, et cette modification survit à la fin de l'appel.

Corollaire à connaître pour l'oral : une fonction qui modifie son argument et qui renvoie aussi une valeur fait deux choses à la fois, ce qu'une spécification claire évite.

Les pièges qui coûtent des points

Les erreurs ci-dessous sont celles que je corrige le plus souvent en séance. Chacune coûte des points sur une copie, même quand le raisonnement est juste.

1. Comparer deux flottants avec un double égal

2 points, et un programme qui ne s'arrête jamais si le test pilote une boucle

Ce qu'il ne faut pas écrire

« if 0.1 + 0.2 == 0.3: ... et le test passe, puisque c'est vrai en mathématiques. »

Ce qu'il faut écrire

« Les flottants sont des approximations binaires : 0.1 + 0.2 vaut 0.30000000000000004, donc le test est FAUX. On écrit if abs(0.1 + 0.2 - 0.3) < 1e-9. »

Pourquoi : Un dixième n'a pas d'écriture binaire finie, exactement comme un tiers n'a pas d'écriture décimale finie. L'erreur est minuscule mais l'égalité, elle, est binaire.

2. Croire la borne de droite incluse

toute la question quand la boucle sort du tableau

Ce qu'il ne faut pas écrire

« for i in range(1, n): parcourt tous les indices de 1 à n, donc tout le tableau sauf le premier. »

Ce qu'il faut écrire

« range(1, n) s'arrête AVANT n : il parcourt 1 à n - 1, ce qui est exactement la bonne plage pour une liste de longueur n. Pour comparer t[i] et t[i+1], il faut range(len(t) - 1). »

Pourquoi : Les indices vont de 00 à n1n-1 : la borne exclue de range est précisément ce qui fait coïncider les deux conventions. Le comprendre supprime toute l'incertitude d'un cran.

3. Croire qu'une affectation copie la liste

3 points, et un bug introuvable à la lecture

Ce qu'il ne faut pas écrire

« b = a puis b.append(4), et a n'a pas changé puisque j'ai travaillé sur b. »

Ce qu'il faut écrire

« b = a fait désigner le MÊME objet par les deux noms : a vaut aussi [1, 2, 3, 4] après l'append. Pour une vraie copie, il faut b = a[:] ou b = list(a). »

b = ab = a[:]ab[1, 2, 3]ab[1, 2, 3][1, 2, 3]un seul objetdeux objets
À gauche, une seule liste porte deux étiquettes : ce que fait bb, aa le subit. À droite, la tranche a fabriqué un second objet, et les deux noms deviennent indépendants.

Pourquoi : En Python, une variable est une étiquette posée sur un objet, jamais une boîte qui le contient. Dupliquer l'étiquette ne duplique pas l'objet.

4. Donner une liste comme valeur par défaut d'un paramètre

2 points, et une fonction dont le résultat dépend de l'historique

Ce qu'il ne faut pas écrire

« def ajoute(x, t=[]): t.append(x); return t, et chaque appel repart d'une liste vide. »

Ce qu'il faut écrire

« La valeur par défaut est créée UNE SEULE FOIS, à la définition : la même liste sert à tous les appels et se remplit. On écrit def ajoute(x, t=None): if t is None: t = []. »

Pourquoi : C'est le corollaire direct du piège précédent : la valeur par défaut est un objet, et il est partagé par tous les appels qui ne le remplacent pas.

5. Inverser la ligne et la colonne d'un tableau à deux dimensions

toute la question sur un tableau non carré, où l'erreur provoque un plantage

Ce qu'il ne faut pas écrire

« t[i][j] désigne la colonne i et la ligne j, comme des coordonnées x et y. »

Ce qu'il faut écrire

« t est une liste de LIGNES : t[i] est la ligne numéro i, et t[i][j] la case de colonne j dans cette ligne. len(t) donne le nombre de lignes, len(t[0]) le nombre de colonnes. »

i = 0i = 1i = 2j = 0j = 1j = 2t[1][2]len(t) = lignes, len(t[0]) = colonnest[i][j] : i la LIGNE, j la COLONNE
Le premier crochet descend dans les lignes, le second se déplace dans la ligne choisie. La case marquée est t[1][2] : deuxième ligne, troisième colonne.

Pourquoi : L'ordre vient de l'imbrication : on ouvre d'abord la liste extérieure, donc la ligne. Sur un tableau carré, l'erreur passe inaperçue jusqu'au jour où il ne l'est plus.

6. Afficher au lieu de renvoyer

2 points, et souvent l'exercice entier qui s'effondre après

Ce qu'il ne faut pas écrire

« def carre(x): print(x * x), puis y = carre(3) et j'utilise y ensuite. »

Ce qu'il faut écrire

« print AFFICHE, return RENVOIE. Sans return, la fonction renvoie None, donc y vaut None et la suite plante. On écrit def carre(x): return x * x. »

Pourquoi : Une fonction sert à produire une valeur réutilisable. L'affichage est un effet de bord destiné à l'humain, et il ne transmet rien au programme.

7. Confondre la division et le quotient entier

1 point, et un indice flottant qui plante l'accès au tableau

Ce qu'il ne faut pas écrire

« 7 / 2 vaut 3 puisque les deux nombres sont des entiers. »

Ce qu'il faut écrire

« 7 / 2 vaut 3.5, un flottant, TOUJOURS. Le quotient entier s'écrit 7 // 2, qui vaut 3, et l'arrondi se fait vers le bas : -7 // 2 vaut -4. »

Pourquoi : Un flottant ne peut pas servir d'indice : t[len(t) / 2] lève une erreur, alors que t[len(t) // 2] fonctionne. C'est là que le bug se manifeste, loin de la ligne fautive.

8. Écrire une boucle while sans exhiber son variant

2 points de justification, systématiquement demandés

Ce qu'il ne faut pas écrire

« while n != 1: n = n // 2, la boucle finit forcément par s'arrêter. »

Ce qu'il faut écrire

« Il faut EXHIBER un variant : ici n est un entier strictement positif qui décroît strictement à chaque tour, donc la boucle se termine. Sur un n négatif ou nul, elle ne s'arrête jamais, ce que la précondition doit exclure. »

Pourquoi : « Cela finit bien par s'arrêter » n'est pas une preuve. Le variant est une quantité entière, positive, strictement décroissante : les trois adjectifs sont exigés.

Quelle méthode choisir

Quelle boucle, d'après ce que l'énoncé demande

On regarde une seule chose : sait-on à l'avance COMBIEN de tours il faudra faire ?

  • Si le nombre de tours est connu à l'avance, ou dépend d'une longueur boucle bornée for avec range

    Exemple : for i in range(len(t)):

    C'est la boucle par défaut : elle se termine toujours, aucune preuve n'est à écrire.

  • Si on parcourt tous les éléments sans avoir besoin de leur position for x in t, qui parcourt les VALEURS

    Exemple : for temperature in releve:

    Plus lisible, et impossible de sortir du tableau.

  • Si on a besoin de l'indice, pour modifier une case ou comparer t[i] et t[i+1] for i in range(len(t)), ou range(len(t) - 1) si l'on regarde le suivant

    Exemple : for i in range(len(t) - 1): if t[i] > t[i+1]:

    Le 1-1 est exactement ce qui évite l'accès hors du tableau au dernier tour.

  • Si on s'arrête sur une condition, sans savoir au bout de combien de tours boucle while, accompagnée d'un variant à exhiber

    Exemple : while n > 1: n = n // 2

    Une boucle while sans variant écrit est une réponse incomplète, même si le code marche.

Modifier une liste PENDANT qu'on la parcourt fausse le parcours : on construit une nouvelle liste, ou on parcourt une copie avec t[:].

Copie ou référence : ce que l'affectation fait vraiment

La question ne se pose que pour les objets MUTABLES, c'est à dire les listes et les dictionnaires. On commence donc par regarder le type.

  • Si l'objet est un entier, un flottant, une chaîne ou un tuple aucun risque : ces types sont immuables, on ne peut pas les modifier en place

    Exemple : b = a puis b = b + 1 laisse a inchangé

  • Si l'objet est une liste et qu'on écrit b = a les deux noms désignent LE MÊME objet, toute modification par l'un se voit par l'autre

    Exemple : b.append(4) ajoute aussi dans a

  • Si l'objet est une liste de nombres et qu'on veut deux listes indépendantes copie de surface avec b = a[:] ou b = list(a)

    Exemple : b[0] = 99 ne touche plus a

  • Si l'objet est une liste de listes la copie de surface ne suffit pas : les sous-listes restent partagées

    Exemple : b = [ligne[:] for ligne in a] recopie chaque ligne

    C'est le cas des tableaux à deux dimensions, donc du chapitre suivant.

Le test qui tranche en une ligne : modifier b, puis afficher a. Si a a changé, les deux noms désignaient le même objet.

La rédaction attendue

Le correcteur coche des étapes. Les voici dans l'ordre, avec la phrase de conclusion qu'il attend mot pour mot.

Prouver qu'une boucle while se termine

Quand l'utiliser : Dès qu'une boucle non bornée apparaît, et systématiquement quand l'énoncé écrit « justifier que l'algorithme se termine ».

  1. 1 Nommer la quantité choisie comme variant, en une phrase : « Considérons la valeur de n au début de chaque tour. »
  2. 2 Montrer qu'elle est ENTIÈRE et POSITIVE, en s'appuyant sur la précondition de la fonction.
  3. 3 Montrer qu'elle DÉCROÎT STRICTEMENT à chaque tour, en citant l'instruction responsable.
  4. 4 Conclure : une suite d'entiers positifs strictement décroissante est finie, donc la boucle se termine.

Phrase de conclusion

La valeur de n est un entier strictement positif d'après la précondition, et l'instruction n = n // 2 la fait strictement décroître à chaque tour tant que n est supérieur à 1. Une suite d'entiers positifs strictement décroissante étant finie, la boucle se termine.

Le piège : Se contenter de « la boucle finit par s'arrêter ». Les trois propriétés, entière, positive, strictement décroissante, doivent être écrites une par une : c'est le barème.

Barème : En général 0,50{,}5 point pour le variant nommé, 0,50{,}5 pour la positivité, 11 point pour la décroissance stricte, 0,50{,}5 pour la conclusion.

Spécifier et tester une fonction

Quand l'utiliser : Dès que l'énoncé demande d'écrire une fonction, et toujours avant d'écrire la première ligne de code.

  1. 1 Écrire la docstring d'abord : ce que la fonction reçoit, ce qu'elle renvoie, et la précondition qui doit être vraie à l'appel.
  2. 2 Choisir les noms des paramètres et le TYPE de la valeur renvoyée, puis seulement écrire le corps.
  3. 3 Construire un jeu de tests avec au moins trois cas : un cas courant, un cas limite, et un cas qui a failli être oublié.
  4. 4 Les cas limites obligatoires : la liste vide, la liste à un seul élément, le premier et le dernier indice, et les valeurs négatives.

Phrase de conclusion

La fonction maximum(t) renvoie le couple constitué du plus grand élément de t et de son indice; sa précondition est que t soit une liste non vide de nombres. Testée sur [3, 9, 2, 9, 1], elle renvoie (9, 1), et sur [-5, -2, -9] elle renvoie (-2, 1).

Le piège : Ne tester que sur l'exemple de l'énoncé. Le cas qui fait tomber une copie est presque toujours la liste à un seul élément, ou une liste de valeurs toutes négatives quand le maximum a été initialisé à zéro.

Barème : 11 point pour la spécification, 22 points pour le code, 11 point pour un jeu de tests couvrant un cas limite.

Vérifier avant de rendre

Cinq minutes de vérification récupèrent plus de points qu'un exercice de plus commencé à la hâte.

L'exercice type décortiqué

Le maximum et son indice, spécifié, écrit, puis testé sur ses cas limites

Écrire une fonction maximum(t) qui renvoie le couple formé du plus grand élément de la liste t et de l'indice de sa première occurrence. Justifier le choix de l'initialisation et proposer un jeu de tests.

On testera sur [3, 9, 2, 9, 1], sur [7] et sur [-5, -2, -9].

python
def maximum(t):
    """Renvoie (m, i) : m le plus grand element de t,
    i l'indice de sa premiere occurrence.
    Precondition : t est une liste non vide de nombres."""
    m = t[0]
    imax = 0
    for i in range(1, len(t)):
        if t[i] > m:
            m = t[i]
            imax = i
    return m, imax

Étape 1

Spécification d'abord : la fonction reçoit une liste de nombres, renvoie un couple, et exige que la liste soit NON VIDE. Cette précondition est écrite avant la première ligne de code.

Pourquoi

La précondition n'est pas de la décoration : c'est elle qui autorise l'initialisation par t[0]. Sans elle, la ligne suivante planterait sur une liste vide.

Étape 2

Initialisation par le premier élément : m = t[0] et imax = 0, jamais m = 0.

Pourquoi

Initialiser à 00 est l'erreur classique : sur [-5, -2, -9], la fonction renverrait 00, une valeur qui n'appartient pas à la liste. Le premier élément, lui, en fait toujours partie.

Étape 3

Boucle for i in range(1, len(t)), donc à partir de l'indice 11 et jusqu'à len(t) - 1 inclus.

Pourquoi

On part de 11 parce que l'indice 00 a déjà servi à l'initialisation, et la borne de droite exclue fait s'arrêter la boucle exactement sur le dernier indice valide.

Étape 4

Comparaison stricte if t[i] > m, et mise à jour des DEUX variables ensemble à l'intérieur du if.

Pourquoi

Le supérieur strict garantit qu'on renvoie la PREMIÈRE occurrence en cas d'ex aequo, ce que la spécification promet. Avec un supérieur ou égal, on renverrait la dernière.

Étape 5

return m, imax à la fin, en dehors de la boucle et sans aucun print.

Pourquoi

Placé dans la boucle, le return sortirait au premier tour. Remplacé par un print, il ne transmettrait rien à l'appelant, qui recevrait None.

Étape 6

Jeu de tests : maximum([3, 9, 2, 9, 1]) donne (9, 1), maximum([7]) donne (7, 0), maximum([-5, -2, -9]) donne (-2, 1).

Pourquoi

Les trois cas ne testent pas la même chose : le premier vérifie l'ex aequo, le deuxième la boucle qui ne fait aucun tour, le troisième l'initialisation. Un test qui ne peut pas échouer ne sert à rien.

Conclusion rédigée

La fonction renvoie bien (9, 1) sur [3, 9, 2, 9, 1], (7, 0) sur la liste à un seul élément et (-2, 1) sur la liste de nombres négatifs. L'initialisation par t[0] et la comparaison strictement supérieure sont les deux choix qui rendent ces trois résultats corrects.

L'erreur classique sur cet exercice : Initialiser m à 00 et boucler sur range(len(t)). Sur [-5, -2, -9], la fonction renvoie alors 00 avec l'indice 00 : une valeur absente de la liste, et un indice qui ne la désigne pas.

À savoir par cœur

  • range(a, b) donne aa à b1b-1, soit bab-a valeurs; t[i:j] donne les indices ii à j1j-1. La borne de droite est TOUJOURS exclue.
  • Les indices d'une liste de longueur nn vont de 00 à n1n-1; le dernier est t[-1].
  • a / b renvoie un flottant, a // b le quotient entier arrondi vers le BAS : 7 // 2-7\ //\ 2 vaut 4-4.
  • Jamais de ==== entre deux flottants : comparer abs(a - b) < 1e-9.
  • and et or s'évaluent en court-circuit, de gauche à droite : l'ordre des tests protège la division par zéro.
  • b = a partage l'objet; b = a[:] le copie. Sur une liste de listes, la copie de surface ne suffit pas.
  • Terminaison d'un while : exhiber un variant ENTIER, POSITIF et STRICTEMENT DÉCROISSANT. Les trois mots comptent.

Questions fréquentes

Pourquoi range(1, n) ne va-t-il pas jusqu'à n ?

Parce que la borne de droite est exclue en Python, comme dans les tranches. range de 1 à n parcourt 1, 2, jusqu'à n moins un, ce qui fait n moins un valeurs. Cette convention est exactement celle qui fait coïncider range avec les indices d'une liste, qui vont de zéro à la longueur moins un.

Pourquoi 0.1 plus 0.2 n'est-il pas égal à 0.3 en Python ?

Parce que les flottants sont codés en binaire et qu'un dixième n'a pas d'écriture binaire finie, comme un tiers n'a pas d'écriture décimale finie. La somme vaut en réalité 0.30000000000000004. On ne teste donc jamais l'égalité de deux flottants : on compare leur écart à une tolérance.

Pourquoi ma liste a-t-elle changé alors que j'ai modifié une autre variable ?

Parce qu'une affectation copie la référence, pas l'objet. Après b égale a, les deux noms désignent la même liste en mémoire, donc tout ajout par b se voit par a. Pour obtenir deux listes indépendantes, il faut écrire b égale a entre crochets deux points, ou b égale list de a.

Quelle différence entre print et return dans une fonction ?

Print affiche un texte à destination de la personne qui lit l'écran, et ne transmet rien au programme. Return renvoie une valeur que l'appelant peut stocker et réutiliser. Une fonction sans return renvoie None, et toute utilisation de son résultat provoquera une erreur plus loin.

Comment prouver qu'une boucle while se termine en NSI ?

On exhibe un variant : une quantité entière, positive, et qui décroît strictement à chaque tour. On cite l'instruction responsable de la décroissance, puis on conclut qu'une suite d'entiers positifs strictement décroissante est finie. Les trois propriétés doivent être écrites séparément, c'est le barème.

Passer à la pratique

Exercices corrigés : Python : types, contrôle, fonctions et tableaux

Une méthode se prouve sur une copie, pas sur une fiche. La série du même chapitre reprend chacun de ces pièges dans un exercice, avec le corrigé rédigé étape par étape.

  • 15 exercices corrigés
  • 150 points
  • 225 minutes
Faire les exercices
Fiche suivante Représentation des données : réels et texte

Ce chapitre resservira dans

Les chapitres qui le réclament en amont, plus tard dans l'année ou dans les années suivantes.

Voir aussi

Vous cherchez un tuteur de NSI à Montréal ?

Contactez-moi pour une première séance. On reprend les réflexes qui coûtent des points en devoir, de la trace d'exécution jusqu'à la preuve de terminaison d'une boucle, puis on les met à l'épreuve sur du code du niveau réel des évaluations de Première.

Site par Studio Squalli