{
  "chapter": {
    "id": "debogage-terminale",
    "level": "terminale",
    "theme": "Programmation",
    "title": "Mise au point et débogage",
    "description": "Mise au point d'un programme : tests d'intégration,\nprofilage (en temps et en mémoire), gestion des\nexceptions avec `try` et `except`, analyse des\njournaux d'exécution, débogueur interactif et\nméthodes systématiques de réduction au cas\nminimal et d'explication à voix haute.",
    "parties": [
      {
        "id": "methode",
        "title": "Méthode de mise au point"
      },
      {
        "id": "erreurs",
        "title": "Erreurs et exceptions"
      },
      {
        "id": "pieges",
        "title": "Pièges classiques"
      }
    ],
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "exception"
      ],
      "title": "Notion d'exception",
      "partie": "erreurs",
      "statement": "Qu'est-ce qu'une **exception** en Python ?",
      "options": [
        {
          "text": "Un type de variable particulier",
          "correct": false,
          "feedback": "Une exception est un événement de\nl'exécution, pas un type de donnée que\nl'on déclare comme on déclarerait un\nentier ou une chaîne.\n"
        },
        {
          "text": "Un simple avertissement qui n'interrompt pas du tout le programme",
          "correct": false,
          "feedback": "Cette description correspond à un\n*warning*. Une exception non capturée,\nau contraire, interrompt l'exécution du\nprogramme.\n"
        },
        {
          "text": "Une erreur signalée à l'exécution, qui interrompt le programme",
          "correct": true,
          "feedback": "Quelques exemples classiques :\n`ValueError`, `TypeError`, `IndexError`,\n`KeyError`. C'est le mécanisme standard\npour signaler une condition inattendue\npendant l'exécution.\n"
        },
        {
          "text": "Un commentaire spécial inséré dans le code",
          "correct": false,
          "feedback": "Une exception n'a aucun rapport avec un\ncommentaire. Les commentaires en Python\ncommencent par le caractère `#` et n'ont\naucun effet sur l'exécution.\n"
        }
      ],
      "explanation": "Les exceptions remontent la pile des appels\nde fonctions jusqu'à un bloc `try / except`\nqui les capture. En l'absence d'un tel\nbloc, le programme s'arrête en affichant\nla trace de l'erreur."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "try-except"
      ],
      "title": "Le bloc try / except",
      "partie": "erreurs",
      "statement": "Comment **gérer** une exception sans que le\nprogramme ne s'interrompe ?",
      "options": [
        {
          "text": "Aucun moyen ne permet d'éviter l'arrêt du programme",
          "correct": false,
          "feedback": "Python fournit précisément le bloc\n`try / except` pour intercepter une\nexception et poursuivre l'exécution.\n"
        },
        {
          "text": "```python\ntry:\n    risque()\nexcept Exception as e:\n    print(\"Erreur :\", e)\n```\n",
          "correct": true,
          "feedback": "Le bloc `try` tente l'exécution. En\ncas d'exception, le bloc `except`\nprend le relais et permet de gérer\nproprement l'erreur.\n"
        },
        {
          "text": "```python\ntry {\n    risque()\n} catch (Exception e) {\n    print(\"Erreur :\", e)\n}\n```\n",
          "correct": false,
          "feedback": "Cette syntaxe à accolades, avec\n`catch`, est celle de Java ou de C++,\npas celle de Python, qui emploie `try`\net `except` suivis de deux-points et\nd'un bloc indenté.\n"
        },
        {
          "text": "```python\nresultat = risque()\nif resultat == error:\n    print(\"Erreur :\", resultat)\n```\n",
          "correct": false,
          "feedback": "Ce code ne gère rien : si `risque()`\nlève une exception, le programme\ns'arrête avant même d'atteindre le\n`if`. La gestion des erreurs ne\nrepose pas sur une comparaison à une\nvaleur spéciale, mais sur le mécanisme\ndes exceptions.\n"
        }
      ],
      "explanation": "Variante utile : `except ValueError:` pour ne capturer qu'un\ntype d'exception précis. Bonne pratique : capturer un type\nd'exception précis plutôt qu'utiliser un `except` nu, qui\nmasquerait aussi les erreurs imprévues."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "documentation",
        "specification"
      ],
      "title": "Chaîne de documentation d'une fonction",
      "partie": "methode",
      "statement": "On considère la fonction suivante :\n\n```python\ndef moyenne(notes):\n    \"\"\"Renvoie la moyenne des nombres de la liste notes, supposée non vide.\"\"\"\n    total = 0\n    for note in notes:\n        total = total + note\n    return total / len(notes)\n```\n\nÀ quoi sert la chaîne écrite entre triples guillemets sous la\nligne `def` ?",
      "options": [
        {
          "text": "À vérifier, à chaque appel, que la liste n'est pas vide",
          "correct": false,
          "feedback": "Erreur : Python ne vérifie jamais le contenu de cette chaîne.\nPour contrôler la précondition à l'exécution, il faudrait une\nassertion comme `assert len(notes) > 0`.\n"
        },
        {
          "text": "À décrire le rôle de la fonction et ce qu'elle suppose",
          "correct": true,
          "feedback": "Bonne réponse : cette chaîne documente la fonction, son résultat\net sa précondition (liste non vide). On la lit dans le code, ou\navec `help(moyenne)`.\n"
        },
        {
          "text": "À afficher ce texte dans la console à chaque appel",
          "correct": false,
          "feedback": "Erreur : rien n'est affiché lors d'un appel. `moyenne([12, 15])`\nrenvoie simplement `13.5` ; seul un `print` produit un\naffichage.\n"
        },
        {
          "text": "À fixer la valeur renvoyée quand la liste est vide",
          "correct": false,
          "feedback": "Erreur : la chaîne n'a aucun effet sur le calcul. Avec une liste\nvide, la division par `len(notes)` lève une `ZeroDivisionError`,\nque la chaîne soit présente ou non.\n"
        }
      ],
      "explanation": "Documenter une fonction, c'est écrire ce qu'elle fait, ce qu'elle\nattend de ses paramètres et ce qu'elle renvoie. Cette\ndocumentation informe le programmeur qui l'utilise, mais ne\nprotège de rien : pour faire respecter une précondition, on\najoute une assertion."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "jeu-de-tests",
        "correction"
      ],
      "title": "Ce que prouve un jeu de tests réussi",
      "partie": "methode",
      "statement": "Une fonction écrite par Léa passe avec succès les cinq tests\nqu'elle a préparés. Que peut-on en conclure ?",
      "options": [
        {
          "text": "Elle donne le bon résultat pour toutes les entrées possibles",
          "correct": false,
          "feedback": "Erreur : cinq tests ne vérifient que cinq cas. Un bug peut se\ncacher dans une entrée non testée, par exemple une liste vide ou\nune valeur placée en dernière position.\n"
        },
        {
          "text": "Écrire d'autres tests pour cette fonction ne servirait à rien",
          "correct": false,
          "feedback": "Erreur : de nouveaux tests, en particulier sur les cas limites,\npeuvent encore révéler un bug. La qualité et le nombre des tests\ncomptent autant que leur succès.\n"
        },
        {
          "text": "Elle est juste sur ces cinq cas, sans garantie pour les autres",
          "correct": true,
          "feedback": "Bonne réponse : un test réussi montre que la fonction se comporte\ncomme prévu sur ce cas précis. Le succès d'un jeu de tests ne\ngarantit pas la correction du programme.\n"
        },
        {
          "text": "Elle est sans erreur de syntaxe, sans rien dire de ses résultats",
          "correct": false,
          "feedback": "Erreur : un test compare le résultat obtenu au résultat attendu.\nSon succès dit donc bien quelque chose des résultats, sur les cas\nqu'il examine.\n"
        }
      ],
      "explanation": "Un jeu de tests augmente la confiance dans un programme sans la\nrendre totale. On le choisit avec soin (cas ordinaires, cas\nlimites, valeurs aux bornes) et on le complète dès qu'un bug est\ndécouvert."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "jeu-de-tests",
        "cas-limites"
      ],
      "title": "Choisir les cas d'un jeu de tests",
      "partie": "methode",
      "statement": "On veut tester une fonction `maximum(t)` qui renvoie le plus grand\nélément d'une liste non vide de nombres. Quel choix de listes\nforme le jeu de tests le plus pertinent ?",
      "options": [
        {
          "text": "Une seule liste de mille nombres tirés au hasard",
          "correct": false,
          "feedback": "Erreur : une seule liste, même longue, ne teste qu'une position\ndu maximum. Une fonction qui oublierait le premier ou le dernier\nélément pourrait passer ce test sans être détectée.\n"
        },
        {
          "text": "Plusieurs listes dont le maximum est toujours le dernier élément",
          "correct": false,
          "feedback": "Erreur : ces listes testent toutes le même cas. Une fonction qui\nrenverrait simplement le dernier élément les passerait toutes,\nsans être correcte.\n"
        },
        {
          "text": "Maximum en tête, au milieu, en fin, et liste à un élément",
          "correct": true,
          "feedback": "Bonne réponse : ces cas couvrent chaque position possible du\nmaximum et le plus petit cas autorisé. Une boucle qui commence ou\ns'arrête un indice trop tôt échoue sur au moins l'un d'eux.\n"
        },
        {
          "text": "Des listes dont tous les éléments sont égaux entre eux",
          "correct": false,
          "feedback": "Erreur : quand tous les éléments sont égaux, n'importe quel\nélément de la liste est une réponse juste. Ces tests ne peuvent\ndonc révéler presque aucun bug.\n"
        }
      ],
      "explanation": "Un bon jeu de tests vise les situations où un bug a le plus de\nchances d'apparaître : les cas limites (liste à un élément) et les\nvaleurs placées aux bornes (premier et dernier élément). Mieux\nvaut quelques cas bien choisis que beaucoup de cas semblables."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "stack-trace"
      ],
      "title": "Trace d'erreur",
      "partie": "erreurs",
      "statement": "Que représente la **trace d'erreur**\naffichée par Python à la suite d'une\nexception non capturée ?",
      "options": [
        {
          "text": "Un fichier journal stocké sur le disque",
          "correct": false,
          "feedback": "Une trace d'erreur n'est pas\nstockée par défaut sur le disque ;\nelle est simplement affichée dans\nla sortie d'erreur du programme.\n"
        },
        {
          "text": "Le code source complet du programme",
          "correct": false,
          "feedback": "La trace ne contient pas tout le\ncode source, mais uniquement la\nséquence d'appels qui a mené à\nl'erreur, avec les numéros de\nlignes correspondants.\n"
        },
        {
          "text": "La suite des appels de fonctions menant à l'erreur",
          "correct": true,
          "feedback": "Cette trace se lit du bas (lieu où\nl'erreur s'est produite) vers le\nhaut (point d'entrée du programme).\nElle aide à comprendre le contexte\ndans lequel l'erreur est apparue.\n"
        },
        {
          "text": "Un type de mémoire interne au système",
          "correct": false,
          "feedback": "Cette description évoque la pile\nd'appels en mémoire, qui est une\nnotion proche mais distincte. Ici,\non parle de l'affichage textuel\nobtenu à la sortie du programme.\n"
        }
      ],
      "explanation": "Pour lire une trace d'erreur, on\ncommence par la dernière ligne, qui\nindique le type d'erreur, puis on\nremonte pour localiser l'origine de\nl'appel problématique. On ignore\ngénéralement les blocs internes aux\nbibliothèques pour se concentrer sur\nson propre code."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "conditionnelle-non-exhaustive",
        "none"
      ],
      "title": "Une conditionnelle incomplète",
      "partie": "pieges",
      "statement": "On considère la fonction suivante :\n\n```python\ndef mention(note):\n    if note >= 16:\n        return \"très bien\"\n    elif note >= 14:\n        return \"bien\"\n    elif note >= 12:\n        return \"assez bien\"\n    elif note >= 10:\n        return \"passable\"\n```\n\nQu'affiche l'instruction `print(mention(8))` ?",
      "options": [
        {
          "text": "`passable`, le texte de la dernière branche",
          "correct": false,
          "feedback": "Erreur : la branche `elif note >= 10` n'est exécutée que si sa\ncondition est vraie. Pour `8`, les quatre conditions sont fausses\net aucun `return` n'est atteint.\n"
        },
        {
          "text": "`None`",
          "correct": true,
          "feedback": "Bonne réponse : aucune condition n'est vraie pour `8`, la fonction\natteint sa fin sans `return` et renvoie donc `None`, que `print`\naffiche. Il manque un cas final, par exemple\n`else: return \"insuffisant\"`.\n"
        },
        {
          "text": "Une erreur, car aucune branche ne correspond à `8`",
          "correct": false,
          "feedback": "Erreur : Python n'exige pas qu'une branche soit choisie. Quand\ntoutes les conditions sont fausses, il passe simplement à la\nsuite, ici la fin de la fonction.\n"
        },
        {
          "text": "Une ligne vide, puisque la fonction ne renvoie rien",
          "correct": false,
          "feedback": "Erreur : une fonction qui se termine sans `return` renvoie la\nvaleur `None`, et `print(None)` affiche bien le mot `None`, pas\nune ligne vide.\n"
        }
      ],
      "explanation": "Une conditionnelle doit prévoir tous les cas possibles. Sinon, une\nfonction peut renvoyer `None` sans aucun message, et l'erreur\napparaît plus loin, quand on utilise ce résultat. Terminer par un\n`else` rend la conditionnelle exhaustive."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "reproduction"
      ],
      "title": "Reproduction d'un bug",
      "partie": "methode",
      "statement": "Pourquoi est-il **essentiel** de pouvoir\nreproduire un bug avant de le corriger ?",
      "options": [
        {
          "text": "Pour pouvoir le montrer à l'utilisateur final",
          "correct": false,
          "feedback": "La présentation du bug à\nl'utilisateur n'est pas la raison\npour laquelle on cherche à le\nreproduire. L'objectif est de\npouvoir vérifier qu'il a été\ncorrigé.\n"
        },
        {
          "text": "Sans reproduction, impossible de vérifier la correction",
          "correct": true,
          "feedback": "Un bug intermittent est très\ndifficile à corriger. Trouver une\nprocédure fiable de reproduction\nreprésente déjà la moitié du\ntravail de correction.\n"
        },
        {
          "text": "Pour s'amuser à le voir se produire",
          "correct": false,
          "feedback": "La reproduction d'un bug est un\ntravail technique sérieux, motivé\npar la nécessité de pouvoir\ncontrôler la correction.\n"
        },
        {
          "text": "Aucune raison particulière",
          "correct": false,
          "feedback": "La reproduction est au contraire\nl'étape fondatrice de tout\nprocessus de correction sérieux.\n"
        }
      ],
      "explanation": "L'adage classique en débogage est :\n« si l'on ne peut pas reproduire le\nbug, on ne peut pas le corriger ».\nUne fois la reproduction obtenue, on\nécrit un test qui échoue dans cette\nsituation, puis on corrige jusqu'à ce\nque ce test passe avec succès."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "methode-systematique"
      ],
      "title": "Réduction au cas minimal",
      "partie": "methode",
      "statement": "Quelle est l'une des techniques\nsystématiques pour **isoler la cause**\nd'un bug dans un programme ?",
      "options": [
        {
          "text": "Réduire l'entrée au cas le plus simple qui reproduit encore le bug",
          "correct": true,
          "feedback": "Cette technique du « cas de test\nminimal » est très efficace : si\nun programme plante sur une liste\nde mille éléments, on essaie avec\ndix, puis cinq, puis deux, en\ngardant à chaque fois la\npropriété qui déclenche le bug.\nCela localise la cause et fournit\nun petit test à ajouter au jeu de\ntests.\n"
        },
        {
          "text": "Multiplier la taille de l'entrée jusqu'à observer un comportement régulier",
          "correct": false,
          "feedback": "C'est l'inverse. Plus l'entrée est\ngrande, plus il est difficile\nd'identifier précisément ce qui\ndéclenche le bug.\n"
        },
        {
          "text": "Demander conseil à un collègue",
          "correct": false,
          "feedback": "Cela peut aider à clarifier ses\nidées (technique dite « du canard\nen plastique »), mais n'est pas\nune méthode systématique\nd'isolation de la cause.\n"
        },
        {
          "text": "Tout réécrire de zéro",
          "correct": false,
          "feedback": "Cette démarche est risquée : elle\npeut introduire de nouveaux bugs\net masquer l'origine du problème\ninitial.\n"
        }
      ],
      "explanation": "La réduction au cas minimal localise\nla cause d'un bug : on simplifie\nl'entrée pas à pas en conservant le\ncomportement fautif. Le petit cas\nobtenu est facile à suivre à la main,\npuis il rejoint le jeu de tests."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "assertion",
        "precondition"
      ],
      "title": "Signaler un argument invalide",
      "partie": "methode",
      "statement": "Dans une fonction `racine_carree(x)`, on\nsouhaite **signaler une erreur** lorsque\n`x` est négatif. Quelle est la bonne\npratique ?",
      "options": [
        {
          "text": "Vérifier la précondition dès l'entrée avec `assert x >= 0`",
          "correct": true,
          "feedback": "Bonne réponse, par exemple :\n\n```python\ndef racine_carree(x):\n    assert x >= 0, \"x doit être positif\"\n    ...\n```\n\nSi la condition est fausse,\nl'assertion interrompt aussitôt\nl'exécution sur une `AssertionError`\nqui porte le message : l'appel\nfautif est désigné clairement.\n"
        },
        {
          "text": "Renvoyer la valeur $-1$ pour signaler l'erreur",
          "correct": false,
          "feedback": "Cette pratique est dangereuse :\nl'appelant peut oublier de tester\nla valeur de retour, et la valeur\n$-1$ peut aussi être un résultat\nlégitime pour une autre fonction.\n"
        },
        {
          "text": "Afficher un message avec `print` puis renvoyer la valeur $0$",
          "correct": false,
          "feedback": "L'appel à `print` ne peut pas être\ncapturé par l'appelant, et la\nvaleur $0$ ne se distingue pas\nd'un résultat valide. Cette\napproche dissimule donc le\nproblème.\n"
        },
        {
          "text": "Continuer le calcul comme si de rien n'était",
          "correct": false,
          "feedback": "Cette pratique repousse le problème :\nle calcul échoue plus loin, sur un\nmessage qui ne désigne pas l'appel\nfautif, ou produit une valeur\naberrante, ce qui rend le bug très\ndifficile à diagnostiquer ensuite.\n"
        }
      ],
      "explanation": "Une assertion placée en tête de\nfonction exprime et vérifie sa\nprécondition. Contrairement à une\nvaleur de retour spéciale, elle ne\npeut pas être ignorée par l'appelant :\nle programme s'arrête sur un message\nexplicite."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "typage",
        "type-error"
      ],
      "title": "Une saisie jamais convertie",
      "partie": "erreurs",
      "statement": "On exécute le programme suivant et l'on saisit `2009` au clavier :\n\n```python\nannee = input(\"Année de naissance : \")\nage = 2026 - annee\nprint(\"Tu as\", age, \"ans\")\n```\n\nQue se passe-t-il ?",
      "options": [
        {
          "text": "Il affiche `Tu as 17 ans`",
          "correct": false,
          "feedback": "Erreur : `input` renvoie toujours une chaîne de caractères, ici\n`\"2009\"`. Python ne la convertit pas en nombre de lui-même, et la\nsoustraction échoue.\n"
        },
        {
          "text": "Une `ValueError`, car la saisie `2009` ne décrit pas un entier",
          "correct": false,
          "feedback": "Erreur : `\"2009\"` décrit bien un entier, et `int(\"2009\")`\nréussirait. Aucune conversion n'est tentée ici : l'erreur vient\nde l'opération entre deux types incompatibles.\n"
        },
        {
          "text": "Une `TypeError`, car `input` renvoie une chaîne non convertie",
          "correct": true,
          "feedback": "Bonne réponse : `2026 - \"2009\"` soustrait une chaîne d'un\nentier. Python s'arrête sur\n`TypeError: unsupported operand type(s) for -: 'int' and 'str'`.\nIl faut écrire `annee = int(input(\"Année de naissance : \"))`.\n"
        },
        {
          "text": "Une `TypeError`, car `print` refuse de mêler texte et nombres",
          "correct": false,
          "feedback": "Erreur : `print` accepte des valeurs de types différents séparées\npar des virgules. Le programme n'atteint d'ailleurs jamais cette\nligne : il s'arrête dès la soustraction.\n"
        }
      ],
      "explanation": "Le type d'une valeur décide des opérations permises. Une saisie au\nclavier est une chaîne : pour calculer avec, on la convertit avec\n`int` ou `float`. Le message d'une `TypeError` nomme les deux types\nen conflit, ce qui oriente directement vers la conversion\noubliée."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "piege-except-nu"
      ],
      "title": "Le bloc except sans type",
      "partie": "erreurs",
      "statement": "Pourquoi le bloc `except:` sans\nprécision de type est-il **déconseillé** ?",
      "options": [
        {
          "text": "Il capture toutes les exceptions, même les interruptions clavier",
          "correct": true,
          "feedback": "Capturer indistinctement toutes\nles exceptions est une mauvaise\npratique. Mieux vaut écrire\n`except Exception:` (qui\nn'attrape pas les interruptions\nclavier) ou, idéalement, capturer\nun type d'exception spécifique.\n"
        },
        {
          "text": "Parce qu'il est plus lent à l'exécution",
          "correct": false,
          "feedback": "La performance n'est pas le\ncritère ici. Le problème est\nd'ordre fonctionnel : un\n`except` nu intercepte trop de\nchoses.\n"
        },
        {
          "text": "Aucune raison ne justifie de l'éviter",
          "correct": false,
          "feedback": "Au contraire, plusieurs raisons\njustifient d'éviter le `except`\nnu, comme expliqué dans la bonne\nréponse.\n"
        },
        {
          "text": "Parce que la syntaxe `except:` n'existe tout simplement pas en Python",
          "correct": false,
          "feedback": "La syntaxe existe et est\nsyntaxiquement valide. Le\nproblème est sémantique : elle\ncapture trop largement.\n"
        }
      ],
      "explanation": "Règle d'or : ne jamais ignorer\nsilencieusement une erreur. Au\nminimum, il faut journaliser ce qui\ns'est passé, même si l'on choisit\nde poursuivre l'exécution."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "effet-de-bord",
        "liste",
        "parametre"
      ],
      "title": "Une liste modifiée par une fonction",
      "partie": "pieges",
      "statement": "On considère le programme suivant :\n```python\ndef ajouter_bonus(notes):\n    for i in range(len(notes)):\n        notes[i] = notes[i] + 1\n    return notes\nnotes_classe = [12, 15, 9]\nbonus = ajouter_bonus(notes_classe)\nprint(notes_classe)\n```\nQu'affiche la dernière instruction ?",
      "options": [
        {
          "text": "`[12, 15, 9]`",
          "correct": false,
          "feedback": "Erreur : la fonction ne reçoit pas une copie de la liste mais la\nliste elle-même. Chaque affectation `notes[i] = ...` modifie donc\n`notes_classe`.\n"
        },
        {
          "text": "`[13, 16, 10]`",
          "correct": true,
          "feedback": "Bonne réponse : `notes` et `notes_classe` désignent la même\nliste. La fonction la modifie en place, et `bonus` désigne lui\naussi cette même liste. Les notes d'origine sont perdues : c'est\nun effet de bord.\n"
        },
        {
          "text": "`[14, 17, 11]`",
          "correct": false,
          "feedback": "Erreur : le bonus n'est ajouté qu'une fois. L'affectation\n`bonus = ajouter_bonus(notes_classe)` ne relance aucun calcul :\nelle nomme la liste renvoyée, qui est la liste déjà modifiée.\n"
        },
        {
          "text": "Une erreur, une fonction ne pouvant modifier une liste reçue",
          "correct": false,
          "feedback": "Erreur : rien n'interdit à une fonction de modifier les éléments\nd'une liste passée en paramètre. Le programme s'exécute sans\nerreur, ce qui rend l'effet de bord d'autant plus discret.\n"
        }
      ],
      "explanation": "Une liste passée en paramètre n'est pas copiée : la modifier dans\nla fonction la modifie aussi pour l'appelant. Si ce n'est pas\nvoulu, la fonction construit une nouvelle liste, par exemple\n`[note + 1 for note in notes]`, et la renvoie.",
      "variants": [
        {
          "statement": "On considère le programme suivant :\n```python\ndef ajouter_bonus(notes):\n    for i in range(len(notes)):\n        notes[i] = notes[i] + 1\n    return notes\nnotes_classe = [12, 15, 9]\nbonus = ajouter_bonus(notes_classe)\nprint(notes_classe)\n```\nQu'affiche la dernière instruction ?",
          "options": [
            {
              "text": "`[12, 15, 9]`",
              "correct": false,
              "feedback": "Erreur : la fonction ne reçoit pas une copie de la liste mais la\nliste elle-même. Chaque affectation `notes[i] = ...` modifie donc\n`notes_classe`.\n"
            },
            {
              "text": "`[13, 16, 10]`",
              "correct": true,
              "feedback": "Bonne réponse : `notes` et `notes_classe` désignent la même\nliste. La fonction la modifie en place, et `bonus` désigne lui\naussi cette même liste. Les notes d'origine sont perdues : c'est\nun effet de bord.\n"
            },
            {
              "text": "`[14, 17, 11]`",
              "correct": false,
              "feedback": "Erreur : le bonus n'est ajouté qu'une fois. L'affectation\n`bonus = ajouter_bonus(notes_classe)` ne relance aucun calcul :\nelle nomme la liste renvoyée, qui est la liste déjà modifiée.\n"
            },
            {
              "text": "Une erreur, une fonction ne pouvant modifier une liste reçue",
              "correct": false,
              "feedback": "Erreur : rien n'interdit à une fonction de modifier les éléments\nd'une liste passée en paramètre. Le programme s'exécute sans\nerreur, ce qui rend l'effet de bord d'autant plus discret.\n"
            }
          ],
          "explanation": "Une liste passée en paramètre n'est pas copiée : la modifier dans\nla fonction la modifie aussi pour l'appelant. Si ce n'est pas\nvoulu, la fonction construit une nouvelle liste, par exemple\n`[note + 1 for note in notes]`, et la renvoie."
        },
        {
          "statement": "On considère le programme suivant :\n```python\ndef ajouter_bonus(notes):\n    for i in range(len(notes)):\n        notes[i] = notes[i] + 1\n    return notes\nnotes_classe = [8, 14, 17]\nbonus = ajouter_bonus(notes_classe)\nprint(notes_classe)\n```\nQu'affiche la dernière instruction ?",
          "options": [
            {
              "text": "`[8, 14, 17]`",
              "correct": false,
              "feedback": "Erreur : la fonction ne reçoit pas une copie de la liste mais la\nliste elle-même. Chaque affectation `notes[i] = ...` modifie donc\n`notes_classe`.\n"
            },
            {
              "text": "`[9, 15, 18]`",
              "correct": true,
              "feedback": "Bonne réponse : `notes` et `notes_classe` désignent la même\nliste. La fonction la modifie en place, et `bonus` désigne lui\naussi cette même liste. Les notes d'origine sont perdues : c'est\nun effet de bord.\n"
            },
            {
              "text": "`[10, 16, 19]`",
              "correct": false,
              "feedback": "Erreur : le bonus n'est ajouté qu'une fois. L'affectation\n`bonus = ajouter_bonus(notes_classe)` ne relance aucun calcul :\nelle nomme la liste renvoyée, qui est la liste déjà modifiée.\n"
            },
            {
              "text": "Une erreur, une fonction ne pouvant modifier une liste reçue",
              "correct": false,
              "feedback": "Erreur : rien n'interdit à une fonction de modifier les éléments\nd'une liste passée en paramètre. Le programme s'exécute sans\nerreur, ce qui rend l'effet de bord d'autant plus discret.\n"
            }
          ],
          "explanation": "Une liste passée en paramètre n'est pas copiée : la modifier dans\nla fonction la modifie aussi pour l'appelant. Si ce n'est pas\nvoulu, la fonction construit une nouvelle liste, par exemple\n`[note + 1 for note in notes]`, et la renvoie."
        },
        {
          "statement": "On considère le programme suivant :\n```python\ndef ajouter_bonus(notes):\n    for i in range(len(notes)):\n        notes[i] = notes[i] + 1\n    return notes\nnotes_classe = [10, 5, 19, 7]\nbonus = ajouter_bonus(notes_classe)\nprint(notes_classe)\n```\nQu'affiche la dernière instruction ?",
          "options": [
            {
              "text": "`[10, 5, 19, 7]`",
              "correct": false,
              "feedback": "Erreur : la fonction ne reçoit pas une copie de la liste mais la\nliste elle-même. Chaque affectation `notes[i] = ...` modifie donc\n`notes_classe`.\n"
            },
            {
              "text": "`[11, 6, 20, 8]`",
              "correct": true,
              "feedback": "Bonne réponse : `notes` et `notes_classe` désignent la même\nliste. La fonction la modifie en place, et `bonus` désigne lui\naussi cette même liste. Les notes d'origine sont perdues : c'est\nun effet de bord.\n"
            },
            {
              "text": "`[12, 7, 21, 9]`",
              "correct": false,
              "feedback": "Erreur : le bonus n'est ajouté qu'une fois. L'affectation\n`bonus = ajouter_bonus(notes_classe)` ne relance aucun calcul :\nelle nomme la liste renvoyée, qui est la liste déjà modifiée.\n"
            },
            {
              "text": "Une erreur, une fonction ne pouvant modifier une liste reçue",
              "correct": false,
              "feedback": "Erreur : rien n'interdit à une fonction de modifier les éléments\nd'une liste passée en paramètre. Le programme s'exécute sans\nerreur, ce qui rend l'effet de bord d'autant plus discret.\n"
            }
          ],
          "explanation": "Une liste passée en paramètre n'est pas copiée : la modifier dans\nla fonction la modifie aussi pour l'appelant. Si ce n'est pas\nvoulu, la fonction construit une nouvelle liste, par exemple\n`[note + 1 for note in notes]`, et la renvoie."
        }
      ]
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "choix-des-inegalites",
        "specification"
      ],
      "title": "Une inégalité stricte de trop",
      "partie": "pieges",
      "statement": "La fonction suivante doit renvoyer le nombre de notes supérieures\nou égales à $10$ :\n\n```python\ndef compter_admis(notes):\n    n = 0\n    for note in notes:\n        if note > 10:\n            n = n + 1\n    return n\n```\n\nOr `compter_admis([8.5, 10, 14, 9.5])` renvoie `1` au lieu de `2`.\nQuelle condition corrige la fonction pour toutes les notes,\nentières ou décimales ?",
      "options": [
        {
          "text": "`if note > 9:`",
          "correct": false,
          "feedback": "Erreur : cette condition fonctionne pour des notes entières, mais\nelle compte aussi `9.5`. L'appel donnerait `3` au lieu de `2`.\n"
        },
        {
          "text": "`if note > 10.0:`",
          "correct": false,
          "feedback": "Erreur : écrire `10.0` au lieu de `10` ne change pas la\ncomparaison. L'inégalité reste stricte, la note `10` est toujours\nécartée et l'appel renvoie encore `1`.\n"
        },
        {
          "text": "`if note == 10:`",
          "correct": false,
          "feedback": "Erreur : cette condition ne compte plus que les notes égales à\n`10` et oublie `14`. L'appel renvoie `1`, et le résultat serait\nfaux pour toute liste contenant une note au-dessus de `10`.\n"
        },
        {
          "text": "`if note >= 10:`",
          "correct": true,
          "feedback": "Bonne réponse : l'inégalité large traduit exactement « supérieure\nou égale à $10$ ». L'appel compte `10` et `14`, et renvoie `2`.\n"
        }
      ],
      "explanation": "Le passage de la spécification au code demande de choisir entre\n`<` et `<=` : c'est une source classique de bug. Un test sur la\nvaleur frontière, ici une note de `10`, suffit à le révéler."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "nommage",
        "type-error"
      ],
      "title": "Une variable qui cache une fonction",
      "partie": "methode",
      "statement": "On exécute le programme suivant :\n\n```python\nnotes = [12, 17, 9]\nmax = 0\nfor note in notes:\n    if note > max:\n        max = note\nprint(max)\nprint(max(notes))\n```\n\nIl affiche `17`, puis s'arrête sur\n`TypeError: 'int' object is not callable`. Quelle est la cause de\nce bug ?",
      "options": [
        {
          "text": "La fonction `max` de Python exige une liste déjà triée",
          "correct": false,
          "feedback": "Erreur : `max` accepte une liste dans n'importe quel ordre.\nD'ailleurs, l'erreur ne parle pas d'ordre : elle signale qu'on\ntente d'appeler un entier.\n"
        },
        {
          "text": "La variable `max` remplace la fonction `max`, devenue un entier",
          "correct": true,
          "feedback": "Bonne réponse : après `max = 0`, le nom `max` désigne un entier,\nqui vaut `17` à la fin de la boucle. `max(notes)` revient à\nécrire `17(notes)`, d'où l'erreur. Il suffit de renommer la\nvariable, par exemple `plus_grande`.\n"
        },
        {
          "text": "La boucle a remplacé la liste `notes` par un entier",
          "correct": false,
          "feedback": "Erreur : la boucle affecte `note`, sans `s`, et ne modifie jamais\n`notes`. La liste vaut toujours `[12, 17, 9]` ; c'est le nom `max`\nqui a changé de sens.\n"
        },
        {
          "text": "La fonction `max` compare deux nombres et refuse une liste",
          "correct": false,
          "feedback": "Erreur : `max` accepte aussi une liste, et `max([12, 17, 9])`\nvaut `17` dans un programme où ce nom n'a pas été réutilisé.\n"
        }
      ],
      "explanation": "Un nom de variable doit être explicite et ne pas reprendre celui\nd'une fonction existante (`max`, `min`, `sum`, `len`, `list`).\nSinon la fonction devient inaccessible, et l'erreur survient loin\nde l'affectation qui l'a causée."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "trace-execution"
      ],
      "title": "Trace d'exécution",
      "partie": "methode",
      "statement": "Que produit une **trace d'exécution** d'un\nprogramme ?",
      "options": [
        {
          "text": "Une optimisation du code",
          "correct": false,
          "feedback": "La trace est un outil\nd'observation du comportement,\net non une transformation du\ncode visant à le rendre plus\nrapide.\n"
        },
        {
          "text": "Un journal des étapes réellement exécutées",
          "correct": true,
          "feedback": "La trace d'exécution permet de\ncomprendre le déroulement réel\ndu programme, ce qui est\nparticulièrement utile pour des\nbugs complexes ou intermittents.\nOn peut l'obtenir simplement avec\ndes `print` placés aux étapes\nclés.\n"
        },
        {
          "text": "Une erreur d'exécution",
          "correct": false,
          "feedback": "La trace d'exécution n'est pas\nun message d'erreur, mais le\njournal complet du déroulement\ndu programme.\n"
        },
        {
          "text": "Une simple copie intégrale du code source du programme",
          "correct": false,
          "feedback": "La trace d'exécution n'est pas\nle code source du programme,\nmais un journal de son\ncomportement effectif.\n"
        }
      ],
      "explanation": "On obtient une trace en affichant,\npar exemple à chaque tour de boucle\nou à chaque appel de fonction, la\nvaleur des variables utiles. Le\nrésultat est verbeux mais permet de\nsuivre pas à pas un comportement\ninattendu."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "assertion"
      ],
      "title": "Assertion et exception",
      "partie": "methode",
      "statement": "Quelle est la différence entre une\n**assertion** et la gestion d'exception ?",
      "options": [
        {
          "text": "Aucune différence",
          "correct": false,
          "feedback": "Les deux mécanismes sont\ndistincts, à la fois par leur\nfonction et par leur usage,\ncomme détaillé dans la bonne\nréponse.\n"
        },
        {
          "text": "Les assertions s'exécutent toujours bien plus rapidement que les exceptions",
          "correct": false,
          "feedback": "La performance n'est pas le\ncritère pertinent ici. Les deux\nmécanismes ont des rôles\ndifférents.\n"
        },
        {
          "text": "Les exceptions n'existent pas en Python",
          "correct": false,
          "feedback": "Les exceptions sont au contraire\nun mécanisme central du langage\nPython.\n"
        },
        {
          "text": "Les assertions signalent un bug, les exceptions gèrent des cas attendus",
          "correct": true,
          "feedback": "On peut résumer ainsi : `assert`\nexprime « cela ne doit jamais\narriver », tandis que\n`try / except` exprime « cela peut\narriver, et voici comment je le\ngère ».\n"
        }
      ],
      "explanation": "Une assertion vérifie une hypothèse\ndu programmeur, comme une\nprécondition : si elle échoue, le\nprogramme contient un bug. Une saisie\nmal formée, elle, est un cas\nprévisible : on capture l'exception\navec `try / except` pour réagir\nproprement."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "debordement",
        "operateurs-sequentiels"
      ],
      "title": "Un indice qui sort de la liste",
      "partie": "pieges",
      "statement": "La fonction suivante doit renvoyer l'indice du premier nombre\nnégatif de la liste `t`, ou `len(t)` s'il n'y en a aucun :\n\n```python\ndef indice_premier_negatif(t):\n    i = 0\n    while t[i] >= 0:\n        i = i + 1\n    return i\n```\n\nL'appel `indice_premier_negatif([3, 5, 2])` s'arrête sur\n`IndexError: list index out of range`. Quelle condition de boucle\ncorrige la fonction ?",
      "options": [
        {
          "text": "`while t[i] >= 0 and i < len(t):`",
          "correct": false,
          "feedback": "Erreur : `and` évalue d'abord la condition de gauche. Pour\n`i = 3`, Python lit `t[3]` avant de tester `i < len(t)`, et\nl'`IndexError` se produit toujours.\n"
        },
        {
          "text": "`while i < len(t) and t[i] >= 0:`",
          "correct": true,
          "feedback": "Bonne réponse : pour `i = 3`, `i < len(t)` est faux, donc `and`\nn'évalue pas `t[3]`. La boucle s'arrête et la fonction renvoie\n`3`, soit `len(t)`.\n"
        },
        {
          "text": "`while i <= len(t) and t[i] >= 0:`",
          "correct": false,
          "feedback": "Erreur : l'indice `len(t)` n'existe pas, le dernier est\n`len(t) - 1`. Pour `i = 3`, le test `3 <= 3` est vrai et Python\nlit `t[3]` : l'`IndexError` demeure.\n"
        },
        {
          "text": "`while t[i] >= 0 or i < len(t):`",
          "correct": false,
          "feedback": "Erreur : avec `or`, la boucle continue dès qu'une condition est\nvraie. Tant que `i < len(t)`, elle ne s'arrête jamais, même sur un\nnombre négatif, et finit par lire `t[3]`.\n"
        }
      ],
      "explanation": "Un parcours par `while` doit garantir que l'indice reste dans la\nliste avant chaque lecture. Placer le test `i < len(t)` en premier\nprofite de l'évaluation séquentielle de `and` : la lecture `t[i]`\nn'a lieu que si l'indice est valide."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "flottants",
        "boucle-non-bornee"
      ],
      "title": "Une boucle qui rate sa cible",
      "partie": "pieges",
      "statement": "On exécute le programme suivant :\n\n```python\nx = 0\nn = 0\nwhile x != 1:\n    x = x + 0.1\n    n = n + 1\nprint(n)\n```\n\nQue se passe-t-il ?",
      "options": [
        {
          "text": "Il affiche `10`, après dix ajouts de `0.1`",
          "correct": false,
          "feedback": "Erreur : `0.1` n'a pas d'écriture binaire exacte. Après dix\najouts, `x` vaut `0.9999999999999999`, qui est différent de `1` :\nla boucle continue.\n"
        },
        {
          "text": "Il affiche `11`, `!=` faisant faire un tour de trop",
          "correct": false,
          "feedback": "Erreur : au onzième tour, `x` vaut `1.0999999999999999`, lui\naussi différent de `1`. Aucune valeur de `x` n'est jamais égale à\n`1`, la boucle ne s'arrête donc à aucun tour.\n"
        },
        {
          "text": "La boucle ne s'arrête jamais",
          "correct": true,
          "feedback": "Bonne réponse : les arrondis s'accumulent et `x` passe de\n`0.9999999999999999` à `1.0999999999999999` sans jamais valoir\nexactement `1`. La condition `x != 1` reste vraie indéfiniment.\n"
        },
        {
          "text": "Une erreur, un flottant ne se comparant pas à un entier",
          "correct": false,
          "feedback": "Erreur : Python compare sans problème un flottant et un entier,\npar exemple `1.0 == 1` vaut `True`. Le programme ne s'arrête sur\naucune erreur ; il boucle.\n"
        }
      ],
      "explanation": "Les calculs sur les flottants sont approchés : il faut éviter de\ntester l'égalité de deux flottants. Ici, on écrirait plutôt\n`while x < 1:`, ou mieux une boucle bornée\n`for n in range(10):` qui ne dépend pas d'arrondis."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "analyse-journaux"
      ],
      "title": "Analyse d'un journal d'exécution",
      "partie": "methode",
      "statement": "Face à un programme qui écrit\nrégulièrement dans un journal\nd'exécution, quelle stratégie\nd'analyse est la plus efficace\nlorsqu'un bug est signalé en\nproduction ?",
      "options": [
        {
          "text": "Lire l'intégralité du journal depuis le début, ligne par ligne",
          "correct": false,
          "feedback": "Démarche peu efficace : un\njournal de production peut\ncontenir des millions de lignes.\nIl faut cibler la zone\ntemporelle proche du moment\noù le bug s'est produit.\n"
        },
        {
          "text": "Effacer le journal et redémarrer le programme",
          "correct": false,
          "feedback": "Démarche désastreuse : on\nperd la principale source\nd'information sur le bug. Au\ncontraire, il faut conserver\nprécieusement les journaux\ndes incidents pour pouvoir\nles analyser à froid.\n"
        },
        {
          "text": "Désactiver le journal et compter sur les utilisateurs pour signaler les erreurs",
          "correct": false,
          "feedback": "Démarche contre-productive : un\njournal détaillé est l'outil\nprincipal de diagnostic en\nproduction. Sans lui, on est\naveugle quand un bug\nsurvient.\n"
        },
        {
          "text": "Cibler la fenêtre temporelle de l'erreur, puis remonter à l'événement anormal",
          "correct": true,
          "feedback": "C'est la démarche standard.\nOn part de la trace d'erreur\n(qui donne l'horodatage et le\ncontexte), on filtre les\nlignes voisines (avec `grep`,\n`tail`, ou un outil dédié),\npuis on remonte la chaîne\nde causalité. Le journal\nrévèle alors ce qui a\nprécédé le plantage : appel\ninattendu, état incohérent,\nressource manquante, etc.\n"
        }
      ],
      "explanation": "Bonne pratique : horodater chaque\nligne du journal. En cas d'incident,\nfiltrer le journal sur la fenêtre\ntemporelle de l'erreur permet de\nreconstituer rapidement la chaîne\nd'événements qui a mené au bug."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "synthese-systematique"
      ],
      "title": "Méthodologie face à un bug",
      "partie": "methode",
      "statement": "Quelle est la **bonne méthodologie**\ngénérale lorsqu'on est confronté à un\nbug ?",
      "options": [
        {
          "text": "Réécrire l'intégralité du programme",
          "correct": false,
          "feedback": "Réécrire l'ensemble du\nprogramme est rarement la\nbonne réponse. Cela demande un\neffort considérable et risque\nd'introduire d'autres bugs.\n"
        },
        {
          "text": "Demander la solution à un collègue",
          "correct": false,
          "feedback": "Solliciter un collègue peut\nêtre utile dans le diagnostic,\nmais ne constitue pas une\nméthodologie complète face à un\nbug.\n"
        },
        {
          "text": "Modifier le code au hasard jusqu'à ce que le bug finisse par disparaître",
          "correct": false,
          "feedback": "Cette démarche est dangereuse :\nelle risque d'introduire de\nnouveaux bugs et masque\nl'origine réelle du problème.\n"
        },
        {
          "text": "Reproduire, isoler, corriger, puis ajouter un test du cas fautif",
          "correct": true,
          "feedback": "Cette méthodologie est éprouvée.\nSauter une étape, en particulier\nl'ajout au jeu de tests du cas qui\nfaisait échouer le programme,\nexpose à voir le bug réapparaître\nlors d'une modification ultérieure.\n"
        }
      ],
      "explanation": "La discipline est essentielle. Il\nfaut s'imposer de ne jamais\ncorriger un bug sans avoir réussi\nà le reproduire, et de ne jamais\ncorriger sans ajouter ensuite un\ntest. Sinon, le bug finira par\nréapparaître, et le temps gagné\nau départ sera perdu plus tard."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "partie": "methode",
      "statement": "Parmi les affirmations suivantes sur la\nmise au point des programmes, laquelle\nest **fausse** ?",
      "options": [
        {
          "text": "Il faut corriger le bug d'abord, puis essayer de le reproduire",
          "correct": true,
          "feedback": "C'est l'inverse qui est vrai\n(donc cette affirmation est\nbien la fausse). Il faut\nd'abord **reproduire** le bug,\nafin de pouvoir vérifier\nensuite que la correction est\neffective, puis seulement\ncorriger.\n"
        },
        {
          "text": "La construction `try / except` permet de capturer des exceptions",
          "correct": false,
          "feedback": "Cette affirmation est correcte.\nC'est précisément le rôle du\nmécanisme `try / except`.\n"
        },
        {
          "text": "Le succès d'un jeu de tests ne garantit pas la correction du programme",
          "correct": false,
          "feedback": "Cette affirmation est correcte. Un\ntest ne vérifie que le cas qu'il\nexamine : un bug peut se cacher dans\nune entrée que personne n'a testée.\n"
        },
        {
          "text": "Une assertion qui échoue signale typiquement un bug dans le programme",
          "correct": false,
          "feedback": "Cette affirmation est correcte.\nUne assertion représente une\nvérification d'invariant : si\nelle échoue, c'est qu'une\nhypothèse fondamentale du\nprogramme est violée.\n"
        }
      ],
      "explanation": "L'adage classique en débogage est :\n« si l'on ne peut pas reproduire le\nbug, on ne peut pas le corriger ».\nLa reproduction est l'étape\npremière, celle qui permet de\nmesurer la qualité de la\ncorrection."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "gestionnaire-contexte"
      ],
      "title": "Le mot-clé with",
      "partie": "erreurs",
      "statement": "Quel est l'avantage d'écrire\n```python\nwith open(\"fichier.txt\") as f:\n    contenu = f.read()\n```\npar rapport à\n```python\nf = open(\"fichier.txt\")\ncontenu = f.read()\nf.close()\n```\n?",
      "options": [
        {
          "text": "La construction `with` permet\nd'écrire dans le fichier sans\nouvrir un mode d'écriture\n",
          "correct": false,
          "feedback": "Erreur : `with` n'a aucun effet\nsur le mode d'ouverture. Pour\nécrire dans un fichier, il faut\ntoujours préciser `mode='w'` ou\n`'a'`, que l'on utilise `with` ou\nnon.\n"
        },
        {
          "text": "La construction `with` est\nobligatoire en Python depuis la\nversion 3\n",
          "correct": false,
          "feedback": "Erreur : la construction `with`\nn'est pas obligatoire ; elle est\nfortement recommandée. La\nversion manuelle avec `open` et\n`close` reste valide\nsyntaxiquement, mais elle est\nfragile face aux exceptions.\n"
        },
        {
          "text": "La construction `with` est plus\nrapide à l'exécution\n",
          "correct": false,
          "feedback": "Erreur : la performance est\nidentique. L'avantage est dans la\nrobustesse face aux exceptions, pas\ndans la vitesse.\n"
        },
        {
          "text": "La construction `with` ferme automatiquement le fichier, même en cas d'exception",
          "correct": true,
          "feedback": "Bonne réponse : c'est la principale\nraison d'utiliser `with`. Si une\nexception est levée pendant le\n`read()` dans la version sans `with`,\nl'instruction `f.close()` ne sera\njamais exécutée, et le fichier reste\nouvert. La construction `with`\ngarantit la fermeture, qu'il y ait\nerreur ou non.\n"
        }
      ],
      "explanation": "Le bloc `with` délimite la durée\nd'utilisation du fichier : dès que\nl'on sort du bloc indenté,\nnormalement ou à cause d'une\nexception, le fichier est fermé.\nC'est la forme à privilégier pour\nlire ou écrire un fichier."
    },
    {
      "id": "q27",
      "difficulty": 2,
      "skills": [
        "pieges",
        "flottants",
        "egalite"
      ],
      "title": "Une égalité entre flottants",
      "partie": "pieges",
      "statement": "Que vaut l'expression `0.1 + 0.2 == 0.3` ?",
      "options": [
        {
          "text": "`False`, la somme valant `0.30000000000000004`",
          "correct": true,
          "feedback": "Bonne réponse : $0{,}1$ et $0{,}2$ n'ont pas d'écriture binaire\nexacte, et les arrondis s'accumulent. On compare des flottants\nà une tolérance près, `abs(a - b) < 1e-9`.\n"
        },
        {
          "text": "`True`, l'addition de deux décimaux étant exacte",
          "correct": false,
          "feedback": "Erreur : en binaire, $0{,}1$ est une fraction infinie, arrondie\nau stockage.\n"
        },
        {
          "text": "Une erreur, deux flottants ne pouvant être comparés",
          "correct": false,
          "feedback": "Erreur : la comparaison est permise, c'est son résultat qui\nsurprend.\n"
        },
        {
          "text": "`True`, Python arrondissant avant de comparer",
          "correct": false,
          "feedback": "Erreur : aucun arrondi n'est fait à la comparaison.\n"
        }
      ],
      "explanation": "Tester l'égalité de deux flottants est un piège classique : on\nteste plutôt que leur écart est petit."
    },
    {
      "id": "q28",
      "difficulty": 3,
      "skills": [
        "pieges",
        "listes-imbriquees",
        "alias",
        "trace"
      ],
      "title": "Une grille dont les lignes sont la même liste",
      "partie": "pieges",
      "statement": "Qu'affiche ce programme ?\n```python\ngrille = [[0] * 3] * 3\ngrille[0][0] = 1\nprint(grille)\n```",
      "options": [
        {
          "text": "`[[1, 0, 0], [1, 0, 0], [1, 0, 0]]`, les trois lignes étant la même liste",
          "correct": true,
          "feedback": "Bonne réponse : `[liste] * 3` répète trois fois la même\nréférence ; modifier une ligne modifie les trois. Il faut\nconstruire chaque ligne séparément, par exemple avec une boucle\nou une compréhension.\n"
        },
        {
          "text": "`[[1, 0, 0], [0, 0, 0], [0, 0, 0]]`, seule la première ligne changeant",
          "correct": false,
          "feedback": "Erreur : ce serait le cas avec trois listes distinctes, ce que\n`* 3` ne crée pas.\n"
        },
        {
          "text": "`[[1, 1, 1], [0, 0, 0], [0, 0, 0]]`, la ligne entière valant `1`",
          "correct": false,
          "feedback": "Erreur : seule la case d'indice $0$ est modifiée, mais dans\nchaque ligne.\n"
        },
        {
          "text": "Une erreur, une liste de listes ne pouvant être multipliée",
          "correct": false,
          "feedback": "Erreur : la multiplication est permise, et c'est elle qui crée\nle piège.\n"
        }
      ],
      "explanation": "Multiplier une liste répète des références, pas des copies : le\npiège touche toutes les structures imbriquées."
    },
    {
      "id": "q29",
      "difficulty": 3,
      "skills": [
        "pieges",
        "argument-par-defaut",
        "mutable",
        "trace"
      ],
      "title": "Une valeur par défaut partagée entre les appels",
      "partie": "pieges",
      "statement": "Qu'affiche la dernière ligne ?\n```python\ndef ajoute(x, l=[]):\n    l.append(x)\n    return l\n\najoute(1)\nprint(ajoute(2))\n```",
      "options": [
        {
          "text": "`[1, 2]`, la liste par défaut étant créée une seule fois",
          "correct": true,
          "feedback": "Bonne réponse : la valeur par défaut est évaluée à la\ndéfinition de la fonction, puis partagée par tous les appels\nqui ne fournissent pas `l`. Le premier appel y a laissé `1`.\n"
        },
        {
          "text": "`[2]`, chaque appel repartant d'une liste vide",
          "correct": false,
          "feedback": "Erreur : la liste vide n'est pas recréée à chaque appel.\n"
        },
        {
          "text": "`[1]`, le second appel n'ayant rien ajouté",
          "correct": false,
          "feedback": "Erreur : le second appel ajoute bien `2`.\n"
        },
        {
          "text": "Une erreur, une liste ne pouvant servir de valeur par défaut",
          "correct": false,
          "feedback": "Erreur : c'est permis, et c'est précisément le piège.\n"
        }
      ],
      "explanation": "Pour une valeur par défaut mutable, on écrit `l=None` et l'on crée\nla liste dans la fonction.",
      "variants": [
        {
          "statement": "Qu'affiche la dernière ligne ?\n```python\ndef ajoute(x, l=[]):\n    l.append(x)\n    return l\n\najoute(1)\nprint(ajoute(2))\n```",
          "options": [
            {
              "text": "`[1, 2]`, la liste par défaut étant créée une seule fois",
              "correct": true,
              "feedback": "Bonne réponse : la valeur par défaut est évaluée à la\ndéfinition de la fonction, puis partagée par tous les appels\nqui ne fournissent pas `l`. Le premier appel y a laissé `1`.\n"
            },
            {
              "text": "`[2]`, chaque appel repartant d'une liste vide",
              "correct": false,
              "feedback": "Erreur : la liste vide n'est pas recréée à chaque appel.\n"
            },
            {
              "text": "`[1]`, le second appel n'ayant rien ajouté",
              "correct": false,
              "feedback": "Erreur : le second appel ajoute bien `2`.\n"
            },
            {
              "text": "Une erreur, une liste ne pouvant servir de valeur par défaut",
              "correct": false,
              "feedback": "Erreur : c'est permis, et c'est précisément le piège.\n"
            }
          ],
          "explanation": "Pour une valeur par défaut mutable, on écrit `l=None` et l'on crée\nla liste dans la fonction."
        },
        {
          "statement": "Qu'affiche la dernière ligne ?\n```python\ndef ajoute(x, l=[]):\n    l.append(x)\n    return l\n\najoute(5)\nprint(ajoute(7))\n```",
          "options": [
            {
              "text": "`[5, 7]`, la liste par défaut étant créée une seule fois",
              "correct": true,
              "feedback": "Bonne réponse : la valeur par défaut est évaluée à la\ndéfinition de la fonction, puis partagée par tous les appels\nqui ne fournissent pas `l`. Le premier appel y a laissé `5`.\n"
            },
            {
              "text": "`[7]`, chaque appel repartant d'une liste vide",
              "correct": false,
              "feedback": "Erreur : la liste vide n'est pas recréée à chaque appel.\n"
            },
            {
              "text": "`[5]`, le second appel n'ayant rien ajouté",
              "correct": false,
              "feedback": "Erreur : le second appel ajoute bien `7`.\n"
            },
            {
              "text": "Une erreur, une liste ne pouvant servir de valeur par défaut",
              "correct": false,
              "feedback": "Erreur : c'est permis, et c'est précisément le piège.\n"
            }
          ],
          "explanation": "Pour une valeur par défaut mutable, on écrit `l=None` et l'on crée\nla liste dans la fonction."
        },
        {
          "statement": "Qu'affiche la dernière ligne ?\n```python\ndef ajoute(x, l=[]):\n    l.append(x)\n    return l\n\najoute(3)\nprint(ajoute(9))\n```",
          "options": [
            {
              "text": "`[3, 9]`, la liste par défaut étant créée une seule fois",
              "correct": true,
              "feedback": "Bonne réponse : la valeur par défaut est évaluée à la\ndéfinition de la fonction, puis partagée par tous les appels\nqui ne fournissent pas `l`. Le premier appel y a laissé `3`.\n"
            },
            {
              "text": "`[9]`, chaque appel repartant d'une liste vide",
              "correct": false,
              "feedback": "Erreur : la liste vide n'est pas recréée à chaque appel.\n"
            },
            {
              "text": "`[3]`, le second appel n'ayant rien ajouté",
              "correct": false,
              "feedback": "Erreur : le second appel ajoute bien `9`.\n"
            },
            {
              "text": "Une erreur, une liste ne pouvant servir de valeur par défaut",
              "correct": false,
              "feedback": "Erreur : c'est permis, et c'est précisément le piège.\n"
            }
          ],
          "explanation": "Pour une valeur par défaut mutable, on écrit `l=None` et l'on crée\nla liste dans la fonction."
        }
      ]
    },
    {
      "id": "q30",
      "difficulty": 2,
      "skills": [
        "pieges",
        "egalite",
        "identite",
        "is"
      ],
      "title": "Deux listes égales mais distinctes",
      "partie": "pieges",
      "statement": "Qu'affichent les deux `print` ?\n```python\na = [1, 2]\nb = [1, 2]\nprint(a == b)\nprint(a is b)\n```",
      "options": [
        {
          "text": "`True` puis `False`, deux listes de même contenu mais distinctes",
          "correct": true,
          "feedback": "Bonne réponse : `==` compare les contenus, `is` demande si les\ndeux noms désignent le même objet en mémoire. Ici deux listes\nont été créées.\n"
        },
        {
          "text": "`True` puis `True`, les deux listes étant identiques",
          "correct": false,
          "feedback": "Erreur : deux crochets, deux listes ; `is` les distingue.\n"
        },
        {
          "text": "`False` puis `False`, deux listes n'étant jamais égales",
          "correct": false,
          "feedback": "Erreur : `==` sur des listes compare élément par élément.\n"
        },
        {
          "text": "`False` puis `True`, `is` étant plus tolérant que `==`",
          "correct": false,
          "feedback": "Erreur : c'est l'inverse, `is` est le test le plus strict.\n"
        }
      ],
      "explanation": "`is` ne sert qu'à tester `None` ou l'identité d'objets ; pour\ncomparer des valeurs, on utilise `==`."
    },
    {
      "id": "q31",
      "difficulty": 2,
      "skills": [
        "pieges",
        "indentation",
        "return",
        "trace"
      ],
      "title": "Un retour placé dans la boucle",
      "partie": "pieges",
      "statement": "Qu'affiche ce programme ?\n```python\ndef somme(l):\n    total = 0\n    for x in l:\n        total = total + x\n        return total\n\nprint(somme([1, 2, 3]))\n```",
      "options": [
        {
          "text": "`1`, la boucle s'arrêtant dès le premier tour",
          "correct": true,
          "feedback": "Bonne réponse : indenté dans le corps de la boucle, `return`\ns'exécute dès le premier élément. Il doit être au niveau du\n`for`, après la boucle.\n"
        },
        {
          "text": "`6`, la somme de tous les éléments de la liste",
          "correct": false,
          "feedback": "Erreur : la boucle ne va pas jusqu'au bout.\n"
        },
        {
          "text": "`3`, le dernier élément de la liste",
          "correct": false,
          "feedback": "Erreur : c'est le premier élément qui est renvoyé, pas le\ndernier.\n"
        },
        {
          "text": "`0`, le `return` précédant toute addition",
          "correct": false,
          "feedback": "Erreur : l'addition de la ligne précédente a déjà eu lieu.\n"
        }
      ],
      "explanation": "Une erreur d'indentation ne provoque aucun message : seul le\nrésultat trahit le `return` mal placé.",
      "variants": [
        {
          "statement": "Qu'affiche ce programme ?\n```python\ndef somme(l):\n    total = 0\n    for x in l:\n        total = total + x\n        return total\n\nprint(somme([1, 2, 3]))\n```",
          "options": [
            {
              "text": "`1`, la boucle s'arrêtant dès le premier tour",
              "correct": true,
              "feedback": "Bonne réponse : indenté dans le corps de la boucle, `return`\ns'exécute dès le premier élément. Il doit être au niveau du\n`for`, après la boucle.\n"
            },
            {
              "text": "`6`, la somme de tous les éléments de la liste",
              "correct": false,
              "feedback": "Erreur : la boucle ne va pas jusqu'au bout.\n"
            },
            {
              "text": "`3`, le dernier élément de la liste",
              "correct": false,
              "feedback": "Erreur : c'est le premier élément qui est renvoyé, pas le\ndernier.\n"
            },
            {
              "text": "`0`, le `return` précédant toute addition",
              "correct": false,
              "feedback": "Erreur : l'addition de la ligne précédente a déjà eu lieu.\n"
            }
          ],
          "explanation": "Une erreur d'indentation ne provoque aucun message : seul le\nrésultat trahit le `return` mal placé."
        },
        {
          "statement": "Qu'affiche ce programme ?\n```python\ndef somme(l):\n    total = 0\n    for x in l:\n        total = total + x\n        return total\n\nprint(somme([4, 5]))\n```",
          "options": [
            {
              "text": "`4`, la boucle s'arrêtant dès le premier tour",
              "correct": true,
              "feedback": "Bonne réponse : indenté dans le corps de la boucle, `return`\ns'exécute dès le premier élément. Il doit être au niveau du\n`for`, après la boucle.\n"
            },
            {
              "text": "`9`, la somme de tous les éléments de la liste",
              "correct": false,
              "feedback": "Erreur : la boucle ne va pas jusqu'au bout.\n"
            },
            {
              "text": "`5`, le dernier élément de la liste",
              "correct": false,
              "feedback": "Erreur : c'est le premier élément qui est renvoyé, pas le\ndernier.\n"
            },
            {
              "text": "`0`, le `return` précédant toute addition",
              "correct": false,
              "feedback": "Erreur : l'addition de la ligne précédente a déjà eu lieu.\n"
            }
          ],
          "explanation": "Une erreur d'indentation ne provoque aucun message : seul le\nrésultat trahit le `return` mal placé."
        },
        {
          "statement": "Qu'affiche ce programme ?\n```python\ndef somme(l):\n    total = 0\n    for x in l:\n        total = total + x\n        return total\n\nprint(somme([7, 1, 2]))\n```",
          "options": [
            {
              "text": "`7`, la boucle s'arrêtant dès le premier tour",
              "correct": true,
              "feedback": "Bonne réponse : indenté dans le corps de la boucle, `return`\ns'exécute dès le premier élément. Il doit être au niveau du\n`for`, après la boucle.\n"
            },
            {
              "text": "`10`, la somme de tous les éléments de la liste",
              "correct": false,
              "feedback": "Erreur : la boucle ne va pas jusqu'au bout.\n"
            },
            {
              "text": "`2`, le dernier élément de la liste",
              "correct": false,
              "feedback": "Erreur : c'est le premier élément qui est renvoyé, pas le\ndernier.\n"
            },
            {
              "text": "`0`, le `return` précédant toute addition",
              "correct": false,
              "feedback": "Erreur : l'addition de la ligne précédente a déjà eu lieu.\n"
            }
          ],
          "explanation": "Une erreur d'indentation ne provoque aucun message : seul le\nrésultat trahit le `return` mal placé."
        }
      ]
    },
    {
      "id": "q32",
      "difficulty": 1,
      "skills": [
        "erreurs",
        "typeerror",
        "message"
      ],
      "title": "Lire un message de type",
      "partie": "erreurs",
      "statement": "Un programme s'arrête sur ce message. Quelle instruction l'a\nprovoqué ?\n```\nTypeError: can only concatenate str (not \"int\") to str\n```",
      "options": [
        {
          "text": "`\"Âge : \" + 17`, une chaîne concaténée à un entier",
          "correct": true,
          "feedback": "Bonne réponse : `+` entre une chaîne et un entier est refusé ;\nil faut convertir avec `str(17)`.\n"
        },
        {
          "text": "`10 / 0`, une division par zéro dans un calcul",
          "correct": false,
          "feedback": "Erreur : ce serait une `ZeroDivisionError`.\n"
        },
        {
          "text": "`liste[5]` sur une liste de trois éléments seulement",
          "correct": false,
          "feedback": "Erreur : ce serait une `IndexError`.\n"
        },
        {
          "text": "`print(age)` sans avoir défini la variable `age`",
          "correct": false,
          "feedback": "Erreur : ce serait une `NameError`.\n"
        }
      ],
      "explanation": "Le nom de l'exception et son message désignent la nature de\nl'erreur ; les lire évite de chercher au hasard."
    },
    {
      "id": "q33",
      "difficulty": 2,
      "skills": [
        "erreurs",
        "valueerror",
        "conversion"
      ],
      "title": "Une conversion qui échoue",
      "partie": "erreurs",
      "statement": "Quelle exception lève `int(\"12a\")` ?",
      "options": [
        {
          "text": "`ValueError`, le type est bon mais la valeur ne se convertit pas",
          "correct": true,
          "feedback": "Bonne réponse : `int` accepte une chaîne, mais `\"12a\"` ne\nreprésente pas un entier. C'est la valeur qui est en cause.\n"
        },
        {
          "text": "`TypeError`, une chaîne ne pouvant devenir un entier",
          "correct": false,
          "feedback": "Erreur : `int(\"12\")` fonctionne, le type chaîne est accepté.\n"
        },
        {
          "text": "`SyntaxError`, la lettre `a` étant mal placée dans le nombre",
          "correct": false,
          "feedback": "Erreur : la syntaxe du programme est correcte ; l'erreur\nsurvient à l'exécution.\n"
        },
        {
          "text": "Aucune, Python gardant les chiffres et renvoyant `12`",
          "correct": false,
          "feedback": "Erreur : Python ne tronque pas, il refuse.\n"
        }
      ],
      "explanation": "`TypeError` signale un type inadapté, `ValueError` une valeur\ninadaptée pour un type accepté."
    },
    {
      "id": "q34",
      "difficulty": 2,
      "skills": [
        "erreurs",
        "propagation",
        "appelant"
      ],
      "title": "Une exception que la fonction ne capture pas",
      "partie": "erreurs",
      "statement": "Une exception est levée dans une fonction qui ne contient aucun\n`try`. Que devient-elle ?",
      "options": [
        {
          "text": "Elle remonte à l'appelant, qui la capture ou laisse le programme s'arrêter",
          "correct": true,
          "feedback": "Bonne réponse : l'exception interrompt la fonction et remonte\nd'appel en appel jusqu'à un `try` qui la capture ; sans lui, le\nprogramme s'arrête avec la trace.\n"
        },
        {
          "text": "Elle est ignorée et la fonction renvoie `None` à l'appelant",
          "correct": false,
          "feedback": "Erreur : une exception n'est jamais ignorée en silence.\n"
        },
        {
          "text": "Elle arrête seulement la fonction, le programme continuant après l'appel",
          "correct": false,
          "feedback": "Erreur : l'appelant est interrompu à son tour, sauf s'il la\ncapture.\n"
        },
        {
          "text": "Elle est capturée automatiquement par Python, qui affiche un avertissement",
          "correct": false,
          "feedback": "Erreur : Python affiche la trace et arrête le programme, ce\nn'est pas un simple avertissement.\n"
        }
      ],
      "explanation": "La trace d'erreur montre cette remontée : la pile des appels, de\nl'appel initial jusqu'à la ligne fautive."
    },
    {
      "id": "q35",
      "difficulty": 2,
      "skills": [
        "erreurs",
        "try",
        "portee"
      ],
      "title": "Ce qu'il faut placer dans un bloc try",
      "partie": "erreurs",
      "statement": "Quelle portion de code placer dans un bloc `try` ?",
      "options": [
        {
          "text": "La seule instruction risquée, pour ne pas masquer d'autres erreurs",
          "correct": true,
          "feedback": "Bonne réponse : un `try` trop large capture aussi les\nexceptions inattendues, et cache alors de vrais bugs.\n"
        },
        {
          "text": "Tout le programme, pour qu'il ne s'arrête jamais en cours de route",
          "correct": false,
          "feedback": "Erreur : un programme qui continue après une erreur inconnue\nproduit des résultats faux.\n"
        },
        {
          "text": "Les définitions de fonctions, où naissent les erreurs",
          "correct": false,
          "feedback": "Erreur : définir une fonction ne lève rien ; c'est son appel\nqui peut échouer.\n"
        },
        {
          "text": "Les appels de `print`, seules instructions pouvant échouer",
          "correct": false,
          "feedback": "Erreur : `print` est une des instructions les plus sûres.\n"
        }
      ],
      "explanation": "Un `try` exprime une attente précise : telle instruction peut\néchouer de telle façon, et voici quoi faire."
    },
    {
      "id": "q36",
      "difficulty": 1,
      "skills": [
        "erreurs",
        "keyerror",
        "dictionnaire"
      ],
      "title": "Une clé absente du dictionnaire",
      "partie": "erreurs",
      "statement": "Que produit `notes[\"zoe\"]` si le dictionnaire `notes` ne contient\npas la clé `\"zoe\"` ?",
      "options": [
        {
          "text": "Une `KeyError`, la clé n'existant pas",
          "correct": true,
          "feedback": "Bonne réponse : lire une clé absente est une erreur ; on teste\nd'abord `\"zoe\" in notes`, ou l'on utilise `notes.get(\"zoe\")`.\n"
        },
        {
          "text": "Une `IndexError`, comme pour une liste",
          "correct": false,
          "feedback": "Erreur : `IndexError` concerne les indices entiers des listes.\n"
        },
        {
          "text": "`None`, sans aucune erreur ni message",
          "correct": false,
          "feedback": "Erreur : c'est `notes.get(\"zoe\")` qui renvoie `None`, pas les\ncrochets.\n"
        },
        {
          "text": "`0`, la valeur par défaut des notes",
          "correct": false,
          "feedback": "Erreur : un dictionnaire n'a pas de valeur par défaut.\n"
        }
      ],
      "explanation": "Chaque structure a son exception d'accès : `IndexError` pour les\nlistes, `KeyError` pour les dictionnaires."
    },
    {
      "id": "q37",
      "difficulty": 2,
      "skills": [
        "erreurs",
        "try",
        "boucle",
        "robustesse"
      ],
      "title": "Une exception au milieu d'une boucle",
      "partie": "erreurs",
      "statement": "Que se passe-t-il si un seul élément de `textes` n'est pas\nconvertible ?\n```python\nnombres = []\nfor t in textes:\n    try:\n        nombres.append(int(t))\n    except ValueError:\n        print(\"valeur ignorée\")\n```",
      "options": [
        {
          "text": "Il est signalé et sauté, les autres éléments étant convertis",
          "correct": true,
          "feedback": "Bonne réponse : le `try` est dans la boucle ; l'exception est\ntraitée au tour fautif et le tour suivant commence normalement.\n"
        },
        {
          "text": "La boucle s'arrête à cet élément, les suivants étant perdus",
          "correct": false,
          "feedback": "Erreur : ce serait le cas avec le `try` autour de la boucle\nentière.\n"
        },
        {
          "text": "Le programme s'arrête malgré le `try`, l'erreur étant fatale",
          "correct": false,
          "feedback": "Erreur : l'exception est bien une `ValueError`, capturée.\n"
        },
        {
          "text": "Tous les éléments sont ignorés par précaution, la liste restant vide",
          "correct": false,
          "feedback": "Erreur : seul le tour où l'exception est levée passe dans le\n`except`.\n"
        }
      ],
      "explanation": "La place du `try` décide de ce qui est abandonné : un tour de\nboucle, ou la boucle entière."
    },
    {
      "id": "q38",
      "difficulty": 2,
      "skills": [
        "methode",
        "tests",
        "regression"
      ],
      "title": "Un test qui échoue après une modification",
      "partie": "methode",
      "statement": "Tous les tests passaient. Après une modification du programme,\nl'un d'eux échoue. Qu'en conclure ?",
      "options": [
        {
          "text": "La modification a cassé un comportement qui fonctionnait",
          "correct": true,
          "feedback": "Bonne réponse : c'est le rôle des tests conservés d'une version\nà l'autre, dits de non-régression ; ils signalent aussitôt ce\nque la modification a dégradé.\n"
        },
        {
          "text": "Le test est forcément faux, puisqu'il passait avant",
          "correct": false,
          "feedback": "Erreur : un test qui passait décrit un comportement attendu ;\nc'est le programme qui a changé.\n"
        },
        {
          "text": "Il faut supprimer ce test pour retrouver une série au vert",
          "correct": false,
          "feedback": "Erreur : supprimer le test cache le problème sans le résoudre.\n"
        },
        {
          "text": "Le programme n'a jamais fonctionné, les anciens tests étant trop faibles",
          "correct": false,
          "feedback": "Erreur : rien ne permet de le dire ; le test échoue à cause de\nla modification.\n"
        }
      ],
      "explanation": "Relancer tous les tests après chaque modification protège contre\nles régressions, ces bugs introduits en corrigeant autre chose."
    },
    {
      "id": "q39",
      "difficulty": 3,
      "skills": [
        "methode",
        "localisation",
        "dichotomie"
      ],
      "title": "Localiser un bug dans un long programme",
      "partie": "methode",
      "statement": "Un programme de deux cents lignes donne un résultat faux, sans\nmessage d'erreur. Comment localiser efficacement la ligne fautive ?",
      "options": [
        {
          "text": "Vérifier l'état à mi-parcours, puis chercher dans la moitié fautive",
          "correct": true,
          "feedback": "Bonne réponse : comme une recherche dichotomique, chaque\nvérification divise par deux la zone à inspecter ; quelques\naffichages suffisent.\n"
        },
        {
          "text": "Relire le programme en entier du début à la fin, autant de fois que nécessaire",
          "correct": false,
          "feedback": "Erreur : une relecture sans point de repère rate souvent\nl'erreur.\n"
        },
        {
          "text": "Commencer par la dernière ligne, où le résultat est affiché",
          "correct": false,
          "feedback": "Erreur : l'affichage final est rarement fautif ; l'erreur est en\namont.\n"
        },
        {
          "text": "Réécrire le programme entièrement, en espérant ne pas refaire l'erreur",
          "correct": false,
          "feedback": "Erreur : réécrire coûte cher et reproduit souvent les mêmes\nerreurs.\n"
        }
      ],
      "explanation": "La mise au point suit la même logique que les algorithmes : réduire\nl'espace de recherche à chaque étape."
    }
  ]
}