NSI · Première
Programmer en Python : comprendre, tester et corriger
Un programme transforme un état dans un ordre précis. Pour le comprendre, tu vas suivre les valeurs, prévoir les branches et expliquer chaque tour de boucle. Pour le rendre fiable, tu préciseras les entrées acceptées, le résultat attendu et ce qui ne doit pas être modifié.
Explications et exemples en accès libre. Ateliers, quiz et cartes avec l’abonnement.
Prévoir mon tempsExplications et exemples en accès libre
Une première étude des explications, schémas, exemples résolus et erreurs expliquées. Les activités, productions, quiz et cartes réservés à l’abonnement ne sont pas comptés ici.
- Comprendre2059 mots d’explication et 4 schémas
- 13 à 21 min
- Étudier les exemples et les erreurs18 cas, exemples et activités guidés
- 36 à 66 min
Étude du cours en accès libre, environ45 min à 1 h 30
Voir le calcul et le temps par étape
Le calcul suit automatiquement les explications et les tâches présentes dans le cours. Ses coefficients sont des repères de planification, pas des temps mesurés auprès d’élèves.
- Lecture active : 2059 mots, à raison de 160 à 220 mots par minute.
- Schémas : 4, avec 1 à 2 min pour lire chacun.
- Exemple guidé : 6 × 2 à 4 min.
- Suivre le code résolu et ses tests : 6 × 3 à 5 min.
- Comprendre une erreur expliquée : 6 × 1 à 2 min.
- Séquence, condition, boucle : suivre l'état au lieu de deviner8 à 15 min
- Fonctions : annoncer le contrat avant d'écrire le corps7 à 14 min
- Portée et mutation : savoir quelle valeur une fonction peut changer8 à 15 min
- Jeux de tests : choisir des cas qui peuvent réellement révéler l'erreur8 à 15 min
- Mise au point : du symptôme reproductible à la première divergence7 à 13 min
- Interfaces et langages : conserver l'algorithme, vérifier la sémantique8 à 16 min
Les sous-totaux sont arrondis à la minute, puis additionnés. La fourchette totale est élargie aux cinq minutes voisines. Les étapes ci-dessus comprennent leurs explications et exemples ; elles ne s’ajoutent pas une seconde fois au total.
Adapte ce repère à tes acquis et au soin apporté aux exercices. Les pauses, les reprises, la consultation des sources externes et les révisions suivantes s’ajoutent selon tes besoins.
Avec les ateliers, le projet, le quiz et les cartes : environ 2 h 45 à 4 h 45, à répartir sur plusieurs séances.
Objectifs du cours
Ce que tu vas savoir faire
- Tracer une séquence, une condition et une boucle en distinguant valeur lue et valeur affectée.
- Définir une fonction par ses entrées, son résultat, ses préconditions, ses postconditions et ses effets.
- Expliquer la portée d'un nom et prévenir les mutations involontaires d'un argument.
- Construire des tests par classes d'équivalence, frontières et propriétés.
- Diagnostiquer une erreur de syntaxe, d'exécution ou de logique par une démarche reproductible.
- Lire une interface de bibliothèque et comparer les traits communs et particuliers de Python et JavaScript.
Étape du cours · 8 à 15 min
Séquence, condition, boucle : suivre l'état au lieu de deviner
Une affectation commence par évaluer l’expression à droite, puis lie le nom à gauche au résultat. Dans x = x + 2, l’ancienne valeur est donc lue avant d’être remplacée : ce n’est pas une égalité mathématique. Après x = 3, puis x = x + 2, puis x = x * 4, les états successifs sont 3, 5 et 20. Inverser deux instructions peut changer le résultat. Pour vérifier une prédiction, note l’état après chaque instruction, pas seulement la valeur finale.
Une condition choisit un chemin. Dans un bloc if / elif / else, la première condition vraie sélectionne sa branche ; les suivantes ne sont pas évaluées. Sans else, aucune branche ne s’exécute si toutes les conditions sont fausses. Deux if séparés sont différents : chacun peut être exécuté. L’indentation délimite les blocs Python. Une boucle for parcourt un objet itérable, par exemple une liste ou un range, tandis que while reteste une condition avant chaque tour.
Le laboratoire fait avancer une valeur par un pas positif jusqu’à atteindre ou dépasser un seuil. Son contrat accepte des entiers, hors booléens : départ et seuil entre -1000 et 1000, pas entre 1 et 1000. Il produit la valeur finale et une liste de tuples (tour, avant, condition, après). Si le départ atteint déjà le seuil, la trace est vide. Le dernier test faux arrête la boucle mais n’ajoute aucun tour à la trace.
Pour raisonner sur l’arrêt, considère max(0, seuil - valeur). Tant qu’un tour reste nécessaire, cet entier positif diminue strictement grâce au pas positif. Le processus finit donc par atteindre ou dépasser le seuil. L’atelier et le code limitent en plus la trace à 200 tours pour garder l’essai lisible : un calcul qui demanderait davantage est signalé, même s’il terminerait mathématiquement. Cette limite d’outil n’est pas la preuve de terminaison.
Voir pour comprendre
Un test, une mise à jour, puis un nouveau test
Suis la valeur du compteur. Le dernier test arrête la boucle : il ne déclenche pas un tour supplémentaire.
while valeur < 10 : valeur += 3
- 1Test 1 : vraiAjouter 3 puis retester
- 4Test 2 : vraiAjouter 3 puis retester
- 7Test 3 : vraiAjouter 3 puis retester
- 10Test 4 : fauxSortir, sans ajouter
3 tours · 4 tests · valeur finale 10
Lis le schéma. Cache la dernière case et retrouve la valeur d’arrêt. Pourquoi le test est-il évalué une fois de plus que le corps de la boucle ?
Le test lit l’état avant le tour ; l’affectation produit l’état suivant.
Exemples résolus et erreurs expliquées
Dépasser le seuil n’ajoute pas un tour
- Départ : valeur=1. Le test 1<10 est vrai ; le premier tour ajoute 4 et produit 5. Note avant et après dans deux colonnes distinctes.
- Deuxième test : 5<10 est vrai. Le deuxième tour produit 9 ; aucun arrêt n’est décidé au seul motif que l’on se rapproche du seuil.
- Troisième test : 9<10 est vrai. Le troisième tour produit 13. Le contrat demande d’atteindre ou dépasser 10, pas de tomber exactement sur 10.
- Quatrième test : 13<10 est faux. La fonction renvoie 13 et une trace de trois tours. Le dernier test n’est pas une quatrième mise à jour.
Conclusion. Un dépassement change la valeur finale, pas la règle de comptage des tours.
Laboratoire de code
Languepython
Butproduire une trace d'état complète pour une boucle bornée par un seuil
Code solution
def atteindre_seuil(depart, pas, seuil):
if any(type(v) is not int for v in (depart, pas, seuil)):
raise ValueError('trois entiers attendus, hors booléens')
if not (-1000 <= depart <= 1000 and -1000 <= seuil <= 1000 and 1 <= pas <= 1000):
raise ValueError('paramètre hors domaine')
valeur = depart
trace = []
tour = 0
while valeur < seuil:
if tour == 200:
raise RuntimeError('trace limitée à 200 tours')
avant = valeur
valeur = valeur + pas
tour += 1
trace.append((tour, avant, avant < seuil, valeur))
return valeur, traceTests
assert atteindre_seuil(1, 3, 10)[0] == 10
assert atteindre_seuil(1, 4, 10)[0] == 13
assert atteindre_seuil(10, 2, 10) == (10, [])
assert atteindre_seuil(-2, 2, 3)[1] == [(1, -2, True, 0), (2, 0, True, 2), (3, 2, True, 4)]Trace
- tour 1 : avant=1, la condition 1<10 est vraie, après=4
- tour 2 : avant=4, la condition est vraie, après=7
- tour 3 : avant=7, après=10 ; la condition suivante devient fausse et la fonction rend 10
Clinique de bogue
Indice observénombre_tours(1, 3, 10) renvoie 4 alors que trois mises à jour suffisent pour atteindre 10
CauseLa condition autorise un tour supplémentaire lorsque la valeur est exactement égale au seuil. Le contrat demandait d'atteindre ou dépasser le seuil, donc la boucle doit continuer seulement tant que la valeur lui est strictement inférieure.
Étape du cours · 7 à 14 min
Fonctions : annoncer le contrat avant d'écrire le corps
Définir une fonction avec def lui donne un nom et des paramètres ; cela ne lance pas encore son corps. L’appel fournit des arguments. Dans note_sur_vingt(15, 30), 15 correspond à note et 30 à maximum. return renvoie une valeur et termine cet appel ; print affiche du texte sans remplacer un résultat renvoyé. Une fonction qui atteint sa fin sans return explicite renvoie None. Une chaîne placée au début du corps forme sa documentation, consultable avec help.
Le contrat décrit le comportement attendu avant de choisir l’implémentation. Ici, les deux arguments sont des nombres Python int ou float représentables par un flottant fini, hors booléens ; le maximum est strictement positif et la note appartient à [0, maximum]. Après un appel valide, le résultat est un flottant entre 0 et 20, avec les extrémités conservées. Les arguments ne sont pas modifiés. La formule utilise 20 * (note / maximum) : le rapport est calculé d’abord pour éviter qu’une multiplication intermédiaire de très grands flottants déborde.
La précondition appartient à la responsabilité de l’appelant ; le code peut néanmoins la contrôler pour signaler une erreur claire. Notre choix est une ValueError pour une entrée hors contrat. Une note supérieure au maximum n’est donc pas silencieusement corrigée. Une postcondition ne remplace pas la formule : renvoyer toujours 0 resterait entre 0 et 20, mais ne convertirait pas les notes. Les tests doivent aussi vérifier les résultats attendus et des propriétés comme la croissance.
assert sert à détecter une propriété fausse pendant la mise au point. L’option Python -O peut retirer ces assertions ; une validation nécessaire au comportement doit donc rester dans un if qui lève une exception. Le laboratoire sépare les contrôles d’entrée de l’assertion de sortie. La fonction reussite compose la conversion avec un seuil optionnel, 10 par défaut, lui aussi borné entre 0 et 20. Les calculs flottants restent des approximations : pour un résultat non exact, compare avec une tolérance annoncée.
Exemples résolus et erreurs expliquées
Convertir une note sans corriger silencieusement l’entrée
- Vérifie le domaine avant de calculer : 18 et 30 sont des nombres admis, le maximum est positif et 18 est compris entre 0 et 30.
- Calcule le rapport 18/30=0,6, puis multiplie par 20 : le résultat vaut 12,0. Ce résultat est renvoyé, il n’est pas seulement affiché.
- Vérifie les garanties : 12 est dans [0,20], les arguments sont inchangés et reussite(18,30) vaut True au seuil par défaut de 10.
- Pour 31 sur 30, la précondition est fausse. La fonction doit lever ValueError avant le calcul ; renvoyer 20 transformerait une donnée invalide en succès apparent.
Conclusion. Le domaine accepté, la formule et le type du résultat font partie d’un même contrat.
Laboratoire de code
Languepython
Butimplémenter une fonction documentée avec préconditions, postcondition et tests de propriété
Code solution
import math
def nombre_fini(valeur):
if type(valeur) not in (int, float):
return False
try:
return math.isfinite(valeur)
except OverflowError:
return False
def note_sur_vingt(note, maximum):
"""Convertit une note valide sur 20 ; renvoie un flottant sans effet de bord."""
if not nombre_fini(note) or not nombre_fini(maximum):
raise ValueError('nombres finis attendus, hors booléens')
if maximum <= 0 or not 0 <= note <= maximum:
raise ValueError('maximum ou note hors intervalle')
resultat = 20 * (note / maximum)
assert 0 <= resultat <= 20
return resultat
def reussite(note, maximum, seuil=10):
if not nombre_fini(seuil) or not 0 <= seuil <= 20:
raise ValueError('seuil entre 0 et 20 attendu')
return note_sur_vingt(note, maximum) >= seuilTests
assert note_sur_vingt(0, 40) == 0
assert note_sur_vingt(40, 40) == 20
assert note_sur_vingt(15, 30) == 10
assert all(0 <= note_sur_vingt(n, 50) <= 20 for n in range(51))
assert reussite(12, 20)
assert not reussite(9, 20)
assert note_sur_vingt(1e308, 1e308) == 20
assert math.isclose(note_sur_vingt(1, 3), 20/3, rel_tol=1e-12)Trace
- appel note_sur_vingt(15, 30) : les deux préconditions sont vraies
- calcul du rapport 15/30=0,5, puis 20×0,5=10,0 ; aucune entrée n’est modifiée
- la postcondition 0<=10<=20 est vraie, puis la valeur est renvoyée à l'appelant
Clinique de bogue
Indice observéune note négative est acceptée, un maximum nul divise par zéro et une note trop grande est silencieusement ramenée à 20
CauseLe corps masque les violations du contrat au lieu de les distinguer. Une précondition invalide ne doit pas être transformée en résultat apparemment plausible, car l'appelant perd toute possibilité de diagnostiquer la donnée fautive.
Étape du cours · 8 à 15 min
Portée et mutation : savoir quelle valeur une fonction peut changer
Chaque appel crée des liaisons locales pour ses paramètres. Le nom utilisé par l’appelant et le nom du paramètre peuvent cependant désigner le même objet. Réaffecter le paramètre à une nouvelle liste ne change pas la liaison de l’appelant ; modifier une case de la liste reçue change l’objet partagé. La portée répond à « où ce nom est-il utilisable ? » ; l’identité répond à « est-ce le même objet ? ». Ce sont deux questions distinctes.
Dans le schéma, b = a crée une seconde liaison vers la liste, pas une copie. c = a.copy() crée une nouvelle liste extérieure avant l’ajout de 8. Ainsi a et b voient [2,5,8], tandis que c reste [2,5]. Pour une liste de listes, la copie est superficielle : les sous-listes peuvent encore être partagées. Un dessin de liaisons permet de repérer ce partage sans confondre une égalité de contenus avec une identité d’objets.
Le laboratoire borner reçoit une liste ou un tuple d’entiers, hors booléens, et deux bornes entières ordonnées. Il renvoie toujours une nouvelle liste, même lorsque toutes les valeurs sont déjà dans l’intervalle ou que l’entrée est vide. Chaque valeur trop petite est remplacée par le minimum, chaque valeur trop grande par le maximum, les autres sont conservées. C’est ici une transformation demandée, contrairement à une validation qui doit refuser les données invalides.
Pour tester la non-mutation, conserve une copie du contenu avant l’appel, puis compare l’entrée après l’appel à cette copie. Pour vérifier l’identité, compare le résultat à l’objet effectivement passé : resultat is not source. Le comparer à une autre liste ne prouverait rien. Ce calcul est sans effet de bord sur ses arguments et dépend seulement d’eux. Une fonction mutante reste possible, mais son nom et son contrat doivent annoncer la modification.
Voir pour comprendre
Deux noms peuvent désigner la même liste
Après l’ajout par b, suis les flèches : a et b accèdent au même objet ; c désigne la copie créée avant l’ajout.
a = [2, 5]
b = a
c = a.copy()
b.append(8)
Lis le schéma. Sans relire le code, désigne les noms qui retrouvent la valeur ajoutée. La flèche représente une liaison, pas une copie.
La copie est ici une nouvelle liste. Une copie superficielle d’une liste imbriquée peut encore partager des objets internes.
Exemples résolus et erreurs expliquées
Même contenu, mais pas même objet
- Prédis les trois transformations : -3 devient 0, 2 reste 2 et 12 devient 10. Le résultat attendu est une nouvelle liste [0,2,10].
- Conserve l’état initial de source avant l’appel. Après l’appel, vérifie source==[-3,2,12] : la fonction n’a pas remplacé les cases de l’objet reçu.
- Vérifie resultat is not source. Comparer resultat à une troisième liste sans rapport avec cet appel ne testerait pas l’exigence d’une nouvelle liste.
- Ajoute 8 à resultat dans ce cas de liste d’entiers. Source doit rester [-3,2,12]. Ce test observe concrètement l’indépendance des deux listes extérieures.
Conclusion. Le test de contenu et le test d’identité répondent à deux exigences différentes.
Laboratoire de code
Languepython
Buttransformer une collection sans modifier l'argument fourni par l'appelant
Code solution
def borner(valeurs, minimum, maximum):
if not isinstance(valeurs, (list, tuple)) or any(type(v) is not int for v in valeurs):
raise ValueError('liste ou tuple d’entiers attendu')
if type(minimum) is not int or type(maximum) is not int or minimum > maximum:
raise ValueError('bornes entières ordonnées attendues')
resultat = []
for valeur in valeurs:
if valeur < minimum:
resultat.append(minimum)
elif valeur > maximum:
resultat.append(maximum)
else:
resultat.append(valeur)
return resultatTests
donnees = [-3, 2, 12]
resultat = borner(donnees, 0, 10)
assert resultat == [0, 2, 10]
assert donnees == [-3, 2, 12]
assert resultat is not donnees
vide = []
assert borner(vide, 0, 10) == []
assert borner(vide, 0, 10) is not vide
assert borner([5, 5], 5, 5) == [5, 5]
assert borner((0, 10), 0, 10) == [0, 10]Trace
- valeur=-3 : elle est sous le minimum, le résultat reçoit 0
- valeur=2 : elle est dans l'intervalle, le résultat reçoit 2
- valeur=12 : elle dépasse le maximum, le résultat reçoit 10 tandis que la liste d'origine reste intacte
Clinique de bogue
Indice observéaprès l'appel, la liste possédée par l'appelant a changé alors que le contrat annonçait une nouvelle liste
CauseLe paramètre local et le nom de l'appelant désignent le même objet mutable. Modifier ses cases produit donc un effet de bord extérieur. Il faut construire un nouvel objet ou annoncer explicitement la mutation.
Étape du cours · 8 à 15 min
Jeux de tests : choisir des cas qui peuvent réellement révéler l'erreur
Un jeu de tests part de la spécification, pas de ce que le programme rend déjà. Pour cet exercice, les températures sont des entiers et les catégories sont conventionnelles : froid pour x<0, tempéré pour 0≤x<20, chaud pour x≥20. Ces étiquettes ne décrivent pas une règle scientifique universelle. Les trois intervalles n’ont ni trou ni recouvrement ; chaque entrée valide doit donc recevoir une seule catégorie.
Choisis des représentants des classes, puis examine les frontières. Autour de 20, les entiers 19, 20 et 21 contrôlent l’avant, la borne et l’après. Tester trois fois 20 apporte moins d’information. Pour des nombres réels, ±1 ne désignerait pas les voisins immédiats : il faudrait choisir une précision adaptée au problème. La fonction cas_frontieres produit ici des cas entiers distincts et triés, même si les seuils sont répétés ou voisins.
L’oracle est le moyen de connaître le résultat attendu sans recopier la condition testée. Une table écrite depuis les intervalles sert d’oracle à nos six cas. Des propriétés complètent ces exemples : une catégorie appartient au vocabulaire prévu ; son rang ne diminue pas lorsque la température augmente. Mais une fonction qui renvoie toujours « chaud » respecte ces deux propriétés. Les valeurs attendues restent donc indispensables.
Commence par un cas qui échoue pour la bonne raison, corrige le minimum nécessaire, puis rejoue l’ensemble. Le cas du bogue devient un test de non-régression. Tester de nombreuses entrées ne prouve pas à lui seul le comportement pour toutes les autres. La qualité d’un test vient aussi de sa capacité à distinguer une implémentation correcte d’une erreur plausible, par exemple < remplacé par <=.
Voir pour comprendre
Tester juste avant, au seuil et juste après
Chaque frontière produit trois cas voisins. Dans cette fonction, la borne appartient à la catégorie située à sa droite.
- Avant-1froid
- Au seuil0tempéré
- Après1tempéré
- Avant19tempéré
- Au seuil20chaud
- Après21chaud
Lis le schéma. Regarde les cases du milieu : quelle catégorie reçoit exactement chaque seuil ? Compare ensuite les deux voisines.
L’oracle vient du contrat. Il ne doit pas recopier une condition potentiellement fautive du programme.
Exemples résolus et erreurs expliquées
Trouver un test qui distingue deux programmes
- Établis les résultats depuis les intervalles du contrat : -5 est froid, 10 est tempéré, 25 est chaud. La variante fautive peut réussir ces trois tests.
- Choisis 20, qui appartient exactement à la classe chaud. La variante utilisant > au lieu de ≥ renvoie tempéré : le test révèle l’erreur.
- Complète par 19 et 21, attendus tempéré puis chaud. Les trois rôles avant, borne et après sont maintenant présents dans le jeu de tests.
- Conserve 20 comme régression après correction. Une propriété de non-décroissance seule ne suffisait pas : la variante est aussi non décroissante.
Conclusion. Un test utile doit pouvoir échouer face à une erreur plausible, pas seulement confirmer un cas facile.
Laboratoire de code
Languepython
Butconstruire un jeu de tests de frontière et vérifier une propriété d'ordre
Code solution
def categorie_temperature(celsius):
if type(celsius) is not int:
raise ValueError('température entière attendue')
if celsius < 0:
return 'froid'
if celsius < 20:
return 'tempere'
return 'chaud'
def cas_frontieres(seuils):
if not isinstance(seuils, (list, tuple)) or any(type(v) is not int for v in seuils):
raise ValueError('seuils entiers attendus')
cas = set()
for seuil in seuils:
cas.update((seuil - 1, seuil, seuil + 1))
return sorted(cas)
ORDRE = {'froid': 0, 'tempere': 1, 'chaud': 2}Tests
assert cas_frontieres([0, 20]) == [-1, 0, 1, 19, 20, 21]
assert categorie_temperature(-1) == 'froid'
assert categorie_temperature(0) == 'tempere'
assert categorie_temperature(19) == 'tempere'
assert categorie_temperature(20) == 'chaud'
assert all(ORDRE[categorie_temperature(a)] <= ORDRE[categorie_temperature(a + 1)] for a in range(-30, 50))
assert cas_frontieres([1, 0, 1]) == [-1, 0, 1, 2]
assert cas_frontieres([]) == []Trace
- le seuil 0 produit les cas -1, 0 et 1, qui couvrent avant, frontière et après
- le seuil 20 produit 19, 20 et 21 selon la même règle
- la propriété parcourt des voisins successifs et interdit une diminution de la catégorie
Clinique de bogue
Indice observé0 est classé froid et 20 tempéré, contrairement aux intervalles annoncés dans la spécification
CauseLes opérateurs incluent les bornes du mauvais côté. Des exemples centraux comme -5, 10 et 25 passent pourtant, ce qui montre pourquoi chaque seuil et ses deux voisins doivent appartenir au jeu de tests.
Étape du cours · 7 à 13 min
Mise au point : du symptôme reproductible à la première divergence
Une erreur de syntaxe empêche l’analyse d’un fragment : par exemple un if auquel il manque les deux-points. Une exception apparaît pendant un chemin exécuté, comme une division par zéro ; le programme peut avoir correctement fonctionné sur d’autres entrées. Une erreur de logique peut ne déclencher aucune exception : le résultat a le bon type, mais ne respecte pas le contrat. La catégorie de l’erreur oriente la recherche sans suffire à localiser sa cause.
Pour déboguer, conserve une entrée qui reproduit le symptôme, le résultat attendu et le résultat observé. Réduis ensuite l’entrée sans faire disparaître l’écart. Trace uniquement les variables utiles, aux mêmes instants dans le calcul attendu et dans le programme. La ligne d’une exception indique où un problème devient observable ; une valeur incorrecte a pu être produite plus tôt. Supprimer cette ligne risquerait de cacher le symptôme.
Le laboratoire moyenne accepte une liste ou un tuple non vide d’au plus 1000 entiers entre -10 000 et 10 000, hors booléens. Ce domaine borné garde la somme et son affichage faciles à suivre. Chaque tuple de trace contient (indice, valeur lue, total avant, total après). La division a lieu seulement après la boucle. Pour [2,4,6], les sommes sont 2,6,12 ; diviser 12 par 3 donne 4.
Le bogue total = valeur produit lui aussi 2 au premier tour, mais 4 au deuxième au lieu de 6. La première divergence précède donc le résultat final erroné. Deux éléments dont le premier est non nul suffisent à révéler ce défaut ; ils n’ont pas besoin d’être différents, puisque [2,2] le révèle aussi. Un singleton ne le détecte pas. Le correctif doit restaurer l’accumulation, puis conserver un cas discriminant et les tests des autres exigences.
Exemples résolus et erreurs expliquées
Séparer le symptôme final de la première divergence
- Avant la boucle, les deux traces commencent à 0. Après la lecture de 2, l’accumulation correcte et le remplacement fautif donnent tous deux 2.
- À la lecture de 4, le total correct devient 2+4=6. Le programme fautif donne 4 : la première divergence est déjà observable avant la division.
- Au dernier tour, les totaux valent 12 et 6. La division par 3 rend 4 et 2 ; elle révèle le symptôme final mais n’est pas sa cause.
- Remplace l’affectation par une addition, puis teste aussi [2,2] : attendu 2, obtenu 1 dans l’ancienne version. Deux valeurs différentes n’étaient pas nécessaires.
Conclusion. Choisis le plus petit cas qui discrimine le bogue, puis garde-le après correction.
Laboratoire de code
Languepython
Butinstrumenter un calcul par une trace minimale qui localise sa première divergence
Code solution
def moyenne_avec_trace(valeurs):
if not isinstance(valeurs, (list, tuple)) or not 1 <= len(valeurs) <= 1000:
raise ValueError('1 à 1000 valeurs attendues')
if any(type(v) is not int or not -10000 <= v <= 10000 for v in valeurs):
raise ValueError('entiers entre -10000 et 10000 attendus')
total = 0
trace = []
for indice, valeur in enumerate(valeurs):
avant = total
total += valeur
trace.append((indice, valeur, avant, total))
return total / len(valeurs), trace
def moyenne(valeurs):
return moyenne_avec_trace(valeurs)[0]Tests
assert moyenne([2, 4, 6]) == 4
assert moyenne([7]) == 7
assert moyenne([-2, 2]) == 0
assert moyenne_avec_trace([2, 4, 6])[1][-1] == (2, 6, 6, 12)
donnees = [1, 3]
assert moyenne(donnees) == 2
assert donnees == [1, 3]Trace
- indice 0 : total avant=0, valeur=2, total après=2
- indice 1 : total avant=2, valeur=4, total après=6
- indice 2 : total après=12 ; la division finale utilise len=3 et rend 4
Clinique de bogue
Indice observémoyenne([2, 4, 6]) renvoie 2 au lieu de 4, tandis qu'un singleton semble fonctionner
CauseL’affectation remplace le total au lieu d’ajouter. Sur [2,4,6], la première divergence est au deuxième tour : 4 remplace 2 au lieu de produire 6. Deux éléments avec un premier non nul suffisent ; [2,2] révèle aussi le défaut. Une liste de longueur un ne distingue pas cette erreur.
Étape du cours · 8 à 16 min
Interfaces et langages : conserver l'algorithme, vérifier la sémantique
Une bibliothèque propose des fonctions réutilisables sous une interface documentée. import math rend le module disponible sous ce nom ; math.isfinite indique si un flottant n’est ni infini ni NaN. La documentation précise le paramètre, le résultat et les limites. La fonction ne certifie ni qu’une note est dans l’intervalle voulu ni qu’un booléen est une note : ces règles appartiennent à notre contrat. Le module standard est déjà fourni avec Python, sans installation supplémentaire.
Comparer des langages consiste à conserver un problème et ses résultats attendus, puis à repérer les constructions communes. Python et JavaScript expriment tous deux affectations, conditions, boucles et fonctions, mais avec indentation ou accolades, mots-clés et interfaces différents. Les styles peuvent aussi varier : impératif pour des changements d’état successifs, fonctionnel pour composer des calculs, événementiel pour réagir à des actions. Un même langage peut servir plusieurs styles. HTML décrit la structure d’un document ; il ne joue pas le rôle de notre programme de calcul.
Le laboratoire teste un palindrome selon une règle volontairement précise : conserver uniquement les caractères ASCII A à Z, a à z et 0 à 9, puis mettre les majuscules conservées en minuscules. Les espaces, accents et autres caractères sont supprimés, sans translittération. Ainsi 'Été' devient 't', tandis que 'étéx' devient 'tx'. Le résultat vide est considéré comme palindrome. Cette règle d’exercice n’est pas une définition linguistique générale du palindrome.
Après ce filtrage, la chaîne ne contient que de l’ASCII : ses indices et sa longueur se correspondent en Python et JavaScript. Sans ce périmètre, la longueur JavaScript compte des unités UTF-16 et peut différer du nombre de points de code parcourus par for...of. On compare les caractères symétriques jusqu’à la moitié, puis on renvoie un booléen. Filtrer avant de changer la casse évite qu’un caractère non ASCII se transforme en lettre admise par notre règle.
En JavaScript, = affecte, == peut convertir les types et === compare sans cette conversion. Pour deux chaînes primitives, == et === donnent le même résultat : il serait faux de rejeter == sur cet exemple pour une différence inexistante. En revanche, '3' et 3 donnent vrai avec ==, faux avec ===. Python donne faux à '3'==3 mais vrai à True==1. Choisis donc le contrat, puis des cas qui révèlent les différences réelles, sans conclure que les langages seraient globalement équivalents.
Voir pour comprendre
Même intention, écritures et comportements à comparer
Ces correspondances portent sur nos petits programmes. La dernière ligne montre une différence de comparaison.
| Intention | Python | JavaScript |
|---|---|---|
Lier x à une valeur | x = 3 | let x = 3; |
Faire un choix | if x < 4: | if (x < 4) { … } |
Parcourir un texte | for c in texte: | for (const c of texte) { … } |
Renvoyer un résultat | return True | return true; |
Compter un texte ASCII | len(texte) | texte.length |
Comparer booléen et nombre | True == 1 donne True | true === 1 donne false |
Lis le schéma. Repère la construction commune, puis ce qui change. Pourquoi la ligne des longueurs précise-t-elle ASCII ?
La syntaxe ne suffit pas : les types, conversions et unités de longueur font partie du comportement à tester.
Exemples résolus et erreurs expliquées
Comparer des résultats, pas seulement des symboles
- Note les types avant toute opération : a contient une chaîne et b contient un nombre. Le contrat veut les considérer différents, sans conversion.
a==brenvoie true dans ce cas après conversion.a===brenvoie false parce que les types diffèrent. Les deux expressions renvoient bien des booléens.a=bfait autre chose : il affecte b à a et renvoie 3. Remplacer une comparaison par une affectation ne respecte ni l’intention ni le résultat attendu.- Refais le test avec deux chaînes '3'. Les égalités == et === donnent toutes deux true. Ce second cas explique pourquoi le premier est nécessaire pour les distinguer.
JavaScript : types différents puis identiques
const a = '3', b = 3;
console.log(a == b); // true
console.log(a === b); // false
console.log('3' == '3', '3' === '3'); // true truePython : deux comparaisons de valeurs
print('3' == 3) # False
print(True == 1) # TrueConclusion. Le contrat et un corpus discriminant permettent une comparaison honnête entre langages.
Laboratoire de code
Languejavascript
Butcomparer des caractères ASCII symétriques en JavaScript sous un contrat explicite
Code solution
function assert(condition) { if (!condition) throw new Error('assertion'); }
function normaliser(texte) {
if (typeof texte !== 'string') throw new TypeError('chaîne attendue');
let resultat = '';
const autorises = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (const caractere of texte) {
if (autorises.includes(caractere)) resultat += caractere.toLowerCase();
}
return resultat;
}
function estPalindrome(texte) {
const propre = normaliser(texte);
for (let i = 0; i < Math.floor(propre.length / 2); i += 1) {
if (propre[i] !== propre[propre.length - 1 - i]) return false;
}
return true;
}Tests
assert(estPalindrome('radar') === true);
assert(estPalindrome('Esope reste ici et se repose') === true);
assert(estPalindrome('NSI') === false);
assert(estPalindrome('') === true);
assert(normaliser('A, b ! 2') === 'ab2');
assert(normaliser('Été K🙂 A') === 'ta');
assert(estPalindrome('étéx') === false);
assert(estPalindrome('🙂') === true);Trace
- normaliser transforme Radar en radar en utilisant l'interface des chaînes
- i=0 compare r au dernier r : l'égalité stricte est vraie
- i=1 compare a à a ; la moitié est parcourue sans divergence et la fonction renvoie true
Clinique de bogue
Indice observéLa variante brute renvoie une chaîne au lieu d’un booléen à cause de return texte = inverse. Elle ne respecte pas non plus la règle de normalisation du laboratoire.
Cause= réalise une affectation dont la valeur est ensuite renvoyée. Une chaîne non vide est vraie dans une condition, même si elle ne représente pas un palindrome. Le correctif doit satisfaire le contrat complet : normaliser selon la règle ASCII, comparer les positions symétriques et rendre un booléen. Remplacer seulement = par === réparerait le type du résultat, mais pas la normalisation.
Poursuivre avec l’abonnement
Comprendre l’exécution, puis essayer par toi-même
Les explications, quatre schémas et six exemples résolus sont en accès libre.
L’abonnement ouvre les six ateliers, les diagnostics, les transferts, dix questions corrigées, douze cartes et le projet à adapter.
10 questions · 12 cartes. La reprise des ateliers, du quiz et des cartes est enregistrée dans ce navigateur.
Voir les formules Déjà abonné ? Se connecterVérifier et prolonger
Sources du cours
Édition Maxdecours · Vérifié le .
Spécialité NSI, Première générale · programme du BO spécial du 22 janvier 2019
- Python : listes, alias et copies superficiellesPython Software Foundation · consulté le 2026-09-06
- Programme de l'enseignement de spécialité NSI de premièreMinistère de l'Éducation nationale · consulté le 2026-09-06
- Diversité et unité des langages de programmationÉduscol · consulté le 2026-09-06
- Mise au point de programmes testésÉduscol · consulté le 2026-09-06
- Errors and ExceptionsPython Software Foundation · consulté le 2026-09-06
- assert statementPython Software Foundation · consulté le 2026-09-06
- Strict equalityMozilla Developer Network · consulté le 2026-09-06
- Python : fonctions, paramètres et noms locauxPython Software Foundation · consulté le 2026-09-06
- Python : finitude et comparaison approchéePython Software Foundation · consulté le 2026-09-06
- Python : chaînes et types intégrésPython Software Foundation · consulté le 2026-09-06
- JavaScript : longueur d’une chaîneMozilla Developer Network · consulté le 2026-09-06
- JavaScript : parcourir les points de codeMozilla Developer Network · consulté le 2026-09-06
- Langages de haut niveau en 1957Computer History Museum · consulté le 2026-09-06
© 2026 Maxdecours.com · Comprendre et progresser