{
  "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.",
    "parties": [
      {
        "id": "classes",
        "title": "Classes et objets"
      },
      {
        "id": "attributs",
        "title": "Attributs et méthodes"
      },
      {
        "id": "usages",
        "title": "Structures de données objet"
      }
    ],
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition-classe"
      ],
      "title": "Notion de classe",
      "partie": "classes",
      "statement": "En programmation orientée objet, qu'est-ce\nqu'une **classe** ?",
      "options": [
        {
          "text": "Un modèle décrivant attributs et méthodes d'objets",
          "correct": true,
          "feedback": "La classe constitue\nl'abstraction. Les objets\nconcrets sont les **instances**\nde cette classe.\n"
        },
        {
          "text": "Un fichier source Python enregistré avec l'extension .py",
          "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 signalée à l'exécution",
          "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 variable ordinaire contenant une valeur 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.\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",
      "partie": "classes",
      "statement": "Qu'est-ce qu'un **objet** (ou\n**instance**) d'une classe ?",
      "options": [
        {
          "text": "Une réalisation concrète d'une classe",
          "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",
      "partie": "attributs",
      "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 (nom, âge)",
          "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",
      "partie": "attributs",
      "statement": "Qu'est-ce qu'une **méthode** d'une\nclasse ?",
      "options": [
        {
          "text": "Une fonction de la classe agissant sur l'objet",
          "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",
      "partie": "classes",
      "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`, puis le nom de la classe, suivi de deux-points et d'un corps indenté. L'instruction `pass` permet de signaler un corps 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": "Une définition de classe commence par le mot-clé `class`, le nom de la classe et deux-points. Le corps indenté contient ensuite les méthodes, comme `__init__`, ou simplement `pass` s'il est vide."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "instanciation"
      ],
      "title": "Créer une instance",
      "partie": "classes",
      "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",
      "partie": "attributs",
      "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": "Aucune fonction `get` ne lit ainsi un attribut, et `marque` sans guillemets désignerait une variable inexistante. On accède aux attributs d'un objet avec la notation pointée.\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",
      "partie": "attributs",
      "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": "Le paramètre `self` ne désigne pas une classe : il désigne l'objet sur lequel la méthode est appelée.\n"
        },
        {
          "text": "L'instance courante, donnant accès à ses attributs",
          "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__`",
      "partie": "classes",
      "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",
          "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 méthode `__init__` intervient à la création de l'objet, pas à sa disparition : elle fixe les valeurs initiales de ses attributs.\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": "Les noms encadrés de doubles soulignés, comme `__init__`, désignent des méthodes que Python appelle de lui-même : `__init__` est exécutée automatiquement à chaque création d'un objet de la classe."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "classe",
        "init",
        "trace"
      ],
      "title": "Trace simple",
      "partie": "classes",
      "statement": "Que produit ce code ?\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\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 = ...`.",
      "variants": [
        {
          "statement": "Que produit ce code ?\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\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 = ...`."
        },
        {
          "statement": "Que produit ce code ?\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\np = Point(7, 1)\nprint(p.x, p.y)\n```",
          "options": [
            {
              "text": "Il affiche `Point(7, 1)`",
              "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 `7 1`",
              "correct": true,
              "feedback": "L'expression `Point(7, 1)`\ncrée une instance ; la\nméthode `__init__` initialise\nles attributs `self.x = 7` et\n`self.y = 1`. La fonction\n`print` affiche les deux\nvaleurs séparées par un\nespace.\n"
            },
            {
              "text": "Il affiche `8`",
              "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 = ...`."
        },
        {
          "statement": "Que produit ce code ?\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\np = Point(0, 9)\nprint(p.x, p.y)\n```",
          "options": [
            {
              "text": "Il affiche `Point(0, 9)`",
              "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 `0 9`",
              "correct": true,
              "feedback": "L'expression `Point(0, 9)`\ncrée une instance ; la\nméthode `__init__` initialise\nles attributs `self.x = 0` et\n`self.y = 9`. La fonction\n`print` affiche les deux\nvaleurs séparées par un\nespace.\n"
            },
            {
              "text": "Il affiche `9`",
              "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",
      "partie": "attributs",
      "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 ?",
      "partie": "classes",
      "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 que nécessaire, chacune indépendante",
          "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",
      "partie": "usages",
      "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",
      "partie": "attributs",
      "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 sont au plus près des données",
          "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": [
        "methode",
        "parametre-objet"
      ],
      "title": "Une méthode qui reçoit un autre objet",
      "partie": "attributs",
      "statement": "On considère le programme suivant :\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n    def distance(self, autre):\n        return abs(self.x - autre.x) + abs(self.y - autre.y)\np = Point(1, 2)\nq = Point(4, 6)\nprint(p.distance(q))\n```\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`7`",
          "correct": true,
          "feedback": "Dans l'appel `p.distance(q)`, `self` désigne `p` et `autre` désigne `q`. La méthode calcule $|1 - 4| + |2 - 6| = 3 + 4 = 7$.\n"
        },
        {
          "text": "`5`",
          "correct": false,
          "feedback": "$5$ serait la distance à vol d'oiseau, $\\sqrt{3^2 + 4^2}$. La méthode écrite ici additionne simplement les deux écarts, $3 + 4$.\n"
        },
        {
          "text": "Une erreur, car `distance` attend deux arguments et n'en reçoit qu'un",
          "correct": false,
          "feedback": "Le paramètre `self` n'est pas à fournir : Python lui donne automatiquement l'objet `p`. Le seul argument écrit, `q`, correspond au paramètre `autre`.\n"
        },
        {
          "text": "Une erreur, car une méthode ne peut lire que les attributs de `self`",
          "correct": false,
          "feedback": "Une méthode peut lire les attributs de tout objet qu'elle reçoit : `autre.x` désigne l'attribut `x` de `q`, accessible par la notation pointée.\n"
        }
      ],
      "explanation": "Une méthode peut prendre en paramètre un autre objet de la même classe. Dans l'appel `p.distance(q)`, l'objet placé avant le point devient `self`, et l'argument entre parenthèses devient `autre`.",
      "variants": [
        {
          "statement": "On considère le programme suivant :\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n    def distance(self, autre):\n        return abs(self.x - autre.x) + abs(self.y - autre.y)\np = Point(1, 2)\nq = Point(4, 6)\nprint(p.distance(q))\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`7`",
              "correct": true,
              "feedback": "Dans l'appel `p.distance(q)`, `self` désigne `p` et `autre` désigne `q`. La méthode calcule $|1 - 4| + |2 - 6| = 3 + 4 = 7$.\n"
            },
            {
              "text": "`5`",
              "correct": false,
              "feedback": "$5$ serait la distance à vol d'oiseau, $\\sqrt{3^2 + 4^2}$. La méthode écrite ici additionne simplement les deux écarts, $3 + 4$.\n"
            },
            {
              "text": "Une erreur, car `distance` attend deux arguments et n'en reçoit qu'un",
              "correct": false,
              "feedback": "Le paramètre `self` n'est pas à fournir : Python lui donne automatiquement l'objet `p`. Le seul argument écrit, `q`, correspond au paramètre `autre`.\n"
            },
            {
              "text": "Une erreur, car une méthode ne peut lire que les attributs de `self`",
              "correct": false,
              "feedback": "Une méthode peut lire les attributs de tout objet qu'elle reçoit : `autre.x` désigne l'attribut `x` de `q`, accessible par la notation pointée.\n"
            }
          ],
          "explanation": "Une méthode peut prendre en paramètre un autre objet de la même classe. Dans l'appel `p.distance(q)`, l'objet placé avant le point devient `self`, et l'argument entre parenthèses devient `autre`."
        },
        {
          "statement": "On considère le programme suivant :\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n    def distance(self, autre):\n        return abs(self.x - autre.x) + abs(self.y - autre.y)\np = Point(2, 1)\nq = Point(8, 9)\nprint(p.distance(q))\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`14`",
              "correct": true,
              "feedback": "Dans l'appel `p.distance(q)`, `self` désigne `p` et `autre` désigne `q`. La méthode calcule $|2 - 8| + |1 - 9| = 6 + 8 = 14$.\n"
            },
            {
              "text": "`10`",
              "correct": false,
              "feedback": "$10$ serait la distance à vol d'oiseau, $\\sqrt{6^2 + 8^2}$. La méthode écrite ici additionne simplement les deux écarts, $6 + 8$.\n"
            },
            {
              "text": "Une erreur, car `distance` attend deux arguments et n'en reçoit qu'un",
              "correct": false,
              "feedback": "Le paramètre `self` n'est pas à fournir : Python lui donne automatiquement l'objet `p`. Le seul argument écrit, `q`, correspond au paramètre `autre`.\n"
            },
            {
              "text": "Une erreur, car une méthode ne peut lire que les attributs de `self`",
              "correct": false,
              "feedback": "Une méthode peut lire les attributs de tout objet qu'elle reçoit : `autre.x` désigne l'attribut `x` de `q`, accessible par la notation pointée.\n"
            }
          ],
          "explanation": "Une méthode peut prendre en paramètre un autre objet de la même classe. Dans l'appel `p.distance(q)`, l'objet placé avant le point devient `self`, et l'argument entre parenthèses devient `autre`."
        },
        {
          "statement": "On considère le programme suivant :\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n    def distance(self, autre):\n        return abs(self.x - autre.x) + abs(self.y - autre.y)\np = Point(0, 0)\nq = Point(5, 12)\nprint(p.distance(q))\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`17`",
              "correct": true,
              "feedback": "Dans l'appel `p.distance(q)`, `self` désigne `p` et `autre` désigne `q`. La méthode calcule $|0 - 5| + |0 - 12| = 5 + 12 = 17$.\n"
            },
            {
              "text": "`13`",
              "correct": false,
              "feedback": "$13$ serait la distance à vol d'oiseau, $\\sqrt{5^2 + 12^2}$. La méthode écrite ici additionne simplement les deux écarts, $5 + 12$.\n"
            },
            {
              "text": "Une erreur, car `distance` attend deux arguments et n'en reçoit qu'un",
              "correct": false,
              "feedback": "Le paramètre `self` n'est pas à fournir : Python lui donne automatiquement l'objet `p`. Le seul argument écrit, `q`, correspond au paramètre `autre`.\n"
            },
            {
              "text": "Une erreur, car une méthode ne peut lire que les attributs de `self`",
              "correct": false,
              "feedback": "Une méthode peut lire les attributs de tout objet qu'elle reçoit : `autre.x` désigne l'attribut `x` de `q`, accessible par la notation pointée.\n"
            }
          ],
          "explanation": "Une méthode peut prendre en paramètre un autre objet de la même classe. Dans l'appel `p.distance(q)`, l'objet placé avant le point devient `self`, et l'argument entre parenthèses devient `autre`."
        }
      ]
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "structure-donnees"
      ],
      "title": "Programmation objet et structures de données",
      "partie": "usages",
      "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": "Regrouper données et opérations 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 strictement 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",
      "partie": "usages",
      "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 liste les opérations, l'implémentation les 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",
      "partie": "classes",
      "statement": "Que renvoie l'appel `type(p)`\nlorsque `p` est une instance de\n`Point` ?",
      "options": [
        {
          "text": "La classe `Point`",
          "correct": true,
          "feedback": "La fonction `type` renvoie la classe de l'objet passé en paramètre, ici `Point` : le test `type(p) == Point` vaut `True`.\n"
        },
        {
          "text": "La classe `object`",
          "correct": false,
          "feedback": "`type` ne renvoie pas une classe générale : il renvoie la classe précise à partir de laquelle l'objet a été créé, ici `Point`.\n"
        },
        {
          "text": "La chaîne `\"p\"`, nom de la variable",
          "correct": false,
          "feedback": "`type` ne s'intéresse pas au nom de la variable : il renvoie la classe de l'objet que cette variable désigne.\n"
        },
        {
          "text": "Une erreur, car `type` ne s'applique qu'aux entiers, flottants et chaînes",
          "correct": false,
          "feedback": "`type` s'applique à toute valeur, y compris aux objets créés à partir d'une classe écrite par le programmeur. Le code ne provoque aucune erreur.\n"
        }
      ],
      "explanation": "La fonction `type` permet de vérifier la classe d'un objet : `type(42) == int` vaut `True`, et de même `type(p) == Point` vaut `True` lorsque `p` a été créé par `Point(...)`."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "methode",
        "etat"
      ],
      "title": "Une méthode qui modifie l'objet",
      "partie": "attributs",
      "statement": "```python\nclass Compteur:\n    def __init__(self):\n        self.n = 0\n    def incrementer(self):\n        self.n += 1\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, pas 1.\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`.",
      "variants": [
        {
          "statement": "```python\nclass Compteur:\n    def __init__(self):\n        self.n = 0\n    def incrementer(self):\n        self.n += 1\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, pas 1.\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`."
        },
        {
          "statement": "```python\nclass Compteur:\n    def __init__(self):\n        self.n = 0\n    def incrementer(self):\n        self.n += 1\nc = Compteur()\nc.incrementer()\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 `3`",
              "correct": true,
              "feedback": "Le constructeur initialise\nl'attribut `n` à $0$.\nChaque appel à\n`incrementer` ajoute $1$\nà `self.n`. Après trois\nappels, l'attribut vaut\ndonc $3$.\n"
            },
            {
              "text": "Il affiche `0`",
              "correct": false,
              "feedback": "Cette réponse ignore les\ntrois appels successifs à\n`incrementer`, qui\nmodifient bien la valeur\nde l'attribut.\n"
            },
            {
              "text": "Il affiche `2`",
              "correct": false,
              "feedback": "La méthode `incrementer`\nest appelée trois fois\nsuccessives, pas 2.\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`."
        },
        {
          "statement": "```python\nclass Compteur:\n    def __init__(self):\n        self.n = 0\n    def incrementer(self):\n        self.n += 1\nc = Compteur()\nc.incrementer()\nc.incrementer()\nc.incrementer()\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 `5`",
              "correct": true,
              "feedback": "Le constructeur initialise\nl'attribut `n` à $0$.\nChaque appel à\n`incrementer` ajoute $1$\nà `self.n`. Après cinq\nappels, l'attribut vaut\ndonc $5$.\n"
            },
            {
              "text": "Il affiche `0`",
              "correct": false,
              "feedback": "Cette réponse ignore les\ncinq appels successifs à\n`incrementer`, qui\nmodifient bien la valeur\nde l'attribut.\n"
            },
            {
              "text": "Il affiche `4`",
              "correct": false,
              "feedback": "La méthode `incrementer`\nest appelée cinq fois\nsuccessives, pas 4.\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",
      "partie": "classes",
      "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 elles aussi des objets : on peut par exemple les ranger dans une variable ou en donner une en argument à `sorted` pour choisir la clé de tri.\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, même les classes",
          "correct": true,
          "feedback": "Par exemple, l'appel `type(42)` renvoie `<class 'int'>`, et `type(\"a\")` renvoie `<class 'str'>`. Les entiers, les chaînes, les fonctions et même les classes sont des objets.\n"
        },
        {
          "text": "Aucune de ces affirmations n'est correcte",
          "correct": false,
          "feedback": "L'affirmation « en Python, tout est un objet, même les classes » est correcte : entiers, chaînes, listes, fonctions et classes sont tous des objets.\n"
        }
      ],
      "explanation": "Une conséquence de cette uniformité : la fonction `type` s'applique à toute valeur, qu'il s'agisse d'un entier, d'une chaîne ou d'un objet créé à partir d'une classe écrite par le programmeur. Ce modèle donne au langage Python une grande cohérence."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "exemple-noeud-arbre"
      ],
      "title": "Une classe Noeud",
      "partie": "usages",
      "statement": "Pour représenter un **nœud d'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=None, droite=None):\n    return {\"val\": val, \"gauche\": gauche, \"droite\": droite}\n```\n",
          "correct": false,
          "feedback": "Cette définition est une\nfonction, et non une\nclasse : aucun type `Noeud`\nn'est créé. Le dictionnaire\nrenvoyé s'utilise avec\n`n[\"val\"]` au lieu de\n`n.val` et n'a aucune\nméthode propre aux nœuds.\n"
        },
        {
          "text": "```python\nclass Noeud:\n    def __init__(self, val, gauche=None, droite=None):\n        val = val\n        gauche = gauche\n        droite = droite\n```\n",
          "correct": false,
          "feedback": "Le constructeur affecte des\nvariables locales, qui\ndisparaissent dès la fin de\n`__init__`. Sans écrire\n`self.val = val`, l'objet\ncréé ne possède aucun\nattribut : `n.val` provoque\nune `AttributeError`.\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 instances**, 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": "q23",
      "difficulty": 3,
      "skills": [
        "objet",
        "affectation",
        "trace"
      ],
      "title": "Affectation d'un objet à une autre variable",
      "partie": "classes",
      "statement": "On considère le programme suivant :\n\n```python\nclass Compteur:\n    def __init__(self):\n        self.valeur = 0\n\n    def incrementer(self):\n        self.valeur = self.valeur + 1\n\na = Compteur()\nb = a\nc = Compteur()\na.incrementer()\nb.incrementer()\nc.incrementer()\nprint(a.valeur, b.valeur, c.valeur)\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`1 1 1`",
          "correct": false,
          "feedback": "L'affectation `b = a` ne copie pas l'objet : `a` et `b` désignent le même compteur. Il est donc incrémenté deux fois, par `a` puis par `b`.\n"
        },
        {
          "text": "`2 2 2`",
          "correct": false,
          "feedback": "Chaque appel à `Compteur()` crée un nouvel objet : `c` est un compteur distinct, incrémenté une seule fois. Seuls `a` et `b` partagent le même objet.\n"
        },
        {
          "text": "Une erreur, car `b` n'a pas été créé par `Compteur()`",
          "correct": false,
          "feedback": "La variable `b` désigne l'objet créé pour `a` ; on peut appeler ses méthodes exactement comme avec `a`. Le programme s'exécute sans erreur.\n"
        },
        {
          "text": "`2 2 1`",
          "correct": true,
          "feedback": "`a` et `b` désignent le même objet, incrémenté deux fois : sa valeur est $2$, lue par `a.valeur` comme par `b.valeur`. `c` est un autre objet, incrémenté une fois.\n"
        }
      ],
      "explanation": "Seul l'appel `Compteur()` crée un objet. L'affectation `b = a` donne simplement un second nom au même objet : toute modification faite par l'un des noms est visible par l'autre."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "comparaison-fonctionnel"
      ],
      "title": "Quand utiliser une classe ?",
      "partie": "usages",
      "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 simple 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": "Modéliser une entité avec état et comportements",
          "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",
      "partie": "usages",
      "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 forte** 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 : la syntaxe Python est assez permissive, mais les conventions partagées, comme le nom `self`, doivent être respectées pour la lisibilité du code."
    },
    {
      "id": "q27",
      "difficulty": 3,
      "skills": [
        "classe-complete"
      ],
      "title": "Classe Compte bancaire",
      "partie": "classes",
      "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\nclass Compte:\n    def __init__(self, solde_initial=0):\n        self.solde = solde_initial\n    def deposer(self, montant):\n        self.solde += montant\n    def retirer(self, montant):\n        self.solde -= montant\n```\n",
          "correct": false,
          "feedback": "Erreur : ce code s'exécute,\nmais `retirer` ne vérifie\nrien : le solde peut devenir\nnégatif, ce que la\nspécification interdit\nexplicitement. Il manque\naussi le contrôle de\npositivité des montants dans\n`deposer`.\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`."
    },
    {
      "id": "q28",
      "difficulty": 2,
      "skills": [
        "usages",
        "file",
        "methode",
        "code"
      ],
      "title": "La méthode de défilement d'une classe File",
      "partie": "usages",
      "statement": "La classe `File` range ses éléments dans la liste `self.elements`,\nles nouveaux étant ajoutés en fin de liste par `append`. Quelle\nligne complète la méthode `defiler`, qui retire et renvoie le plus\nancien élément ?",
      "options": [
        {
          "text": "`return self.elements.pop(0)`",
          "correct": true,
          "feedback": "Bonne réponse : le plus ancien est en tête de liste, à l'indice\n$0$ ; `pop(0)` le retire et le renvoie.\n"
        },
        {
          "text": "`return self.elements.pop()`",
          "correct": false,
          "feedback": "Erreur : sans argument, `pop` retire le dernier arrivé ; ce\nserait une pile.\n"
        },
        {
          "text": "`return self.elements[0]`",
          "correct": false,
          "feedback": "Erreur : l'élément est lu mais reste dans la file.\n"
        },
        {
          "text": "`return self.elements.remove(0)`",
          "correct": false,
          "feedback": "Erreur : `remove(0)` cherche la valeur $0$, pas l'indice, et ne\nrenvoie rien.\n"
        }
      ],
      "explanation": "Écrire une structure de données en classe, c'est traduire chaque\nopération de l'interface en une méthode sur l'attribut interne."
    },
    {
      "id": "q29",
      "difficulty": 2,
      "skills": [
        "usages",
        "pile",
        "est-vide",
        "booleen"
      ],
      "title": "Une méthode qui teste si la pile est vide",
      "partie": "usages",
      "statement": "Quelle ligne complète correctement la méthode `est_vide` d'une\nclasse `Pile` dont les éléments sont dans la liste\n`self.elements` ?",
      "options": [
        {
          "text": "`return len(self.elements) == 0`",
          "correct": true,
          "feedback": "Bonne réponse : la comparaison renvoie un booléen, `True` quand\nla liste est vide.\n"
        },
        {
          "text": "`return self.elements == None`",
          "correct": false,
          "feedback": "Erreur : une liste vide n'est pas `None` ; ce test répond\ntoujours `False`.\n"
        },
        {
          "text": "`return len(self.elements)`",
          "correct": false,
          "feedback": "Erreur : la méthode renverrait un entier, nul précisément quand\nla pile est vide, soit l'inverse d'un `True`.\n"
        },
        {
          "text": "`return self.elements.pop() is None`",
          "correct": false,
          "feedback": "Erreur : tester ne doit rien retirer, et `pop` sur une liste\nvide provoque une erreur.\n"
        }
      ],
      "explanation": "Une méthode de test renvoie un booléen et ne modifie pas l'objet."
    },
    {
      "id": "q30",
      "difficulty": 3,
      "skills": [
        "usages",
        "fraction",
        "methode",
        "nouvel-objet",
        "trace"
      ],
      "title": "Une méthode qui renvoie un nouvel objet",
      "partie": "usages",
      "statement": "Que renvoie `a.plus(b)` ?\n```python\nclass Rationnel:\n    def __init__(self, n, d):\n        self.n = n\n        self.d = d\n\n    def plus(self, autre):\n        return Rationnel(self.n * autre.d + autre.n * self.d,\n                        self.d * autre.d)\n\na = Rationnel(1, 2)\nb = Rationnel(1, 3)\n```",
      "options": [
        {
          "text": "`Rationnel(5, 6)`, un nouvel objet, `a` et `b` restant intacts",
          "correct": true,
          "feedback": "Bonne réponse : la méthode applique la règle d'addition des\nfractions, $1 \\times 3 + 1 \\times 2 = 5$ sur\n$2 \\times 3 = 6$, sans simplifier, et construit un\ntroisième objet.\n"
        },
        {
          "text": "`Rationnel(2, 5)`, les numérateurs et dénominateurs s'ajoutant",
          "correct": false,
          "feedback": "Erreur : ce n'est pas ce que calcule le code, ni la règle des\nfractions.\n"
        },
        {
          "text": "`Rationnel(1, 6)`, le produit des deux fractions",
          "correct": false,
          "feedback": "Erreur : le numérateur calculé est une somme de deux produits,\npas un seul produit.\n"
        },
        {
          "text": "Une erreur, une méthode ne pouvant créer un objet de sa propre classe",
          "correct": false,
          "feedback": "Erreur : une méthode peut appeler `Rationnel(...)` comme\nn'importe quel code.\n"
        }
      ],
      "explanation": "Une méthode qui renvoie un nouvel objet au lieu de modifier `self`\nlaisse les opérandes inchangés, comme les nombres.",
      "variants": [
        {
          "statement": "Que renvoie `a.plus(b)` ?\n```python\nclass Rationnel:\n    def __init__(self, n, d):\n        self.n = n\n        self.d = d\n\n    def plus(self, autre):\n        return Rationnel(self.n * autre.d + autre.n * self.d,\n                        self.d * autre.d)\n\na = Rationnel(1, 2)\nb = Rationnel(1, 3)\n```",
          "options": [
            {
              "text": "`Rationnel(5, 6)`, un nouvel objet, `a` et `b` restant intacts",
              "correct": true,
              "feedback": "Bonne réponse : la méthode applique la règle d'addition des\nfractions, $1 \\times 3 + 1 \\times 2 = 5$ sur\n$2 \\times 3 = 6$, sans simplifier, et construit un\ntroisième objet.\n"
            },
            {
              "text": "`Rationnel(2, 5)`, les numérateurs et dénominateurs s'ajoutant",
              "correct": false,
              "feedback": "Erreur : ce n'est pas ce que calcule le code, ni la règle des\nfractions.\n"
            },
            {
              "text": "`Rationnel(1, 6)`, le produit des deux fractions",
              "correct": false,
              "feedback": "Erreur : le numérateur calculé est une somme de deux produits,\npas un seul produit.\n"
            },
            {
              "text": "Une erreur, une méthode ne pouvant créer un objet de sa propre classe",
              "correct": false,
              "feedback": "Erreur : une méthode peut appeler `Rationnel(...)` comme\nn'importe quel code.\n"
            }
          ],
          "explanation": "Une méthode qui renvoie un nouvel objet au lieu de modifier `self`\nlaisse les opérandes inchangés, comme les nombres."
        },
        {
          "statement": "Que renvoie `a.plus(b)` ?\n```python\nclass Rationnel:\n    def __init__(self, n, d):\n        self.n = n\n        self.d = d\n\n    def plus(self, autre):\n        return Rationnel(self.n * autre.d + autre.n * self.d,\n                        self.d * autre.d)\n\na = Rationnel(1, 4)\nb = Rationnel(1, 4)\n```",
          "options": [
            {
              "text": "`Rationnel(8, 16)`, un nouvel objet, `a` et `b` restant intacts",
              "correct": true,
              "feedback": "Bonne réponse : la méthode applique la règle d'addition des\nfractions, $1 \\times 4 + 1 \\times 4 = 8$ sur\n$4 \\times 4 = 16$, sans simplifier, et construit un\ntroisième objet.\n"
            },
            {
              "text": "`Rationnel(2, 8)`, les numérateurs et dénominateurs s'ajoutant",
              "correct": false,
              "feedback": "Erreur : ce n'est pas ce que calcule le code, ni la règle des\nfractions.\n"
            },
            {
              "text": "`Rationnel(1, 16)`, le produit des deux fractions",
              "correct": false,
              "feedback": "Erreur : le numérateur calculé est une somme de deux produits,\npas un seul produit.\n"
            },
            {
              "text": "Une erreur, une méthode ne pouvant créer un objet de sa propre classe",
              "correct": false,
              "feedback": "Erreur : une méthode peut appeler `Rationnel(...)` comme\nn'importe quel code.\n"
            }
          ],
          "explanation": "Une méthode qui renvoie un nouvel objet au lieu de modifier `self`\nlaisse les opérandes inchangés, comme les nombres."
        },
        {
          "statement": "Que renvoie `a.plus(b)` ?\n```python\nclass Rationnel:\n    def __init__(self, n, d):\n        self.n = n\n        self.d = d\n\n    def plus(self, autre):\n        return Rationnel(self.n * autre.d + autre.n * self.d,\n                        self.d * autre.d)\n\na = Rationnel(2, 3)\nb = Rationnel(1, 6)\n```",
          "options": [
            {
              "text": "`Rationnel(15, 18)`, un nouvel objet, `a` et `b` restant intacts",
              "correct": true,
              "feedback": "Bonne réponse : la méthode applique la règle d'addition des\nfractions, $2 \\times 6 + 1 \\times 3 = 15$ sur\n$3 \\times 6 = 18$, sans simplifier, et construit un\ntroisième objet.\n"
            },
            {
              "text": "`Rationnel(3, 9)`, les numérateurs et dénominateurs s'ajoutant",
              "correct": false,
              "feedback": "Erreur : ce n'est pas ce que calcule le code, ni la règle des\nfractions.\n"
            },
            {
              "text": "`Rationnel(2, 18)`, le produit des deux fractions",
              "correct": false,
              "feedback": "Erreur : le numérateur calculé est une somme de deux produits,\npas un seul produit.\n"
            },
            {
              "text": "Une erreur, une méthode ne pouvant créer un objet de sa propre classe",
              "correct": false,
              "feedback": "Erreur : une méthode peut appeler `Rationnel(...)` comme\nn'importe quel code.\n"
            }
          ],
          "explanation": "Une méthode qui renvoie un nouvel objet au lieu de modifier `self`\nlaisse les opérandes inchangés, comme les nombres."
        }
      ]
    },
    {
      "id": "q31",
      "difficulty": 2,
      "skills": [
        "usages",
        "liste-d-objets",
        "acces",
        "trace"
      ],
      "title": "Une liste d'objets",
      "partie": "usages",
      "statement": "Qu'affiche ce programme ?\n```python\nclass Eleve:\n    def __init__(self, nom, note):\n        self.nom = nom\n        self.note = note\n\neleves = [Eleve(\"Ana\", 15), Eleve(\"Bob\", 12)]\nprint(eleves[1].note)\n```",
      "options": [
        {
          "text": "`12`, la note du second élève de la liste",
          "correct": true,
          "feedback": "Bonne réponse : `eleves[1]` est le second objet, et `.note` lit\nson attribut.\n"
        },
        {
          "text": "`15`, la note du premier élève de la liste",
          "correct": false,
          "feedback": "Erreur : les indices commencent à $0$, `eleves[1]` est le\nsecond.\n"
        },
        {
          "text": "`Bob`, le nom du second élève",
          "correct": false,
          "feedback": "Erreur : c'est l'attribut `note` qui est affiché, pas `nom`.\n"
        },
        {
          "text": "Une erreur, une liste ne pouvant contenir des objets",
          "correct": false,
          "feedback": "Erreur : une liste contient des valeurs de tout type, objets\ncompris.\n"
        }
      ],
      "explanation": "Les objets se rangent dans les listes et les dictionnaires comme\ntoute autre valeur ; on accède à leurs attributs après l'indexation.",
      "variants": [
        {
          "statement": "Qu'affiche ce programme ?\n```python\nclass Eleve:\n    def __init__(self, nom, note):\n        self.nom = nom\n        self.note = note\n\neleves = [Eleve(\"Ana\", 15), Eleve(\"Bob\", 12)]\nprint(eleves[1].note)\n```",
          "options": [
            {
              "text": "`12`, la note du second élève de la liste",
              "correct": true,
              "feedback": "Bonne réponse : `eleves[1]` est le second objet, et `.note` lit\nson attribut.\n"
            },
            {
              "text": "`15`, la note du premier élève de la liste",
              "correct": false,
              "feedback": "Erreur : les indices commencent à $0$, `eleves[1]` est le\nsecond.\n"
            },
            {
              "text": "`Bob`, le nom du second élève",
              "correct": false,
              "feedback": "Erreur : c'est l'attribut `note` qui est affiché, pas `nom`.\n"
            },
            {
              "text": "Une erreur, une liste ne pouvant contenir des objets",
              "correct": false,
              "feedback": "Erreur : une liste contient des valeurs de tout type, objets\ncompris.\n"
            }
          ],
          "explanation": "Les objets se rangent dans les listes et les dictionnaires comme\ntoute autre valeur ; on accède à leurs attributs après l'indexation."
        },
        {
          "statement": "Qu'affiche ce programme ?\n```python\nclass Eleve:\n    def __init__(self, nom, note):\n        self.nom = nom\n        self.note = note\n\neleves = [Eleve(\"Lou\", 18), Eleve(\"Max\", 9)]\nprint(eleves[1].note)\n```",
          "options": [
            {
              "text": "`9`, la note du second élève de la liste",
              "correct": true,
              "feedback": "Bonne réponse : `eleves[1]` est le second objet, et `.note` lit\nson attribut.\n"
            },
            {
              "text": "`18`, la note du premier élève de la liste",
              "correct": false,
              "feedback": "Erreur : les indices commencent à $0$, `eleves[1]` est le\nsecond.\n"
            },
            {
              "text": "`Max`, le nom du second élève",
              "correct": false,
              "feedback": "Erreur : c'est l'attribut `note` qui est affiché, pas `nom`.\n"
            },
            {
              "text": "Une erreur, une liste ne pouvant contenir des objets",
              "correct": false,
              "feedback": "Erreur : une liste contient des valeurs de tout type, objets\ncompris.\n"
            }
          ],
          "explanation": "Les objets se rangent dans les listes et les dictionnaires comme\ntoute autre valeur ; on accède à leurs attributs après l'indexation."
        },
        {
          "statement": "Qu'affiche ce programme ?\n```python\nclass Eleve:\n    def __init__(self, nom, note):\n        self.nom = nom\n        self.note = note\n\neleves = [Eleve(\"Eva\", 11), Eleve(\"Tom\", 14)]\nprint(eleves[1].note)\n```",
          "options": [
            {
              "text": "`14`, la note du second élève de la liste",
              "correct": true,
              "feedback": "Bonne réponse : `eleves[1]` est le second objet, et `.note` lit\nson attribut.\n"
            },
            {
              "text": "`11`, la note du premier élève de la liste",
              "correct": false,
              "feedback": "Erreur : les indices commencent à $0$, `eleves[1]` est le\nsecond.\n"
            },
            {
              "text": "`Tom`, le nom du second élève",
              "correct": false,
              "feedback": "Erreur : c'est l'attribut `note` qui est affiché, pas `nom`.\n"
            },
            {
              "text": "Une erreur, une liste ne pouvant contenir des objets",
              "correct": false,
              "feedback": "Erreur : une liste contient des valeurs de tout type, objets\ncompris.\n"
            }
          ],
          "explanation": "Les objets se rangent dans les listes et les dictionnaires comme\ntoute autre valeur ; on accède à leurs attributs après l'indexation."
        }
      ]
    },
    {
      "id": "q32",
      "difficulty": 3,
      "skills": [
        "usages",
        "instances",
        "attribut-liste",
        "independance"
      ],
      "title": "Chaque instance a sa propre liste",
      "partie": "usages",
      "statement": "Qu'affiche ce programme ?\n```python\nclass Panier:\n    def __init__(self):\n        self.articles = []\n\np1 = Panier()\np2 = Panier()\np1.articles.append(\"pain\")\nprint(len(p2.articles))\n```",
      "options": [
        {
          "text": "`0`, chaque appel de `__init__` créant une liste distincte",
          "correct": true,
          "feedback": "Bonne réponse : `[]` est évalué à chaque construction ; `p1` et\n`p2` ont chacun leur liste, et seule celle de `p1` a reçu un\narticle.\n"
        },
        {
          "text": "`1`, la liste étant partagée par toutes les instances",
          "correct": false,
          "feedback": "Erreur : la liste n'est pas définie dans la classe mais créée\ndans `__init__`, une fois par objet.\n"
        },
        {
          "text": "Une erreur, `p2.articles` n'existant pas encore",
          "correct": false,
          "feedback": "Erreur : `__init__` a créé l'attribut de `p2` à sa\nconstruction.\n"
        },
        {
          "text": "`None`, la liste de `p2` n'ayant jamais été remplie",
          "correct": false,
          "feedback": "Erreur : une liste vide a une longueur, $0$.\n"
        }
      ],
      "explanation": "Les attributs créés dans `__init__` appartiennent à chaque objet :\ndeux instances ne partagent rien par défaut."
    },
    {
      "id": "q33",
      "difficulty": 1,
      "skills": [
        "attributs",
        "modification",
        "acces"
      ],
      "title": "Modifier un attribut depuis l'extérieur de la classe",
      "partie": "attributs",
      "statement": "`v` est un objet de la classe `Voiture`, qui possède un attribut\n`vitesse`. Que fait l'instruction `v.vitesse = 50` ?",
      "options": [
        {
          "text": "Elle donne la valeur `50` à l'attribut `vitesse` de l'objet `v`",
          "correct": true,
          "feedback": "Bonne réponse : un attribut se lit et se modifie par la\nnotation pointée, depuis la classe ou depuis l'extérieur.\n"
        },
        {
          "text": "Elle crée une variable globale `vitesse` valant `50`",
          "correct": false,
          "feedback": "Erreur : le préfixe `v.` désigne l'attribut de l'objet, pas une\nvariable.\n"
        },
        {
          "text": "Elle provoque une erreur, un attribut ne se modifiant que par une méthode",
          "correct": false,
          "feedback": "Erreur : Python n'impose pas de passer par une méthode, même si\nc'est souvent préférable.\n"
        },
        {
          "text": "Elle donne la valeur `50` à la vitesse de toutes les voitures",
          "correct": false,
          "feedback": "Erreur : seul l'objet `v` est concerné.\n"
        }
      ],
      "explanation": "Un attribut est une variable attachée à un objet : la notation\n`objet.attribut` y accède en lecture comme en écriture."
    },
    {
      "id": "q34",
      "difficulty": 2,
      "skills": [
        "attributs",
        "self",
        "oubli",
        "attributeerror"
      ],
      "title": "Un `self` oublié dans le constructeur",
      "partie": "attributs",
      "statement": "Que se passe-t-il à la dernière ligne ?\n```python\nclass Personne:\n    def __init__(self, nom):\n        nom = nom\n\np = Personne(\"Ada\")\nprint(p.nom)\n```",
      "options": [
        {
          "text": "Une `AttributeError`, l'attribut `nom` n'ayant jamais été créé",
          "correct": true,
          "feedback": "Bonne réponse : `nom = nom` réaffecte la variable locale du\nconstructeur, sans toucher à l'objet. Il fallait écrire\n`self.nom = nom`.\n"
        },
        {
          "text": "L'affichage de `Ada`, Python ajoutant `self.` automatiquement",
          "correct": false,
          "feedback": "Erreur : rien n'est automatique, un attribut doit être créé\nexplicitement sur `self`.\n"
        },
        {
          "text": "Une erreur de syntaxe dès la définition de la classe",
          "correct": false,
          "feedback": "Erreur : `nom = nom` est une affectation valide, inutile mais\nvalide.\n"
        },
        {
          "text": "L'affichage de `None`, l'attribut existant sans valeur",
          "correct": false,
          "feedback": "Erreur : l'attribut n'existe pas du tout.\n"
        }
      ],
      "explanation": "Dans une méthode, seules les variables préfixées par `self.`\nappartiennent à l'objet ; les autres disparaissent à la fin de\nl'appel."
    },
    {
      "id": "q35",
      "difficulty": 2,
      "skills": [
        "attributs",
        "methode",
        "parentheses",
        "appel"
      ],
      "title": "Une méthode appelée sans parenthèses",
      "partie": "attributs",
      "statement": "La classe `Rectangle` a une méthode `aire` qui renvoie le produit\nde la largeur par la hauteur. Que vaut `r.aire`, sans parenthèses ?",
      "options": [
        {
          "text": "La méthode elle-même, pas le résultat du calcul",
          "correct": true,
          "feedback": "Bonne réponse : sans parenthèses, on désigne la méthode sans\nl'appeler ; son affichage ressemble à `<bound method ...>`.\n`r.aire()` effectue le calcul.\n"
        },
        {
          "text": "Le résultat du calcul, les parenthèses étant facultatives",
          "correct": false,
          "feedback": "Erreur : un appel exige des parenthèses, même sans argument.\n"
        },
        {
          "text": "Une erreur, l'attribut `aire` n'existant pas",
          "correct": false,
          "feedback": "Erreur : `aire` existe bien, c'est une méthode.\n"
        },
        {
          "text": "`None`, la méthode n'ayant pas encore été appelée",
          "correct": false,
          "feedback": "Erreur : l'expression a une valeur, la méthode.\n"
        }
      ],
      "explanation": "`r.aire` et `r.aire()` sont deux expressions différentes : la\nseconde seule déclenche l'exécution."
    },
    {
      "id": "q36",
      "difficulty": 3,
      "skills": [
        "attributs",
        "dependance",
        "calcul-a-la-construction"
      ],
      "title": "Un attribut qui ne suit pas les autres",
      "partie": "attributs",
      "statement": "Après ces instructions, `r.aire` vaut-il $40$ ?\n```python\nclass Rectangle:\n    def __init__(self, largeur, hauteur):\n        self.largeur = largeur\n        self.hauteur = hauteur\n        self.aire = largeur * hauteur\n\nr = Rectangle(2, 4)\nr.largeur = 10\n```",
      "options": [
        {
          "text": "Non, `aire` garde $8$, calculé une seule fois à la construction",
          "correct": true,
          "feedback": "Bonne réponse : l'attribut `aire` est une valeur stockée, pas une\nformule ; modifier `largeur` ne le recalcule pas. Une méthode\n`aire()` qui calcule à la demande évite ce décalage.\n"
        },
        {
          "text": "Oui, Python recalcule les attributs qui dépendent d'autres attributs",
          "correct": false,
          "feedback": "Erreur : aucun lien n'est conservé entre les attributs.\n"
        },
        {
          "text": "Une erreur, `largeur` ne pouvant être modifié après la construction",
          "correct": false,
          "feedback": "Erreur : la modification est permise, c'est son effet qui est\nincomplet.\n"
        },
        {
          "text": "Oui, mais seulement si `aire` a été défini après `largeur`",
          "correct": false,
          "feedback": "Erreur : l'ordre dans `__init__` ne crée aucune dépendance\ndurable.\n"
        }
      ],
      "explanation": "Stocker une valeur dérivée expose à l'incohérence ; la calculer\ndans une méthode garantit qu'elle est toujours à jour."
    },
    {
      "id": "q37",
      "difficulty": 2,
      "skills": [
        "classes",
        "constructeur",
        "absence"
      ],
      "title": "Une classe sans constructeur",
      "partie": "classes",
      "statement": "Peut-on créer un objet d'une classe définie sans méthode\n`__init__` ?\n```python\nclass Point:\n    pass\n\np = Point()\n```",
      "options": [
        {
          "text": "Oui, l'objet naît sans attribut, on peut lui en ajouter ensuite",
          "correct": true,
          "feedback": "Bonne réponse : `__init__` sert à initialiser, pas à créer ;\n`p.x = 3` ajoute un attribut après coup. Écrire un constructeur\nreste préférable pour que tous les objets aient les mêmes\nattributs.\n"
        },
        {
          "text": "Non, `__init__` est obligatoire pour toute classe instanciable",
          "correct": false,
          "feedback": "Erreur : Python fournit un constructeur par défaut, sans\nparamètre.\n"
        },
        {
          "text": "Oui, mais l'objet ne pourra jamais recevoir le moindre attribut",
          "correct": false,
          "feedback": "Erreur : les attributs peuvent être ajoutés à tout moment.\n"
        },
        {
          "text": "Non, `Point()` renvoie `None` faute de constructeur défini",
          "correct": false,
          "feedback": "Erreur : `Point()` renvoie bien un objet de type `Point`.\n"
        }
      ],
      "explanation": "Le constructeur n'est qu'une méthode appelée juste après la\ncréation ; sans lui, l'objet naît vide."
    },
    {
      "id": "q38",
      "difficulty": 3,
      "skills": [
        "classes",
        "egalite",
        "identite",
        "objets"
      ],
      "title": "Comparer deux objets avec l'opérateur d'égalité",
      "partie": "classes",
      "statement": "Qu'affiche ce programme ?\n```python\nclass Point:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\nprint(Point(1, 2) == Point(1, 2))\n```",
      "options": [
        {
          "text": "`False`, `==` comparant par défaut l'identité des objets",
          "correct": true,
          "feedback": "Bonne réponse : sans règle d'égalité définie dans la classe,\n`==` compare l'identité des objets, comme `is`. Deux\nconstructions donnent deux objets différents, malgré des\nattributs identiques.\n"
        },
        {
          "text": "`True`, les attributs `x` et `y` ayant les mêmes valeurs",
          "correct": false,
          "feedback": "Erreur : Python ne compare pas les attributs de lui-même.\n"
        },
        {
          "text": "Une erreur, `==` ne s'appliquant pas aux objets d'une classe",
          "correct": false,
          "feedback": "Erreur : `==` s'applique à tout, mais avec un sens par défaut\nqui surprend.\n"
        },
        {
          "text": "`None`, la comparaison n'étant pas définie pour cette classe",
          "correct": false,
          "feedback": "Erreur : la comparaison renvoie un booléen.\n"
        }
      ],
      "explanation": "Pour que `==` compare les attributs, la classe doit définir sa\npropre règle d'égalité ; sinon, seul le même objet est égal à\nlui-même."
    }
  ]
}