{
  "chapter": {
    "id": "classes-objets",
    "level": "terminale",
    "theme": "Programmation",
    "title": "Classes et programmation objet",
    "description": "Vocabulaire de la programmation objet en Python :\nclasses, attributs, méthodes, objets (instances),\nconstructeur `__init__`, accès aux attributs et\nméthodes, premiers exemples (Point, Voiture,\nPile). Lien avec les structures de données.",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition-classe"
      ],
      "title": "Notion de classe",
      "statement": "En programmation orientée objet, qu'est-ce\nqu'une **classe** ?",
      "options": [
        {
          "text": "Un modèle ou « plan » qui décrit la structure (les attributs) et le comportement (les méthodes) d'objets de même nature",
          "correct": true,
          "feedback": "La classe constitue\nl'abstraction. Les objets\nconcrets sont les **instances**\nde cette classe.\n"
        },
        {
          "text": "Un fichier source Python",
          "correct": false,
          "feedback": "Un fichier source Python est un\n**module**, pas une classe. Un\nmodule peut d'ailleurs contenir\nplusieurs classes.\n"
        },
        {
          "text": "Un type particulier d'erreur Python",
          "correct": false,
          "feedback": "Une classe n'a aucun rapport avec\nune erreur. Les erreurs sont\nplutôt signalées par des\nexceptions.\n"
        },
        {
          "text": "Une simple variable du programme",
          "correct": false,
          "feedback": "Une classe est bien plus qu'une\nvariable : c'est un modèle qui\ndécrit la structure et le\ncomportement d'objets de même\nnature, comme expliqué dans la\nbonne réponse.\n"
        }
      ],
      "explanation": "Une analogie classique : la classe\nest comparable à un moule à\ngâteaux ; les objets, ses instances,\nsont les gâteaux fabriqués avec ce\nmoule. Tous les gâteaux partagent\nla même forme (les méthodes), mais\nchacun peut avoir sa propre\ngarniture (ses attributs)."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "definition-objet"
      ],
      "title": "Notion d'objet",
      "statement": "Qu'est-ce qu'un **objet** (ou\n**instance**) d'une classe ?",
      "options": [
        {
          "text": "Une réalisation concrète d'une classe, possédant ses propres valeurs d'attributs",
          "correct": true,
          "feedback": "On utilise indifféremment les\nmots « objet » et « instance ».\nÀ partir d'une classe `Voiture`,\non peut créer plusieurs objets\n`Voiture` distincts, chacun avec\nses propres caractéristiques.\n"
        },
        {
          "text": "Un commentaire inséré dans le code source",
          "correct": false,
          "feedback": "Les commentaires sont précédés\ndu caractère `#` en Python et\nn'ont aucun effet à\nl'exécution. Un objet, en\nrevanche, est manipulé pendant\nl'exécution.\n"
        },
        {
          "text": "Un fichier de configuration du programme",
          "correct": false,
          "feedback": "Un objet est une entité\ncréée à l'exécution à partir\nd'une classe, et non un fichier\nstocké sur le disque.\n"
        },
        {
          "text": "Un type primitif comme un entier ou un flottant",
          "correct": false,
          "feedback": "Un objet est créé à partir\nd'une classe, généralement\ndéfinie par le programmeur.\nCela dit, en Python, les\nentiers et les flottants sont\neux-mêmes des objets, instances\nde classes prédéfinies.\n"
        }
      ],
      "explanation": "En Python, **tout est un objet** :\nles entiers, les chaînes, les\nlistes, les fonctions, voire les\nclasses elles-mêmes sont des\ninstances de classes prédéfinies."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "attribut"
      ],
      "title": "Attribut",
      "statement": "Qu'est-ce qu'un **attribut** d'une\nclasse ?",
      "options": [
        {
          "text": "Une fonction définie à l'intérieur de la classe",
          "correct": false,
          "feedback": "Cette description correspond à\nune **méthode**, et non à un\nattribut. Les méthodes\ndéfinissent le comportement,\nles attributs représentent les\ndonnées.\n"
        },
        {
          "text": "Un module de la bibliothèque Python",
          "correct": false,
          "feedback": "Un module est un fichier\nPython, et non un attribut.\nLes attributs appartiennent à\ndes objets.\n"
        },
        {
          "text": "Une donnée propre à un objet, par exemple le `nom` ou l'`age` d'une `Personne`",
          "correct": true,
          "feedback": "Les attributs caractérisent\nl'**état** d'un objet. Chaque\ninstance possède ses propres\nvaleurs d'attributs.\n"
        },
        {
          "text": "Un type particulier de boucle",
          "correct": false,
          "feedback": "Les boucles relèvent du\ncontrôle de flux, pas des\nobjets. Un attribut est un\ncomposant d'un objet.\n"
        }
      ],
      "explanation": "Pour une classe `Voiture`, des\nattributs typiques pourraient\nêtre : `marque`, `modele`,\n`couleur`, `vitesse`. Chaque\nvoiture aura ses propres valeurs\npour chacun de ces attributs."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "methode"
      ],
      "title": "Méthode",
      "statement": "Qu'est-ce qu'une **méthode** d'une\nclasse ?",
      "options": [
        {
          "text": "Une fonction définie dans la classe, qui peut accéder aux attributs des instances et agir sur eux",
          "correct": true,
          "feedback": "Les méthodes définissent le\n**comportement** des objets.\nPar convention, leur premier\nparamètre se nomme `self` et\ndésigne l'instance sur\nlaquelle la méthode est\nappelée.\n"
        },
        {
          "text": "Un fichier dédié aux tests automatisés",
          "correct": false,
          "feedback": "Les fichiers de tests\ncontiennent typiquement des\nfonctions de test. Une méthode,\nelle, est une fonction définie\nau sein d'une classe.\n"
        },
        {
          "text": "Un attribut un peu particulier",
          "correct": false,
          "feedback": "Une méthode est en réalité une\nfonction, et non un attribut.\nLes méthodes définissent le\ncomportement des objets, les\nattributs définissent leur\nétat.\n"
        },
        {
          "text": "Un type de variable particulier",
          "correct": false,
          "feedback": "Une méthode n'est pas une\nvariable, mais une fonction\nattachée à une classe.\n"
        }
      ],
      "explanation": "Pour la classe `Voiture`, des\nméthodes typiques pourraient\nêtre : `accelerer`, `freiner`,\n`afficher`. Elles agissent\ndirectement sur l'état de l'objet\npar l'intermédiaire de `self`."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "definition-classe-syntax"
      ],
      "title": "Définir une classe",
      "statement": "Quelle est la syntaxe Python correcte\npour **définir** une classe `Voiture`\nvide ?",
      "options": [
        {
          "text": "```python\nclass Voiture:\n    pass\n```\n",
          "correct": true,
          "feedback": "On utilise le mot-clé `class`,\npuis le nom de la classe (par\nconvention en CamelCase),\nsuivi de deux-points et d'un\ncorps indenté. L'instruction\n`pass` permet de signaler un\ncorps vide.\n"
        },
        {
          "text": "```python\nnew class Voiture {}\n```\n",
          "correct": false,
          "feedback": "Cette syntaxe correspond à\nJava ou à C++, mais pas à\nPython.\n"
        },
        {
          "text": "```python\nVoiture = class()\n```\n",
          "correct": false,
          "feedback": "Cette syntaxe n'existe pas en\nPython. La définition d'une\nclasse se fait avec le mot-clé\n`class` suivi du nom de la\nclasse.\n"
        },
        {
          "text": "```python\ndef Voiture():\n    pass\n```\n",
          "correct": false,
          "feedback": "Le mot-clé `def` introduit une\n**fonction**, et non une\nclasse. Pour une classe, il\nfaut utiliser `class`.\n"
        }
      ],
      "explanation": "Conventions Python : les noms de\nclasses s'écrivent en\n**CamelCase** (`Voiture`,\n`MaSuperClasse`), tandis que les\nnoms de fonctions et de variables\ns'écrivent en **snake_case**\n(`ma_fonction`, `ma_variable`)."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "instanciation"
      ],
      "title": "Créer une instance",
      "statement": "Comment **crée-t-on une instance** de\nla classe `Voiture` en Python ?",
      "options": [
        {
          "text": "Avec la syntaxe `v = new Voiture()`",
          "correct": false,
          "feedback": "Le mot-clé `new` n'existe pas\nen Python. Cette syntaxe est\nutilisée en Java, en JavaScript\nou en C++.\n"
        },
        {
          "text": "Avec la syntaxe `v = create Voiture`",
          "correct": false,
          "feedback": "La syntaxe `create` n'existe\npas en Python. La création\nd'une instance se fait\nsimplement en appelant la\nclasse avec des parenthèses.\n"
        },
        {
          "text": "Avec la syntaxe `v = Voiture`",
          "correct": false,
          "feedback": "Sans les parenthèses, la\nvariable `v` reçoit la classe\nelle-même, et non une instance.\nPour créer un objet, il faut\nappeler la classe.\n"
        },
        {
          "text": "Avec la syntaxe `v = Voiture()`",
          "correct": true,
          "feedback": "On appelle la classe comme\nune fonction. Cet appel\ndéclenche le constructeur\n(`__init__`, s'il est défini)\net renvoie le nouvel objet\ncréé.\n"
        }
      ],
      "explanation": "Une distinction subtile : le nom\n`Voiture` désigne la classe\nelle-même (un objet de type\nclasse), tandis que `Voiture()`\ndésigne une instance qu'on vient\nde créer."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "acces-attribut"
      ],
      "title": "Accéder à un attribut",
      "statement": "Si `v` est une instance de la classe\n`Voiture` possédant un attribut\n`marque`, comment lit-on la valeur de\ncet attribut ?",
      "options": [
        {
          "text": "Avec la notation `v.marque`",
          "correct": true,
          "feedback": "La notation pointée permet\nd'accéder aux attributs d'un\nobjet. Elle est aussi utilisée\npour appeler les méthodes :\n`v.demarrer()`.\n"
        },
        {
          "text": "Avec la notation `v->marque`",
          "correct": false,
          "feedback": "Cette syntaxe est utilisée en\nC++ ou en PHP, mais pas en\nPython.\n"
        },
        {
          "text": "Avec un appel `get(v, marque)`",
          "correct": false,
          "feedback": "Une fonction `getattr(v,\n\"marque\")` existe en Python,\nmais on lui préfère\nsystématiquement la notation\npointée, plus simple et plus\nlisible.\n"
        },
        {
          "text": "Avec la notation `v[\"marque\"]`",
          "correct": false,
          "feedback": "Cette syntaxe sert à accéder\nà une valeur dans un\ndictionnaire ou à un élément\nd'une liste, et non à un\nattribut d'objet.\n"
        }
      ],
      "explanation": "La notation pointée est la\nsyntaxe standard et la plus\nlisible pour accéder aux attributs\ncomme aux méthodes d'un objet."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "self"
      ],
      "title": "Le paramètre self",
      "statement": "À quoi sert le paramètre **`self`**\ndans les méthodes d'une classe Python ?",
      "options": [
        {
          "text": "C'est une variable globale du programme",
          "correct": false,
          "feedback": "`self` est un paramètre local\nà chaque méthode, et non une\nvariable globale.\n"
        },
        {
          "text": "Il s'agit d'un mot-clé réservé sans signification particulière",
          "correct": false,
          "feedback": "Le nom `self` n'est pas un\nmot-clé réservé. C'est un\nparamètre comme un autre,\nutilisé par convention pour\ndésigner l'instance courante.\n"
        },
        {
          "text": "Il fait référence à la classe parente",
          "correct": false,
          "feedback": "Pour faire référence à la\nclasse parente, on utilise la\nfonction `super()`. Le\nparamètre `self` désigne\nl'instance, et non une\nclasse.\n"
        },
        {
          "text": "Il représente l'instance courante sur laquelle la méthode est appelée. Il permet d'accéder aux attributs et aux autres méthodes de l'objet",
          "correct": true,
          "feedback": "Par convention en Python, on\nnomme `self` le premier\nparamètre des méthodes.\nLorsqu'on écrit\n`v.demarrer()`, Python\ntransmet automatiquement `v`\ncomme paramètre `self`.\n"
        }
      ],
      "explanation": "La convention est forte mais non\nobligatoire. Le paramètre\npourrait techniquement s'appeler\nautrement, mais ce serait\nvivement déconseillé. En Java ou\nen C++, le rôle équivalent est\ntenu par `this`, qui est\nimplicite."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "init"
      ],
      "title": "La méthode `__init__`",
      "statement": "Quel est le rôle de la méthode\nspéciale **`__init__`** d'une classe ?",
      "options": [
        {
          "text": "Initialiser les attributs d'un nouvel objet au moment de sa création (rôle de constructeur)",
          "correct": true,
          "feedback": "Cette méthode est appelée\nautomatiquement au moment de\nl'instanciation. C'est dans\nson corps qu'on définit\nl'état initial de l'objet.\n"
        },
        {
          "text": "Détruire l'objet en fin de vie",
          "correct": false,
          "feedback": "La destruction d'un objet est\nplutôt prise en charge par la\nméthode spéciale `__del__`,\nrarement utilisée en Python en\nraison du ramasse-miettes.\n"
        },
        {
          "text": "Initialiser l'ensemble du programme",
          "correct": false,
          "feedback": "La méthode `__init__` est\nlocale à la classe : elle ne\nconcerne que la création des\ninstances de cette classe.\n"
        },
        {
          "text": "Importer un module dans le programme",
          "correct": false,
          "feedback": "L'import de modules se fait\navec l'instruction `import`,\net n'a aucun rapport avec\n`__init__`.\n"
        }
      ],
      "explanation": "Convention : les noms encadrés de\ndoubles soulignés (`__nom__`)\ndésignent des méthodes spéciales\nen Python, comme `__init__`,\n`__str__` ou `__len__`. Parmi\nelles, `__init__` est de loin la\nplus utilisée."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "exemple-classe-simple"
      ],
      "title": "Trace simple",
      "statement": "Que produit ce code ?\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\np = Point(3, 4)\nprint(p.x, p.y)\n```",
      "options": [
        {
          "text": "Il affiche `Point(3, 4)`",
          "correct": false,
          "feedback": "L'instruction `print(p.x,\np.y)` n'affiche pas l'objet\ntout entier, mais\nuniquement la valeur de ses\ndeux attributs.\n"
        },
        {
          "text": "Il affiche `3 4`",
          "correct": true,
          "feedback": "L'expression `Point(3, 4)`\ncrée une instance ; la\nméthode `__init__` initialise\nles attributs `self.x = 3` et\n`self.y = 4`. La fonction\n`print` affiche les deux\nvaleurs séparées par un\nespace.\n"
        },
        {
          "text": "Il affiche `7`",
          "correct": false,
          "feedback": "La fonction `print` n'effectue\npas d'addition entre les\narguments qui lui sont\npassés. Elle se contente de\nles afficher tels quels.\n"
        },
        {
          "text": "Il déclenche une erreur de syntaxe",
          "correct": false,
          "feedback": "Ce code est parfaitement\nvalide. Aucune erreur n'est\nlevée.\n"
        }
      ],
      "explanation": "Voici le motif de base d'une\nclasse simple : une méthode\n`__init__` qui prend les valeurs\ninitiales en paramètres et les\nstocke dans `self` à l'aide\nd'instructions de la forme\n`self.attr = ...`."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "methode-instance"
      ],
      "title": "Définir une méthode",
      "statement": "Comment ajouter une méthode\n`distance_origine` à la classe\n`Point`, qui renvoie la distance\ndu point à l'origine $(0, 0)$ ?",
      "options": [
        {
          "text": "```python\nclass Point:\n    def distance_origine():\n        return ...\n```\n",
          "correct": false,
          "feedback": "Il manque le paramètre\n`self` en première position.\nSans lui, la méthode ne peut\npas accéder aux attributs\nde l'instance.\n"
        },
        {
          "text": "```python\ndef distance_origine(p):\n    return (p.x ** 2 + p.y ** 2) ** 0.5\n```\n",
          "correct": false,
          "feedback": "Cette écriture définit une\nfonction externe, et non une\nméthode de la classe. Elle\nfonctionne, mais elle est\nmoins idiomatique en\nprogrammation orientée\nobjet.\n"
        },
        {
          "text": "```python\nclass Point:\n    ...\n    def distance_origine(self):\n        return (self.x ** 2 + self.y ** 2) ** 0.5\n```\n",
          "correct": true,
          "feedback": "La méthode est définie dans le\ncorps de la classe, avec\n`self` comme premier\nparamètre. On accède aux\nattributs de l'instance par\n`self.x` et `self.y`.\n"
        },
        {
          "text": "```python\nclass Point:\n    distance_origine = (x ** 2 + y ** 2) ** 0.5\n```\n",
          "correct": false,
          "feedback": "Cette écriture est invalide.\nLes noms `x` et `y` ne sont\npas définis au niveau de la\nclasse ; il faut une méthode\nqui s'applique à une\ninstance.\n"
        }
      ],
      "explanation": "L'appel se fait simplement avec\n`p.distance_origine()`. Python\ntransmet automatiquement `p`\ncomme paramètre `self`."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "classe-vs-instance"
      ],
      "title": "Combien d'instances ?",
      "statement": "Combien d'instances peut-on créer à\npartir d'une seule classe ?",
      "options": [
        {
          "text": "Au plus dix instances",
          "correct": false,
          "feedback": "Aucune limite particulière\nn'est imposée par le langage.\nLe nombre d'instances n'est\ncontraint que par la mémoire\ndisponible.\n"
        },
        {
          "text": "Autant d'instances que nécessaire, chacune ayant ses propres valeurs d'attributs",
          "correct": true,
          "feedback": "La classe est un modèle. On\npeut écrire par exemple\n`v1 = Voiture(\"Renault\")`,\n`v2 = Voiture(\"Peugeot\")`, et\nainsi de suite. Chaque\ninstance reste indépendante.\n"
        },
        {
          "text": "Une seule instance par classe",
          "correct": false,
          "feedback": "Cette limite n'existe pas en\nPython. On peut créer autant\nd'instances que nécessaire à\npartir d'une même classe.\n"
        },
        {
          "text": "Aucune instance ne peut être créée",
          "correct": false,
          "feedback": "Une classe sans instance ne\nservirait à rien. Le rôle\nd'une classe est précisément\nde servir de patron pour\ncréer des objets.\n"
        }
      ],
      "explanation": "Une classe sans instance n'a\naucune utilité. On crée autant\nd'instances que la situation\nl'exige."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "exemple-pile"
      ],
      "title": "Constructeur pour une classe Pile",
      "statement": "On souhaite écrire une classe `Pile`\n(de type LIFO) avec les méthodes\n`empiler` et `depiler`. Quel\n`__init__` choisir ?",
      "options": [
        {
          "text": "```python\ndef __init__(self):\n    return []\n```\n",
          "correct": false,
          "feedback": "La méthode `__init__` ne\nrenvoie rien (la valeur\nrenvoyée est implicitement\n`None`). On y initialise\nsimplement les attributs de\nl'instance.\n"
        },
        {
          "text": "```python\ndef __init__():\n    elements = []\n```\n",
          "correct": false,
          "feedback": "Le paramètre `self` manque,\net `elements` n'est ici\nqu'une variable locale, qui\ndisparaîtra à la fin de\n`__init__`. Il faudrait\nécrire `self.elements = []`\npour stocker la liste dans\nl'instance.\n"
        },
        {
          "text": "```python\ndef __init__(self):\n    elements = []\n```\n",
          "correct": false,
          "feedback": "Sans le préfixe `self.`, le\nnom `elements` désigne une\nsimple variable locale, qui\nne persiste pas après la fin\nde `__init__`. Il faut\nécrire `self.elements = []`.\n"
        },
        {
          "text": "```python\ndef __init__(self):\n    self.elements = []\n```\n",
          "correct": true,
          "feedback": "On initialise une liste vide,\nqui contiendra les éléments\nempilés. Les méthodes\n`empiler` et `depiler`\nmanipuleront ensuite\n`self.elements`.\n"
        }
      ],
      "explanation": "Le motif standard est le\nsuivant : `__init__` initialise\nles attributs avec\n`self.nom = valeur`. Sans le\npréfixe `self.`, on ne crée\nqu'une variable locale, qui ne\nsurvit pas à l'appel."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "donnees-comportements"
      ],
      "title": "Données et comportements groupés",
      "statement": "Quel est l'intérêt principal de\nregrouper, dans une même classe,\nles **attributs** (les données) et\nles **méthodes** (les\ncomportements) qui les manipulent ?",
      "options": [
        {
          "text": "Le code est plus court mais devient moins lisible",
          "correct": false,
          "feedback": "C'est en général l'inverse.\nLa lisibilité augmente, car\nles opérations sont\nregroupées au plus près des\ndonnées qu'elles\nmanipulent.\n"
        },
        {
          "text": "Les opérations qui agissent sur une donnée sont placées à côté de cette donnée, ce qui rend le code plus cohérent et plus facile à faire évoluer",
          "correct": true,
          "feedback": "On peut alors lire ou\nmodifier les attributs\nuniquement par les méthodes\nprévues à cet effet. Cela\npermet de changer\nl'implémentation interne\nsans impacter le code qui\nutilise la classe.\n"
        },
        {
          "text": "Les données sont automatiquement enregistrées sur le disque",
          "correct": false,
          "feedback": "La programmation orientée\nobjet n'a aucun lien direct\navec la persistance des\ndonnées sur le disque, qui\nrelève de mécanismes\nséparés.\n"
        },
        {
          "text": "Le programme s'exécute plus rapidement",
          "correct": false,
          "feedback": "La performance n'est pas\nl'avantage principal de la\nprogrammation orientée\nobjet. Son intérêt est avant\ntout structurel.\n"
        }
      ],
      "explanation": "Une classe regroupe l'état\n(les attributs) et les\nopérations (les méthodes) liés\nà un même concept. Dans une\nclasse `Pile`, par exemple, les\nméthodes `empiler` et\n`depiler` manipulent\ndirectement la liste interne.\nL'utilisateur n'a qu'à\nconnaître ces méthodes."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "str-method"
      ],
      "title": "La méthode `__str__`",
      "statement": "À quoi sert la méthode spéciale\n**`__str__`** ?",
      "options": [
        {
          "text": "À supprimer l'objet",
          "correct": false,
          "feedback": "La suppression d'un objet\nrelève plutôt de la méthode\n`__del__`. La méthode\n`__str__` produit une\nchaîne descriptive.\n"
        },
        {
          "text": "À convertir l'objet en entier",
          "correct": false,
          "feedback": "Cette conversion serait\nplutôt assurée par la\nméthode spéciale `__int__`.\nLa méthode `__str__`\nconcerne la représentation\ntextuelle.\n"
        },
        {
          "text": "À déclarer une variable",
          "correct": false,
          "feedback": "La déclaration d'une\nvariable n'a rien à voir\navec une méthode spéciale.\nLa méthode `__str__`\nretourne une chaîne de\ncaractères.\n"
        },
        {
          "text": "À définir la représentation textuelle de l'objet, utilisée par `print(obj)` et `str(obj)`",
          "correct": true,
          "feedback": "Sans `__str__`, l'instruction\n`print(p)` afficherait\nquelque chose comme\n`<__main__.Point object at\n0x...>`. Avec une `__str__`\nbien définie, on peut par\nexemple obtenir\n`Point(3, 4)`.\n"
        }
      ],
      "explanation": "Exemple typique :\n```python\ndef __str__(self):\n    return f\"Point({self.x}, {self.y})\"\n```\nL'instruction `print(Point(3, 4))`\naffichera alors `Point(3, 4)`."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "structure-donnees"
      ],
      "title": "Programmation objet et structures de données",
      "statement": "Pourquoi la programmation orientée\nobjet est-elle particulièrement\nutile pour implémenter des\n**structures de données** comme les\narbres ou les graphes ?",
      "options": [
        {
          "text": "Aucune raison particulière",
          "correct": false,
          "feedback": "La programmation orientée\nobjet apporte au contraire\nde réels bénéfices pour\nl'implémentation des\nstructures de données,\ncomme expliqué dans la\nbonne réponse.\n"
        },
        {
          "text": "Elle permet de regrouper proprement les données (par exemple `valeur`, `gauche`, `droite` pour un nœud d'arbre) et les opérations (parcours, recherche, insertion) en une seule entité",
          "correct": true,
          "feedback": "Cette modélisation est\nnaturelle. Une classe\n`Noeud`, par exemple,\nencapsule l'état et les\ncomportements d'un nœud\nd'arbre. Le code est plus\nclair et plus\nréutilisable.\n"
        },
        {
          "text": "Elle est plus rapide à exécuter",
          "correct": false,
          "feedback": "La programmation orientée\nobjet n'est pas\nsystématiquement plus\nrapide. Son intérêt est\navant tout structurel et\nconceptuel.\n"
        },
        {
          "text": "Elle est obligatoire dans le programme officiel",
          "correct": false,
          "feedback": "Le programme officiel ne\nrend pas la programmation\norientée objet obligatoire\npour l'implémentation de\nces structures. C'est une\noption, parmi d'autres.\n"
        }
      ],
      "explanation": "Le programme officiel de\nTerminale, dans la rubrique\nStructures de données, cite\nexplicitement la\nprogrammation orientée objet\ncomme outil possible pour ces\nimplémentations."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "interface-vs-impl"
      ],
      "title": "Interface et implémentation",
      "statement": "Quelle est la différence entre\nl'**interface** et\nl'**implémentation** d'une\nstructure de données ?",
      "options": [
        {
          "text": "Aucune différence",
          "correct": false,
          "feedback": "La distinction est au\ncontraire fondamentale en\nprogrammation, comme\nexpliqué dans la bonne\nréponse.\n"
        },
        {
          "text": "L'implémentation est aujourd'hui une notion obsolète",
          "correct": false,
          "feedback": "L'implémentation reste une\nnotion absolument\ncentrale. Tout programme\nimplémente concrètement\nune ou plusieurs\ninterfaces.\n"
        },
        {
          "text": "L'interface désigne l'ensemble des opérations disponibles (les signatures des méthodes). L'implémentation désigne la manière dont ces opérations sont effectivement réalisées en code",
          "correct": true,
          "feedback": "On peut, par exemple,\nimplémenter une file (de\ntype FIFO) avec une seule\nliste, ou bien avec deux\npiles. L'interface\n(`enfiler`, `defiler`)\nreste la même, mais les\nimplémentations sont\ndifférentes.\n"
        },
        {
          "text": "L'interface s'exécute plus rapidement que l'implémentation",
          "correct": false,
          "feedback": "Cette comparaison n'a pas\nde sens. L'interface n'est\npas exécutée\nindépendamment ; elle\ndécrit simplement les\nopérations disponibles.\n"
        }
      ],
      "explanation": "Cette distinction est\ncruciale en programmation\nmodulaire. On programme\ncontre l'**interface** ; on\npeut alors changer\nl'implémentation sans\nconséquence pour les\nutilisateurs du module."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "type"
      ],
      "title": "La fonction type appliquée à une instance",
      "statement": "Que renvoie l'appel `type(p)`\nlorsque `p` est une instance de\n`Point` ?",
      "options": [
        {
          "text": "`<class 'Point'>`",
          "correct": true,
          "feedback": "La fonction `type` renvoie\nla classe de l'objet\npassé en paramètre. Elle\npermet de connaître la\nnature exacte d'un objet.\n"
        },
        {
          "text": "`object`",
          "correct": false,
          "feedback": "La classe `object` est la\nclasse ancêtre de toutes\nles classes Python, mais\nce n'est pas la classe\nprécise de `p`.\n"
        },
        {
          "text": "`Point`",
          "correct": false,
          "feedback": "L'appel renvoie bien la\nclasse `Point`, mais sa\nreprésentation textuelle\ndans la console est\n`<class 'Point'>`. Le\ntest `type(p) == Point`\nrenvoie cependant\n`True`.\n"
        },
        {
          "text": "Une exception",
          "correct": false,
          "feedback": "Le code est parfaitement\nvalide et ne lève aucune\nexception.\n"
        }
      ],
      "explanation": "Une variante utile :\n`isinstance(p, Point)`\nrenvoie `True` si `p` est\nune instance de `Point` ou\nd'une éventuelle sous-classe.\nCette forme est plus\nsouple que `type(p) == Point`."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "methode-modifie"
      ],
      "title": "Une méthode qui modifie l'objet",
      "statement": "```python\nclass Compteur:\n    def __init__(self):\n        self.n = 0\n    def incrementer(self):\n        self.n += 1\n\nc = Compteur()\nc.incrementer()\nc.incrementer()\nprint(c.n)\n```\nQue produit ce code ?",
      "options": [
        {
          "text": "Il déclenche une erreur",
          "correct": false,
          "feedback": "Le code est parfaitement\nvalide et s'exécute sans\nerreur.\n"
        },
        {
          "text": "Il affiche `2`",
          "correct": true,
          "feedback": "Le constructeur initialise\nl'attribut `n` à $0$.\nChaque appel à\n`incrementer` ajoute $1$\nà `self.n`. Après deux\nappels, l'attribut vaut\ndonc $2$.\n"
        },
        {
          "text": "Il affiche `0`",
          "correct": false,
          "feedback": "Cette réponse ignore les\ndeux appels successifs à\n`incrementer`, qui\nmodifient bien la valeur\nde l'attribut.\n"
        },
        {
          "text": "Il affiche `1`",
          "correct": false,
          "feedback": "La méthode `incrementer`\nest appelée deux fois\nsuccessives, et non une\nseule.\n"
        }
      ],
      "explanation": "Le **changement d'état** des\nobjets est l'un des piliers\nde la programmation orientée\nobjet. Les méthodes\nmodifient les attributs en\nréaffectant `self.attr`."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "tout-est-objet"
      ],
      "title": "Tout est objet en Python",
      "statement": "Laquelle des affirmations\nsuivantes est correcte au sujet\ndes objets en Python ?",
      "options": [
        {
          "text": "Les fonctions ne sont pas des objets",
          "correct": false,
          "feedback": "Les fonctions Python sont\ndes **objets de première\nclasse** : elles peuvent\nêtre passées en\nparamètre, stockées dans\nune variable, ou\nrenvoyées par d'autres\nfonctions.\n"
        },
        {
          "text": "Seules les classes définies par l'utilisateur produisent des objets",
          "correct": false,
          "feedback": "En Python, les entiers,\nles chaînes et les listes\nsont eux aussi des objets.\nIls sont des instances de\nclasses prédéfinies par\nle langage.\n"
        },
        {
          "text": "En Python, tout est un objet : les entiers, les chaînes, les listes, les fonctions et même les classes sont des instances de classes prédéfinies",
          "correct": true,
          "feedback": "Par exemple, l'appel\n`type(42)` renvoie\n`<class 'int'>`, et\n`type(\"a\")` renvoie\n`<class 'str'>`. Même\nles classes elles-mêmes\nsont des objets, qui sont\ndes instances de la\nmétaclasse `type`.\n"
        },
        {
          "text": "Aucune des affirmations précédentes n'est correcte",
          "correct": false,
          "feedback": "La deuxième proposition\nest précisément correcte,\ncomme expliqué dans la\nbonne réponse.\n"
        }
      ],
      "explanation": "Une conséquence de cette\nuniformité : on peut écrire\n`type(42)` ou même\n`(42).__class__`. Ce modèle\n« tout est objet » donne au\nlangage Python une grande\ncohérence et autorise des\nconstructions puissantes."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "exemple-noeud-arbre"
      ],
      "title": "Une classe Noeud",
      "statement": "Pour représenter un **nœud\nd'arbre binaire** comportant une\nvaleur, un sous-arbre gauche et\nun sous-arbre droit, quelle\ndéfinition de classe est\ncorrecte ?",
      "options": [
        {
          "text": "```python\ndef Noeud(val, gauche, droite):\n    return val\n```\n",
          "correct": false,
          "feedback": "Cette définition est une\nfonction, et non une\nclasse. De plus, elle ne\nrenvoie que la valeur,\nsans construire de\nstructure d'arbre.\n"
        },
        {
          "text": "```python\nclass Noeud:\n    pass\n```\n",
          "correct": false,
          "feedback": "Cette classe est vide :\nelle ne permet pas de\nstocker la valeur, le\nsous-arbre gauche ni le\nsous-arbre droit.\n"
        },
        {
          "text": "```python\nclass Noeud:\n    def __init__(self, val, gauche=None, droite=None):\n        self.val = val\n        self.gauche = gauche\n        self.droite = droite\n```\n",
          "correct": true,
          "feedback": "Cette classe définit trois\nattributs : `val`,\n`gauche` et `droite`. Les\nfils sont par défaut à\n`None` (ce qui correspond\nà une feuille). Les fils\nsont eux-mêmes des\ninstances de `Noeud`, ce\nqui produit une structure\nrécursive.\n"
        },
        {
          "text": "```python\nclass Noeud:\n    val = 0\n    gauche = None\n    droite = None\n```\n",
          "correct": false,
          "feedback": "Ces attributs sont\ndéclarés au niveau de la\nclasse, et seraient donc\n**partagés par toutes les\ninstances**, au lieu\nd'être propres à chacune.\nIl s'agit d'un bug subtil\nmais important.\n"
        }
      ],
      "explanation": "Cette modélisation\nrécursive est typique des\narbres binaires : chaque\nnœud porte une valeur et\npossède au plus deux fils,\nqui sont eux-mêmes des\nnœuds (ou `None` lorsqu'ils\nsont absents)."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "piege-attribut-classe"
      ],
      "title": "Attribut de classe et attribut d'instance",
      "statement": "```python\nclass Voiture:\n    marques = []\n    def __init__(self, marque):\n        Voiture.marques.append(marque)\n\nv1 = Voiture(\"Renault\")\nv2 = Voiture(\"Peugeot\")\nprint(Voiture.marques)\n```\nQue produit ce code ?",
      "options": [
        {
          "text": "Il affiche `['Peugeot']`",
          "correct": false,
          "feedback": "La marque « Renault » est\négalement ajoutée par le\npremier appel à\n`__init__`, et reste donc\ndans la liste.\n"
        },
        {
          "text": "Il affiche `['Renault', 'Peugeot']`",
          "correct": true,
          "feedback": "L'attribut `marques` est\ndéclaré au niveau de la\n**classe**, et non d'une\ninstance : il est donc\npartagé par toutes les\nvoitures. Les ajouts\nsuccessifs s'accumulent\ndans la même liste.\n"
        },
        {
          "text": "Il affiche `[]`",
          "correct": false,
          "feedback": "Cette réponse ignore les\ndeux appels à\n`__init__`, qui ajoutent\nchacun une marque à la\nliste partagée.\n"
        },
        {
          "text": "Il déclenche une erreur",
          "correct": false,
          "feedback": "Le code est parfaitement\nvalide et s'exécute sans\nerreur.\n"
        }
      ],
      "explanation": "Distinction essentielle :\nles attributs de **classe**\nsont partagés entre toutes\nles instances, alors que\nles attributs\nd'**instance** appartiennent\nà un objet particulier. La\nconfusion est fréquente,\nsurtout avec des listes\nmutables placées en\nattribut de classe."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "encapsulation-pratique"
      ],
      "title": "Pratique déconseillée",
      "statement": "Quelle pratique est\n**déconseillée** en programmation\norientée objet ?",
      "options": [
        {
          "text": "Utiliser des méthodes pour modifier les attributs d'un objet",
          "correct": false,
          "feedback": "C'est au contraire une\nbonne pratique. Les\nméthodes encapsulent les\nmodifications et peuvent\ny ajouter des\nvérifications.\n"
        },
        {
          "text": "Définir un constructeur `__init__`",
          "correct": false,
          "feedback": "C'est au contraire une\nbonne pratique standard\nque de définir\nexplicitement les\nattributs d'un objet\ndans `__init__`.\n"
        },
        {
          "text": "Donner un nom en CamelCase à la classe",
          "correct": false,
          "feedback": "Le CamelCase est\nprécisément la\nconvention recommandée\nen Python pour les noms\nde classes.\n"
        },
        {
          "text": "Modifier directement les attributs internes d'un objet depuis l'extérieur, par exemple en écrivant `compte.solde = -1000` au lieu de passer par une méthode comme `compte.deposer(...)`",
          "correct": true,
          "feedback": "Ce contournement\ncourt-circuite les\nvérifications éventuelles\n(par exemple, garantir\nque le solde reste\npositif). On préfère\nexposer des méthodes qui\nmaintiennent les\ninvariants de l'objet.\n"
        }
      ],
      "explanation": "Le principe est le\nsuivant : on interagit avec\nun objet à travers son\n**interface publique**. Les\ndétails internes peuvent\nainsi évoluer sans affecter\nle code utilisateur, tant\nque l'interface reste\nstable."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "comparaison-fonctionnel"
      ],
      "title": "Quand utiliser une classe ?",
      "statement": "Dans quel cas est-il **judicieux**\nd'utiliser une classe plutôt\nqu'une simple fonction ?",
      "options": [
        {
          "text": "Jamais, les classes ne servent à rien en pratique",
          "correct": false,
          "feedback": "Cette position est tout\naussi catégorique. Les\nclasses apportent de\nréels bénéfices dans de\nnombreuses situations,\ncomme expliqué dans la\nbonne réponse.\n"
        },
        {
          "text": "Pour calculer la somme d'une liste de notes",
          "correct": false,
          "feedback": "Une fonction simple suffit\npour ce calcul. Définir\nune classe serait\ndisproportionné.\n"
        },
        {
          "text": "Toujours, car c'est l'approche la plus moderne",
          "correct": false,
          "feedback": "Cette position est trop\ndogmatique. Aucun\nparadigme n'est\nuniversellement\nsupérieur ; le choix\ndépend du problème\ntraité.\n"
        },
        {
          "text": "Pour modéliser une entité avec un état qui change au cours du temps et plusieurs comportements liés : par exemple un compte bancaire, une voiture, ou une structure de données comme une pile",
          "correct": true,
          "feedback": "La programmation orientée\nobjet est particulièrement\nadaptée pour modéliser des\nentités du monde réel, ou\ndes structures de données\ncomplexes. Pour des\ncalculs simples sans\nétat, une fonction est\nplus directe.\n"
        }
      ],
      "explanation": "Une maxime utile : « une\nclasse quand il y a un état\net un ensemble de\ncomportements ; une\nfonction sinon ». Une\nclasse ne contenant qu'une\nseule méthode est souvent\nle signe qu'une simple\nfonction aurait suffi."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "statement": "Parmi les affirmations suivantes\nsur la programmation orientée\nobjet en Python, laquelle est\n**fausse** ?",
      "options": [
        {
          "text": "La méthode `__init__` est appelée automatiquement à la création d'une instance",
          "correct": false,
          "feedback": "Cette affirmation est\ncorrecte. C'est le rôle\nde constructeur joué\npar `__init__`.\n"
        },
        {
          "text": "Le paramètre `self` doit obligatoirement s'appeler `self`",
          "correct": true,
          "feedback": "Cette affirmation est\nfausse (donc c'est la\nbonne réponse). Il\ns'agit d'une **convention\nforte** en Python, mais\npas d'une obligation\nsyntaxique. On pourrait\nécrire `this`, `obj` ou\ntout autre nom, même si\nc'est très vivement\ndéconseillé pour la\nlisibilité du code.\n"
        },
        {
          "text": "En Python, toutes les valeurs (entiers, chaînes, listes, fonctions, classes) sont des objets",
          "correct": false,
          "feedback": "Cette affirmation est\ncorrecte. C'est l'une\ndes caractéristiques\nfondamentales du modèle\nde Python.\n"
        },
        {
          "text": "Une classe est un modèle ; une instance est une réalisation concrète",
          "correct": false,
          "feedback": "Cette affirmation est\ncorrecte. C'est la\ndistinction essentielle\nentre les deux notions.\n"
        }
      ],
      "explanation": "Convention contre règle :\nla syntaxe Python est\nassez permissive, mais les\nconventions (comme le\nstyle PEP $8$ ou les\nidiomes communs) doivent\nêtre respectées pour la\nlisibilité et la\nmaintenabilité du code."
    },
    {
      "id": "q26",
      "difficulty": 3,
      "skills": [
        "methodes-speciales"
      ],
      "title": "Méthodes spéciales",
      "statement": "On définit la classe suivante :\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n    def __eq__(self, autre):\n        return self.x == autre.x and self.y == autre.y\n```\nQuel est l'effet d'avoir défini la\nméthode `__eq__` ?",
      "options": [
        {
          "text": "La classe `Point` devient\nimmuable\n",
          "correct": false,
          "feedback": "Erreur : la définition de\n`__eq__` ne rend pas la classe\nimmuable. Pour cela, il\nfaudrait écrire des méthodes\nspécifiques qui empêchent la\nmodification des attributs\n(par exemple en utilisant des\n*property* en lecture seule).\n"
        },
        {
          "text": "Aucun effet : Python ignore les\nméthodes commençant par un\ndouble soulignement\n",
          "correct": false,
          "feedback": "Erreur : c'est exactement\nl'inverse. Les méthodes encadrées\nde doubles soulignements\n(`__nom__`) sont les **méthodes\nspéciales** que Python appelle\nautomatiquement dans certaines\nsituations (création d'objet,\ncomparaison, affichage, etc.).\n"
        },
        {
          "text": "La comparaison `Point(1, 2) ==\nPoint(1, 2)` renverra `True`\n(deux points avec mêmes\ncoordonnées sont considérés\négaux), au lieu de `False`\ncomme c'était le cas par défaut\n",
          "correct": true,
          "feedback": "Bonne réponse : sans `__eq__`,\nPython compare les objets par\n**identité** (l'opérateur `is`),\ndonc deux instances distinctes\nsont différentes même si elles\nont les mêmes attributs. En\ndéfinissant `__eq__`, on\nsurcharge l'opérateur `==` pour\ncomparer par **valeur**. C'est\nl'une des nombreuses méthodes\nspéciales (avec `__lt__`,\n`__hash__`, `__len__`,\n`__str__`, etc.) qui permettent\nde personnaliser le\ncomportement des objets.\n"
        },
        {
          "text": "Les objets `Point` ne pourront\nplus être créés\n",
          "correct": false,
          "feedback": "Erreur : `__eq__` n'a aucun\neffet sur la création d'objets,\nqui reste régie par\n`__init__`. Elle agit\nuniquement sur la comparaison\nd'égalité.\n"
        }
      ],
      "explanation": "Quelques méthodes spéciales\nutiles : `__init__` (constructeur),\n`__str__` (affichage par\n`print`), `__repr__`\n(représentation détaillée),\n`__eq__` et `__lt__`\n(comparaisons), `__len__`\n(longueur via `len`),\n`__getitem__` (accès indexé via\n`obj[i]`). Elles permettent à\nPython de manipuler les objets\npersonnalisés comme des objets\nnatifs."
    },
    {
      "id": "q27",
      "difficulty": 3,
      "skills": [
        "classe-complete"
      ],
      "title": "Classe Compte bancaire",
      "statement": "On souhaite implémenter une classe\n`Compte` représentant un compte\nbancaire avec un solde, une\nméthode `deposer(montant)` et\nune méthode `retirer(montant)`\nqui refuse l'opération si le\nsolde devient négatif. Quel\ncode est correct ?",
      "options": [
        {
          "text": "```python\nclass Compte:\n    def __init__(self, solde_initial=0):\n        self.solde = solde_initial\n    def deposer(self, montant):\n        if montant > 0:\n            self.solde += montant\n    def retirer(self, montant):\n        if 0 < montant <= self.solde:\n            self.solde -= montant\n            return True\n        return False\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : le constructeur\ninitialise le solde, `deposer`\najoute si le montant est\npositif, `retirer` vérifie que\nle compte ne devient pas\nnégatif et signale l'échec en\nrenvoyant `False`. Les\ncontraintes sont vérifiées\ndans les méthodes, ce qui\ngarantit l'invariant « solde\n≥ 0 » sans avoir à faire\nconfiance au code utilisateur.\n"
        },
        {
          "text": "```python\nclass Compte:\n    def __init__(self):\n        self.solde = 0\n    def __retirer__(self, m):\n        self.solde -= m\n```\n",
          "correct": false,
          "feedback": "Erreur : `__retirer__` n'est\npas une méthode spéciale\nreconnue par Python. Les\ndoubles soulignements sont\nréservés aux méthodes spéciales\ndu langage (`__init__`,\n`__str__`, etc.). Ici, il\nfaut simplement nommer la\nméthode `retirer`. De plus,\nil n'y a pas de méthode\n`deposer` ni de vérification\ndu solde.\n"
        },
        {
          "text": "```python\ndef Compte(solde):\n    return solde\n```\n",
          "correct": false,
          "feedback": "Erreur : ce code définit une\nfonction, pas une classe. Il\nn'y a ni méthode `deposer`,\nni `retirer`, ni encapsulation\nde l'état. C'est juste une\nfonction qui renvoie son\nparamètre.\n"
        },
        {
          "text": "```python\nclass Compte:\n    solde = 0\n    def deposer(montant):\n        solde += montant\n    def retirer(montant):\n        solde -= montant\n```\n",
          "correct": false,
          "feedback": "Plusieurs erreurs ici. (1) Le\nparamètre `self` est absent\ndans les méthodes, donc Python\nappellerait avec un mauvais\nnombre de paramètres. (2)\n`solde` est un attribut de\nclasse partagé entre toutes\nles instances, pas un attribut\nd'instance (bug subtil). (3)\nAucune vérification du\nmontant. (4) Les `solde += ...`\ndans les méthodes\nn'atteindront pas l'attribut\nd'instance sans `self.`.\n"
        }
      ],
      "explanation": "Cet exemple illustre les principes\nessentiels de la programmation\norientée objet : encapsulation\n(état dans `self.solde`),\ninvariants (vérifications dans\nles méthodes), interface publique\nclaire (`deposer` et `retirer`\navec une signification précise),\ngestion des erreurs (renvoyer\n`False` plutôt que de laisser le\nsolde devenir négatif). Une\nversion plus avancée pourrait\nlever une exception explicite\nau lieu de renvoyer `False`."
    }
  ]
}