{
  "chapter": {
    "id": "modules",
    "level": "premiere",
    "theme": "Programmation",
    "title": "Modules Python",
    "description": "Notion de module, importation (`import`,\n`from ... import`), modules standards (`math`,\n`random`, `os`, `csv`...), création de ses propres\nmodules, espaces de noms, alias.",
    "parties": [
      {
        "id": "import",
        "title": "Importer un module"
      },
      {
        "id": "standard",
        "title": "Modules de la bibliothèque standard"
      },
      {
        "id": "ecrire",
        "title": "Écrire et documenter son module"
      }
    ],
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition"
      ],
      "title": "Qu'est-ce qu'un module ?",
      "partie": "import",
      "statement": "En Python, qu'est-ce qu'un **module** ?",
      "options": [
        {
          "text": "Un type de données spécialisé dans les calculs mathématiques",
          "correct": false,
          "feedback": "Erreur : un module est un fichier, pas un\ntype de données ; et il peut contenir du\ncode de toute nature, pas seulement du\ncalcul.\n"
        },
        {
          "text": "Un fichier `.py` regroupant du code réutilisable",
          "correct": true,
          "feedback": "Bonne réponse : un module rassemble des\nfonctions ou constantes réutilisables. On\npeut l'importer dans un autre programme\npour utiliser ses fonctions sans les\nrecoder.\n"
        },
        {
          "text": "Une boucle imbriquée exécutée à l'intérieur d'une fonction",
          "correct": false,
          "feedback": "Une boucle imbriquée est\nune construction de\ncontrôle interne à une\nfonction. Un module est\nau contraire une unité\nd'organisation du code,\nstockée dans un fichier.\n"
        },
        {
          "text": "Une variable globale accessible dans tout le programme",
          "correct": false,
          "feedback": "Une variable globale est\nune variable accessible\ndans tout un programme.\nUn module, lui, est une\nunité d'organisation du\ncode, contenue dans un\nfichier source distinct.\n"
        }
      ],
      "explanation": "La modularité est un principe central en génie\nlogiciel : organiser le code en unités\ncohérentes et réutilisables, plutôt qu'un seul\ngros fichier."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "import"
      ],
      "title": "Import",
      "partie": "import",
      "statement": "Comment **importer** le module `math` en Python ?",
      "options": [
        {
          "text": "`require math`",
          "correct": false,
          "feedback": "Erreur : c'est la syntaxe JavaScript / Ruby.\n"
        },
        {
          "text": "`use math`",
          "correct": false,
          "feedback": "Erreur : c'est la syntaxe Perl.\n"
        },
        {
          "text": "`include math`",
          "correct": false,
          "feedback": "Erreur : c'est la syntaxe C/C++.\n"
        },
        {
          "text": "`import math`",
          "correct": true,
          "feedback": "Bonne réponse : l'instruction `import` rend\naccessible le contenu du module via le\npréfixe `math.` (ex. `math.sqrt(2)`).\n"
        }
      ],
      "explanation": "`import math` charge le module et le rend\naccessible sous le nom `math`. Pour des\nfonctions précises sans préfixe :\n`from math import sqrt`."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "usage"
      ],
      "title": "Utilisation après import",
      "partie": "import",
      "statement": "Après `import math`, comment appelle-t-on la\nfonction `sqrt` (racine carrée) ?",
      "options": [
        {
          "text": "`math->sqrt(2)`",
          "correct": false,
          "feedback": "Erreur : `->` est la syntaxe Perl/PHP.\n"
        },
        {
          "text": "`math.sqrt(2)`",
          "correct": true,
          "feedback": "Bonne réponse : on utilise le préfixe\n`module.fonction`.\n"
        },
        {
          "text": "`sqrt(2)`",
          "correct": false,
          "feedback": "Erreur : sans préfixe `math.`, Python ne\nconnaît pas `sqrt`.\n"
        },
        {
          "text": "`math::sqrt(2)`",
          "correct": false,
          "feedback": "Erreur : `::` est la syntaxe C++ ou Ruby.\n"
        }
      ],
      "explanation": "Préfixe = **espace de noms**. Évite les\nconflits si plusieurs modules ont des\nfonctions de même nom."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "from-import"
      ],
      "title": "from import",
      "partie": "import",
      "statement": "Que fait `from math import sqrt` ?",
      "options": [
        {
          "text": "Importe l'intégralité du module `math` dans le programme",
          "correct": false,
          "feedback": "Erreur : on importe seulement `sqrt`.\n"
        },
        {
          "text": "Importe seulement `sqrt`, appelable directement",
          "correct": true,
          "feedback": "Bonne réponse : on écrit alors `sqrt(2)`\nsans préfixe. Utile quand on n'a besoin\nque de quelques fonctions et qu'on veut\néviter le préfixe.\n"
        },
        {
          "text": "Provoque une erreur, `sqrt` devant rester préfixé",
          "correct": false,
          "feedback": "Erreur : la syntaxe est parfaitement\nvalide ; c'est même elle qui permet de se\npasser du préfixe `math.`.\n"
        },
        {
          "text": "Renomme le module `math` en `sqrt` pour la suite",
          "correct": false,
          "feedback": "Le renommage à\nl'importation utilise\nle mot-clé `as`, par\nexemple `import math as\nm`. La syntaxe `from ...\nimport` ne renomme rien,\nelle expose un symbole\nprécis.\n"
        }
      ],
      "explanation": "Variantes : `from math import sqrt, pi` (deux\nnoms) ou `from math import *` (tout, mais à\néviter, pollue l'espace de noms)."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "math-pi"
      ],
      "title": "Constante pi",
      "partie": "standard",
      "statement": "Comment obtenir la valeur de π en Python ?",
      "options": [
        {
          "text": "`math.PI`",
          "correct": false,
          "feedback": "Erreur : Python distingue majuscules et\nminuscules ; `math.PI` n'existe pas\n(`AttributeError`). La constante s'écrit\n`math.pi`.\n"
        },
        {
          "text": "`math.pi`",
          "correct": true,
          "feedback": "Bonne réponse : après `import math`,\n`math.pi` vaut environ 3.141592653589793,\nla meilleure approximation flottante\ndisponible.\n"
        },
        {
          "text": "`Math.pi`",
          "correct": false,
          "feedback": "Erreur : `Math` avec une majuscule est\nl'objet de JavaScript ; en Python le module\ns'appelle `math` (`NameError` sinon).\n"
        },
        {
          "text": "`math.pi()`",
          "correct": false,
          "feedback": "Erreur : `math.pi` est une constante (un\nflottant), pas une fonction : les\nparenthèses provoquent `TypeError`\n(« 'float' object is not callable »).\n"
        }
      ],
      "explanation": "Constantes utiles dans `math` : `math.pi`,\n`math.e`, `math.inf`, `math.nan`."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "random"
      ],
      "title": "Module random",
      "partie": "standard",
      "statement": "Quel module de la bibliothèque standard permet\nde générer des nombres aléatoires ?",
      "options": [
        {
          "text": "`time`",
          "correct": false,
          "feedback": "Erreur : `time` sert à mesurer ou à attendre une durée,\npar exemple avec `time.time()`, pas à tirer des\nnombres au hasard.\n"
        },
        {
          "text": "`math`",
          "correct": false,
          "feedback": "Erreur : `math` est pour les fonctions\nmathématiques classiques.\n"
        },
        {
          "text": "`random`",
          "correct": true,
          "feedback": "Bonne réponse : `random.randint(a, b)`,\n`random.choice(liste)`, `random.shuffle(...)`,\netc.\n"
        },
        {
          "text": "`stochastic`",
          "correct": false,
          "feedback": "Erreur : pas de module standard de ce nom.\n"
        }
      ],
      "explanation": "`random` est très utilisé en NSI Première\npour les jeux, les simulations, le tirage\naléatoire."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "usage-randint"
      ],
      "title": "Tirage aléatoire",
      "partie": "standard",
      "statement": "Que fait `random.randint(1, 6)` ?",
      "options": [
        {
          "text": "Renvoie 6 (le maximum)",
          "correct": false,
          "feedback": "Erreur : c'est aléatoire, pas\nsystématiquement le max.\n"
        },
        {
          "text": "Renvoie une liste de 6 éléments",
          "correct": false,
          "feedback": "Erreur : un seul entier.\n"
        },
        {
          "text": "Renvoie un nombre flottant entre 1 et 6",
          "correct": false,
          "feedback": "Erreur : `randint` renvoie un **entier**.\n"
        },
        {
          "text": "Renvoie un entier aléatoire entre 1 et 6, inclus",
          "correct": true,
          "feedback": "Bonne réponse : simule un dé à 6 faces.\nAttention : contrairement à `range`, les\ndeux bornes sont **incluses**.\n"
        }
      ],
      "explanation": "Distinction subtile : `random.randint(a, b)`\n→ bornes **incluses** ; `random.randrange(a,\nb)` → b **exclue** (comme `range`)."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "import-as"
      ],
      "title": "Alias avec as",
      "partie": "import",
      "statement": "Que fait `import math as m` ?",
      "options": [
        {
          "text": "Importe deux modules, `math` et `m`",
          "correct": false,
          "feedback": "Erreur : un seul module est chargé ; `m` n'est\nqu'un second nom pour le désigner.\n"
        },
        {
          "text": "Importe `math` mais le rend accessible sous le nom `m`",
          "correct": true,
          "feedback": "Bonne réponse : `as m` crée un **alias**. On\nécrit ensuite `m.sqrt(2)` ; le nom `math` seul\nn'est pas défini et `math.sqrt(2)` lève\n`NameError`. Pratique pour raccourcir un nom\nde module long.\n"
        },
        {
          "text": "Provoque une erreur de syntaxe à cause du mot `as`",
          "correct": false,
          "feedback": "Erreur : la syntaxe `import x as y` est\nvalide et courante.\n"
        },
        {
          "text": "Importe seulement la fonction `m` du module `math`",
          "correct": false,
          "feedback": "Erreur : c'est tout `math` qui est importé,\nsimplement accessible via `m`. Importer un\nseul nom s'écrit `from math import sqrt`.\n"
        }
      ],
      "explanation": "L'alias ne change rien au contenu du module : il\nraccourcit seulement le préfixe à écrire devant\nchaque fonction, ce qui allège le code quand le\nmodule est utilisé souvent."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "bibliotheque-standard"
      ],
      "title": "Bibliothèque standard",
      "partie": "import",
      "statement": "Que signifie « bibliothèque standard de Python » ?",
      "options": [
        {
          "text": "Une norme ISO obligatoire",
          "correct": false,
          "feedback": "Erreur : pas de normalisation officielle.\n"
        },
        {
          "text": "La documentation de référence officielle du langage",
          "correct": false,
          "feedback": "Confusion : la bibliothèque standard est un\n**ensemble de modules**, pas la doc.\n"
        },
        {
          "text": "Les modules livrés avec Python, sans installation",
          "correct": true,
          "feedback": "Bonne réponse : `math`, `random`, `csv` ou\n`time` sont prêts à l'emploi, sans rien\ninstaller. C'est l'une des grandes forces\nde Python.\n"
        },
        {
          "text": "Les modules que l'on écrit soi-même pour son projet",
          "correct": false,
          "feedback": "Erreur : un fichier `.py` écrit pour son projet\nest bien un module, mais il ne fait pas partie\nde la bibliothèque standard, qui regroupe les\nmodules fournis avec Python.\n"
        }
      ],
      "explanation": "Un module de la bibliothèque standard, comme\n`math` ou `random`, s'importe sans aucune\ninstallation préalable : il suffit d'écrire\n`import math`."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "math",
        "sqrt"
      ],
      "title": "Exemple complet",
      "partie": "standard",
      "statement": "```python\nimport math\nprint(math.sqrt(16))\n```\nQue produit ce code ?",
      "options": [
        {
          "text": "Affiche `16.0`",
          "correct": false,
          "feedback": "Erreur : `sqrt` calcule la **racine**, pas\nl'identité.\n"
        },
        {
          "text": "Affiche `4.0`",
          "correct": true,
          "feedback": "Bonne réponse : `math.sqrt(16)` = 4.0\n(flottant, même pour un carré parfait).\n"
        },
        {
          "text": "Lève une erreur",
          "correct": false,
          "feedback": "Erreur : code valide.\n"
        },
        {
          "text": "Affiche `4`",
          "correct": false,
          "feedback": "Pas tout à fait : `sqrt` renvoie un\n**flottant** (`4.0`), pas un entier.\n"
        }
      ],
      "explanation": "Pour un entier, utiliser `int(math.sqrt(16))`\nou `math.isqrt(16)` (entier directement, sans\npasser par un flottant).",
      "variants": [
        {
          "statement": "```python\nimport math\nprint(math.sqrt(16))\n```\nQue produit ce code ?",
          "options": [
            {
              "text": "Affiche `16.0`",
              "correct": false,
              "feedback": "Erreur : `sqrt` calcule la **racine**, pas\nl'identité.\n"
            },
            {
              "text": "Affiche `4.0`",
              "correct": true,
              "feedback": "Bonne réponse : `math.sqrt(16)` = 4.0\n(flottant, même pour un carré parfait).\n"
            },
            {
              "text": "Lève une erreur",
              "correct": false,
              "feedback": "Erreur : code valide.\n"
            },
            {
              "text": "Affiche `4`",
              "correct": false,
              "feedback": "Pas tout à fait : `sqrt` renvoie un\n**flottant** (`4.0`), pas un entier.\n"
            }
          ],
          "explanation": "Pour un entier, utiliser `int(math.sqrt(16))`\nou `math.isqrt(16)` (entier directement, sans\npasser par un flottant)."
        },
        {
          "statement": "```python\nimport math\nprint(math.sqrt(25))\n```\nQue produit ce code ?",
          "options": [
            {
              "text": "Affiche `25.0`",
              "correct": false,
              "feedback": "Erreur : `sqrt` calcule la **racine**, pas\nl'identité.\n"
            },
            {
              "text": "Affiche `5.0`",
              "correct": true,
              "feedback": "Bonne réponse : `math.sqrt(25)` = 5.0\n(flottant, même pour un carré parfait).\n"
            },
            {
              "text": "Lève une erreur",
              "correct": false,
              "feedback": "Erreur : code valide.\n"
            },
            {
              "text": "Affiche `5`",
              "correct": false,
              "feedback": "Pas tout à fait : `sqrt` renvoie un\n**flottant** (`5.0`), pas un entier.\n"
            }
          ],
          "explanation": "Pour un entier, utiliser `int(math.sqrt(25))`\nou `math.isqrt(25)` (entier directement, sans\npasser par un flottant)."
        },
        {
          "statement": "```python\nimport math\nprint(math.sqrt(81))\n```\nQue produit ce code ?",
          "options": [
            {
              "text": "Affiche `81.0`",
              "correct": false,
              "feedback": "Erreur : `sqrt` calcule la **racine**, pas\nl'identité.\n"
            },
            {
              "text": "Affiche `9.0`",
              "correct": true,
              "feedback": "Bonne réponse : `math.sqrt(81)` = 9.0\n(flottant, même pour un carré parfait).\n"
            },
            {
              "text": "Lève une erreur",
              "correct": false,
              "feedback": "Erreur : code valide.\n"
            },
            {
              "text": "Affiche `9`",
              "correct": false,
              "feedback": "Pas tout à fait : `sqrt` renvoie un\n**flottant** (`9.0`), pas un entier.\n"
            }
          ],
          "explanation": "Pour un entier, utiliser `int(math.sqrt(81))`\nou `math.isqrt(81)` (entier directement, sans\npasser par un flottant)."
        }
      ]
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "namespace"
      ],
      "title": "Espace de noms",
      "partie": "import",
      "statement": "Pourquoi est-il **déconseillé** d'utiliser\n`from math import *` ?",
      "options": [
        {
          "text": "Parce que ce mode d'importation est nettement trop lent à l'exécution",
          "correct": false,
          "feedback": "Erreur : performance équivalente.\n"
        },
        {
          "text": "Parce que les noms importés peuvent masquer des fonctions existantes",
          "correct": true,
          "feedback": "Bonne réponse : tous les noms du module\nsont importés d'un coup (collisions\npossibles). Par exemple, si vous aviez une\nvariable `pow`, elle serait écrasée par\n`math.pow`. Source de bugs subtils.\n"
        },
        {
          "text": "Parce que cette syntaxe est interdite depuis Python 3",
          "correct": false,
          "feedback": "Erreur : la syntaxe reste parfaitement\nautorisée, elle est seulement déconseillée.\n"
        },
        {
          "text": "Parce que seule la première fonction du module est importée",
          "correct": false,
          "feedback": "Erreur : c'est tout l'inverse, l'étoile\nimporte **tous** les noms publics du\nmodule d'un seul coup.\n"
        }
      ],
      "explanation": "Bonne pratique : `import math` (préfixe) ou\n`from math import sqrt, pi` (sélectif).\nÉviter `from x import *` sauf pour des\nmodules conçus pour cela."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "creer-module"
      ],
      "title": "Créer son module",
      "partie": "ecrire",
      "statement": "Comment créer son propre module\n`mes_outils.py` et l'importer ?",
      "options": [
        {
          "text": "Il faut placer un mot-clé `module` au début du fichier source",
          "correct": false,
          "feedback": "Erreur : aucun mot-clé spécial n'est\nnécessaire, tout fichier `.py` est\nd'office un module.\n"
        },
        {
          "text": "Il faut compiler `mes_outils.py` avant de pouvoir l'importer",
          "correct": false,
          "feedback": "Erreur : Python est interprété, aucune\ncompilation explicite n'est demandée au\nprogrammeur.\n"
        },
        {
          "text": "Écrire le fichier, puis faire `import mes_outils`",
          "correct": true,
          "feedback": "Bonne réponse : tout fichier `.py` est un\nmodule. On crée `mes_outils.py` avec ses\nfonctions, et `import mes_outils` suffit.\nAucune déclaration spéciale n'est\nnécessaire.\n"
        },
        {
          "text": "Il faut placer le fichier dans le dossier d'installation de Python",
          "correct": false,
          "feedback": "Erreur : un module placé dans le même dossier\nque le programme qui l'importe est trouvé\ndirectement ; aucun déplacement n'est\nnécessaire.\n"
        }
      ],
      "explanation": "Cette simplicité est l'un des grands atouts\nde Python pour la modularité. Un projet est\nnaturellement organisé en plusieurs fichiers\n`.py`."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "documentation",
        "random"
      ],
      "title": "Lire la documentation de uniform",
      "partie": "ecrire",
      "statement": "La documentation du module `random` indique :\n« `random.uniform(a, b)` renvoie un nombre flottant choisi au\nhasard entre `a` et `b`, bornes comprises. »\nAprès `import random`, quelle expression simule une température\nà virgule comprise entre $18$ et $25$ degrés ?",
      "options": [
        {
          "text": "`random.uniform(18, 25)`",
          "correct": true,
          "feedback": "Bonne réponse : d'après la documentation,\n`uniform(18, 25)` renvoie un flottant compris\nentre $18$ et $25$, par exemple `21.73`.\n"
        },
        {
          "text": "`random.randint(18, 25)`",
          "correct": false,
          "feedback": "Erreur : `randint` ne renvoie que des entiers\n($18$, $19$, …, $25$). La température n'aurait\njamais de partie décimale.\n"
        },
        {
          "text": "`random.random(18, 25)`",
          "correct": false,
          "feedback": "Erreur : `random.random()` ne prend aucun\nargument et renvoie un flottant entre $0$ et $1$.\nCet appel lève `TypeError`.\n"
        },
        {
          "text": "`18 + random.random() * 25`",
          "correct": false,
          "feedback": "Erreur : `random.random() * 25` est compris entre\n$0$ et $25$, donc le résultat va de $18$ à $43$.\nIl faudrait multiplier par $25 - 18 = 7$.\n"
        }
      ],
      "explanation": "Lire la documentation suffit à choisir la bonne fonction :\n`uniform(a, b)` pour un flottant entre deux bornes,\n`randint(a, b)` pour un entier, `random()` sans argument\npour un flottant entre $0$ et $1$.",
      "variants": [
        {
          "statement": "La documentation du module `random` indique :\n« `random.uniform(a, b)` renvoie un nombre flottant choisi au\nhasard entre `a` et `b`, bornes comprises. »\nAprès `import random`, quelle expression simule une température\nà virgule comprise entre $18$ et $25$ degrés ?",
          "options": [
            {
              "text": "`random.uniform(18, 25)`",
              "correct": true,
              "feedback": "Bonne réponse : d'après la documentation,\n`uniform(18, 25)` renvoie un flottant compris\nentre $18$ et $25$, par exemple `21.73`.\n"
            },
            {
              "text": "`random.randint(18, 25)`",
              "correct": false,
              "feedback": "Erreur : `randint` ne renvoie que des entiers\n($18$, $19$, …, $25$). La température n'aurait\njamais de partie décimale.\n"
            },
            {
              "text": "`random.random(18, 25)`",
              "correct": false,
              "feedback": "Erreur : `random.random()` ne prend aucun\nargument et renvoie un flottant entre $0$ et $1$.\nCet appel lève `TypeError`.\n"
            },
            {
              "text": "`18 + random.random() * 25`",
              "correct": false,
              "feedback": "Erreur : `random.random() * 25` est compris entre\n$0$ et $25$, donc le résultat va de $18$ à $43$.\nIl faudrait multiplier par $25 - 18 = 7$.\n"
            }
          ],
          "explanation": "Lire la documentation suffit à choisir la bonne fonction :\n`uniform(a, b)` pour un flottant entre deux bornes,\n`randint(a, b)` pour un entier, `random()` sans argument\npour un flottant entre $0$ et $1$."
        },
        {
          "statement": "La documentation du module `random` indique :\n« `random.uniform(a, b)` renvoie un nombre flottant choisi au\nhasard entre `a` et `b`, bornes comprises. »\nAprès `import random`, quelle expression simule une température\nà virgule comprise entre $10$ et $30$ degrés ?",
          "options": [
            {
              "text": "`random.uniform(10, 30)`",
              "correct": true,
              "feedback": "Bonne réponse : d'après la documentation,\n`uniform(10, 30)` renvoie un flottant compris\nentre $10$ et $30$, par exemple `17.4`.\n"
            },
            {
              "text": "`random.randint(10, 30)`",
              "correct": false,
              "feedback": "Erreur : `randint` ne renvoie que des entiers\n($10$, $11$, …, $30$). La température n'aurait\njamais de partie décimale.\n"
            },
            {
              "text": "`random.random(10, 30)`",
              "correct": false,
              "feedback": "Erreur : `random.random()` ne prend aucun\nargument et renvoie un flottant entre $0$ et $1$.\nCet appel lève `TypeError`.\n"
            },
            {
              "text": "`10 + random.random() * 30`",
              "correct": false,
              "feedback": "Erreur : `random.random() * 30` est compris entre\n$0$ et $30$, donc le résultat va de $10$ à $40$.\nIl faudrait multiplier par $30 - 10 = 20$.\n"
            }
          ],
          "explanation": "Lire la documentation suffit à choisir la bonne fonction :\n`uniform(a, b)` pour un flottant entre deux bornes,\n`randint(a, b)` pour un entier, `random()` sans argument\npour un flottant entre $0$ et $1$."
        },
        {
          "statement": "La documentation du module `random` indique :\n« `random.uniform(a, b)` renvoie un nombre flottant choisi au\nhasard entre `a` et `b`, bornes comprises. »\nAprès `import random`, quelle expression simule une température\nà virgule comprise entre $15$ et $22$ degrés ?",
          "options": [
            {
              "text": "`random.uniform(15, 22)`",
              "correct": true,
              "feedback": "Bonne réponse : d'après la documentation,\n`uniform(15, 22)` renvoie un flottant compris\nentre $15$ et $22$, par exemple `19.06`.\n"
            },
            {
              "text": "`random.randint(15, 22)`",
              "correct": false,
              "feedback": "Erreur : `randint` ne renvoie que des entiers\n($15$, $16$, …, $22$). La température n'aurait\njamais de partie décimale.\n"
            },
            {
              "text": "`random.random(15, 22)`",
              "correct": false,
              "feedback": "Erreur : `random.random()` ne prend aucun\nargument et renvoie un flottant entre $0$ et $1$.\nCet appel lève `TypeError`.\n"
            },
            {
              "text": "`15 + random.random() * 22`",
              "correct": false,
              "feedback": "Erreur : `random.random() * 22` est compris entre\n$0$ et $22$, donc le résultat va de $15$ à $37$.\nIl faudrait multiplier par $22 - 15 = 7$.\n"
            }
          ],
          "explanation": "Lire la documentation suffit à choisir la bonne fonction :\n`uniform(a, b)` pour un flottant entre deux bornes,\n`randint(a, b)` pour un entier, `random()` sans argument\npour un flottant entre $0$ et $1$."
        }
      ]
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "random-choice"
      ],
      "title": "Choix aléatoire",
      "partie": "standard",
      "statement": "Quelle fonction du module `random` permet de\nchoisir un élément au hasard dans une liste ?",
      "options": [
        {
          "text": "`random.select(liste)`",
          "correct": false,
          "feedback": "Erreur : pas de fonction de ce nom.\n"
        },
        {
          "text": "`random.random(liste)`",
          "correct": false,
          "feedback": "Erreur : `random.random()` renvoie un\nflottant entre 0 et 1, sans paramètre.\n"
        },
        {
          "text": "`random.choice(liste)`",
          "correct": true,
          "feedback": "Bonne réponse : `choice` renvoie un\nélément aléatoire de la liste (chaque\nélément a la même probabilité d'être\nchoisi).\n"
        },
        {
          "text": "`random.pick(liste)`",
          "correct": false,
          "feedback": "Erreur : la fonction n'a pas ce nom.\n"
        }
      ],
      "explanation": "Autres fonctions utiles : `random.choices`\n(avec poids), `random.sample` (sans\nremise), `random.shuffle` (mélange en place)."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "module-eleve",
        "specification"
      ],
      "title": "Réutiliser la fonction d'un camarade",
      "partie": "ecrire",
      "statement": "Pour son projet, Lina veut utiliser la fonction `moyenne` du\nmodule `stats.py` écrit par un camarade, sans lire son code.\nQue lui faut-il connaître pour l'appeler correctement ?",
      "options": [
        {
          "text": "Sa spécification : ses paramètres et ce qu'elle renvoie",
          "correct": true,
          "feedback": "Bonne réponse : le nom de la fonction, ses paramètres\n(avec leurs éventuelles préconditions) et la valeur\nrenvoyée suffisent. C'est l'intérêt d'un module bien\ndocumenté : on le réutilise sans en lire le code.\n"
        },
        {
          "text": "Le nom des variables utilisées dans le corps de la fonction",
          "correct": false,
          "feedback": "Erreur : ces variables sont internes à la fonction.\nLina n'en a pas besoin, et le camarade peut les\nrenommer sans rien changer pour elle.\n"
        },
        {
          "text": "Le nom du dossier où le camarade a écrit le module",
          "correct": false,
          "feedback": "Erreur : ce qui compte, c'est que `stats.py` soit\ndans le dossier du programme de Lina au moment de\nl'import, pas l'endroit où il a été écrit.\n"
        },
        {
          "text": "Rien : `import stats` affiche la liste des paramètres attendus",
          "correct": false,
          "feedback": "Erreur : `import stats` charge le module sans rien\nafficher sur ses fonctions. C'est leur documentation\nqui indique comment les appeler.\n"
        }
      ],
      "explanation": "Un module sert à réutiliser du code. Pour appeler une de ses\nfonctions, il suffit de connaître sa spécification : nom,\nparamètres, préconditions éventuelles et valeur renvoyée.\nC'est ce que doit fournir sa documentation."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "help-doc"
      ],
      "title": "Documentation interactive",
      "partie": "ecrire",
      "statement": "Comment obtenir l'aide d'une fonction d'un\nmodule en mode interactif ?",
      "options": [
        {
          "text": "`help(math.sqrt)`",
          "correct": true,
          "feedback": "Bonne réponse : la fonction native `help`\naffiche la documentation de la fonction\nreçue en argument. Très utile pour\ndécouvrir un module.\n"
        },
        {
          "text": "`man math.sqrt`",
          "correct": false,
          "feedback": "Erreur : `man` est une commande Unix.\n"
        },
        {
          "text": "`info(math.sqrt)`",
          "correct": false,
          "feedback": "Erreur : pas de fonction `info` en Python.\n"
        },
        {
          "text": "`math.sqrt.help()`",
          "correct": false,
          "feedback": "Erreur : `help` n'est pas rangée dans la\nfonction `sqrt` ; c'est une fonction native\nqui reçoit en argument la fonction à\ndocumenter. Cet appel lève `AttributeError`.\n"
        }
      ],
      "explanation": "`help(math.sqrt)` fonctionne après\n`import math`. Pour une lecture plus\ncomplète, la documentation officielle\n(docs.python.org) décrit chaque module."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "import-fonction"
      ],
      "title": "Importation sélective",
      "partie": "import",
      "statement": "Quel code utilise `pi` correctement après\n`from math import pi` ?",
      "options": [
        {
          "text": "`math.pi`",
          "correct": false,
          "feedback": "Erreur : avec `from ... import`, on\nn'utilise **pas** le préfixe.\n"
        },
        {
          "text": "`pi`",
          "correct": true,
          "feedback": "Bonne réponse : `pi` est importé\ndirectement dans l'espace de noms local.\nOn peut écrire `2 * pi`.\n"
        },
        {
          "text": "`import.pi`",
          "correct": false,
          "feedback": "Erreur : syntaxe invalide.\n"
        },
        {
          "text": "`from.pi`",
          "correct": false,
          "feedback": "Erreur : syntaxe invalide.\n"
        }
      ],
      "explanation": "`import math` → `math.pi` ; `from math import\npi` → `pi`. Choisir selon ses préférences."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "conflict-noms"
      ],
      "title": "Conflit de noms",
      "partie": "import",
      "statement": "```python\nfrom math import sqrt\n\ndef sqrt(x):\n    return x ** 0.5\n\nprint(sqrt(16))\n```\nQuelle fonction est appelée ?",
      "options": [
        {
          "text": "La fonction `sqrt` importée du module `math`",
          "correct": false,
          "feedback": "Erreur : la définition locale, postérieure\nà l'import, **écrase** le nom importé.\n"
        },
        {
          "text": "Les deux fonctions `sqrt`, appelées l'une après l'autre",
          "correct": false,
          "feedback": "Erreur : un nom ne désigne qu'une seule\nfonction à la fois ; un seul appel a lieu.\n"
        },
        {
          "text": "La fonction `sqrt` définie localement",
          "correct": true,
          "feedback": "Bonne réponse : la dernière définition\ngagne, et le programme affiche `4.0`. Les\ndeux fonctions calculent ici la même chose\n(la racine carrée), donc le résultat est\nidentique, mais en général c'est un piège :\nla définition locale masque silencieusement\nl'import.\n"
        },
        {
          "text": "Aucune, Python lève une erreur de conflit de noms",
          "correct": false,
          "feedback": "Erreur : Python ne signale rien, il écrase\nsilencieusement l'ancien nom. C'est\nprécisément ce qui rend ce piège dangereux.\n"
        }
      ],
      "explanation": "Bonne pratique : éviter de redéfinir des\nnoms importés. Si on doit le faire, l'import\navec préfixe (`import math`) protège."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "random-seed"
      ],
      "title": "Reproductibilité",
      "partie": "standard",
      "statement": "Pourquoi utilise-t-on `random.seed(42)` ?",
      "options": [
        {
          "text": "Pour rendre les tirages plus aléatoires",
          "correct": false,
          "feedback": "Erreur : c'est l'inverse, on les rend\ndéterministes.\n"
        },
        {
          "text": "Pour accélérer significativement le générateur de nombres",
          "correct": false,
          "feedback": "Erreur : pas d'effet sur la performance.\n"
        },
        {
          "text": "Pour rendre les tirages reproductibles",
          "correct": true,
          "feedback": "Bonne réponse : même graine, même séquence\nde tirages. Utile pour le débogage, les\ntests, la reproductibilité scientifique.\n"
        },
        {
          "text": "Pour limiter les tirages aux valeurs inférieures à 42",
          "correct": false,
          "feedback": "Erreur : la graine initialise l'état du\ngénérateur, elle ne borne pas les valeurs\nproduites.\n"
        }
      ],
      "explanation": "Pour les simulations et les jeux, fixer\nla graine permet de rejouer exactement la\nmême suite de tirages pendant la mise au\npoint."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "creer-module",
        "namespace"
      ],
      "title": "Imports d'un module importé",
      "partie": "ecrire",
      "statement": "Le fichier `outils.py` contient :\n\n```python\nimport math\n\ndef aire_disque(r):\n    return math.pi * r ** 2\n```\n\nDans le même dossier, le programme `main.py`\ncontient :\n\n```python\nimport outils\n\nprint(outils.aire_disque(2))\nprint(math.pi)\n```\n\nQue se passe-t-il quand on exécute `main.py` ?",
      "options": [
        {
          "text": "Les deux lignes s'affichent, car `import outils` importe aussi `math`",
          "correct": false,
          "feedback": "Erreur : l'import de `math` a lieu dans\nl'espace de noms d'`outils`, pas dans celui\nde `main.py`. Le nom `math` n'existe pas dans\nle programme principal, et `print(math.pi)`\nlève `NameError`.\n"
        },
        {
          "text": "Le premier affichage échoue, car `main.py` n'a pas importé `math`",
          "correct": false,
          "feedback": "Erreur : `aire_disque` est définie dans\n`outils.py`, où `math` est bien importé. Elle\ns'exécute sans problème et affiche\n`12.566370614359172`. C'est la ligne suivante\nqui échoue.\n"
        },
        {
          "text": "Le premier affichage réussit, puis `print(math.pi)` lève `NameError`",
          "correct": true,
          "feedback": "Bonne réponse : `12.566370614359172` s'affiche,\ncar `aire_disque` trouve `math` dans son propre\nmodule. Puis `main.py` cherche le nom `math`\ndans son espace de noms, où il n'a jamais été\nimporté, et Python lève\n`NameError: name 'math' is not defined`.\n"
        },
        {
          "text": "La ligne `import outils` échoue, car un module ne peut pas en importer un autre",
          "correct": false,
          "feedback": "Erreur : un module peut importer d'autres\nmodules, c'est même courant. `import outils`\nréussit et exécute `import math` à l'intérieur\nd'`outils`.\n"
        }
      ],
      "explanation": "Chaque module a son propre espace de noms. Les\nimports faits dans `outils.py` ne sont visibles que\ndans `outils.py` : pour utiliser `math.pi` dans\n`main.py`, il faut y écrire aussi `import math`."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "assert",
        "module",
        "precondition"
      ],
      "title": "Une précondition vérifiée dans un module",
      "partie": "ecrire",
      "statement": "Le fichier `geometrie.py` contient :\n```python\ndef aire_rectangle(longueur, largeur):\n    assert longueur >= 0 and largeur >= 0\n    return longueur * largeur\n```\nDans le même dossier, `main.py` contient :\n```python\nimport geometrie\nprint(geometrie.aire_rectangle(4, 3))\nprint(geometrie.aire_rectangle(-4, 3))\n```\nQue se passe-t-il à l'exécution de `main.py` ?",
      "options": [
        {
          "text": "`12` s'affiche, puis le second appel lève `AssertionError`",
          "correct": true,
          "feedback": "Bonne réponse : le premier appel respecte la\nprécondition et affiche `12`. Au second,\n`longueur >= 0` est faux : l'assertion échoue,\nPython lève `AssertionError` et le programme\ns'arrête.\n"
        },
        {
          "text": "`12` puis `-12` : les assertions d'un module importé sont ignorées",
          "correct": false,
          "feedback": "Erreur : une fonction importée s'exécute\nexactement comme si elle était écrite dans\n`main.py`. L'assertion est vérifiée à chaque\nappel et échoue pour `-4`.\n"
        },
        {
          "text": "Rien ne s'affiche : l'erreur survient dès la ligne `import geometrie`",
          "correct": false,
          "feedback": "Erreur : l'import ne fait que définir la\nfonction ; l'assertion n'est évaluée qu'à\nl'appel. Le premier appel affiche donc `12`\navant que le second échoue.\n"
        },
        {
          "text": "`12` puis `0` : l'assertion remplace la longueur négative par zéro",
          "correct": false,
          "feedback": "Erreur : `assert` ne corrige aucune valeur. Elle\nvérifie une condition et arrête le programme\navec `AssertionError` si celle-ci est fausse.\n"
        }
      ],
      "explanation": "Une assertion placée en tête de fonction garantit sa\nprécondition, que la fonction soit appelée depuis son\npropre fichier ou depuis un programme qui importe le\nmodule. Un appel qui ne la respecte pas lève\n`AssertionError`.",
      "variants": [
        {
          "statement": "Le fichier `geometrie.py` contient :\n```python\ndef aire_rectangle(longueur, largeur):\n    assert longueur >= 0 and largeur >= 0\n    return longueur * largeur\n```\nDans le même dossier, `main.py` contient :\n```python\nimport geometrie\nprint(geometrie.aire_rectangle(4, 3))\nprint(geometrie.aire_rectangle(-4, 3))\n```\nQue se passe-t-il à l'exécution de `main.py` ?",
          "options": [
            {
              "text": "`12` s'affiche, puis le second appel lève `AssertionError`",
              "correct": true,
              "feedback": "Bonne réponse : le premier appel respecte la\nprécondition et affiche `12`. Au second,\n`longueur >= 0` est faux : l'assertion échoue,\nPython lève `AssertionError` et le programme\ns'arrête.\n"
            },
            {
              "text": "`12` puis `-12` : les assertions d'un module importé sont ignorées",
              "correct": false,
              "feedback": "Erreur : une fonction importée s'exécute\nexactement comme si elle était écrite dans\n`main.py`. L'assertion est vérifiée à chaque\nappel et échoue pour `-4`.\n"
            },
            {
              "text": "Rien ne s'affiche : l'erreur survient dès la ligne `import geometrie`",
              "correct": false,
              "feedback": "Erreur : l'import ne fait que définir la\nfonction ; l'assertion n'est évaluée qu'à\nl'appel. Le premier appel affiche donc `12`\navant que le second échoue.\n"
            },
            {
              "text": "`12` puis `0` : l'assertion remplace la longueur négative par zéro",
              "correct": false,
              "feedback": "Erreur : `assert` ne corrige aucune valeur. Elle\nvérifie une condition et arrête le programme\navec `AssertionError` si celle-ci est fausse.\n"
            }
          ],
          "explanation": "Une assertion placée en tête de fonction garantit sa\nprécondition, que la fonction soit appelée depuis son\npropre fichier ou depuis un programme qui importe le\nmodule. Un appel qui ne la respecte pas lève\n`AssertionError`."
        },
        {
          "statement": "Le fichier `geometrie.py` contient :\n```python\ndef aire_rectangle(longueur, largeur):\n    assert longueur >= 0 and largeur >= 0\n    return longueur * largeur\n```\nDans le même dossier, `main.py` contient :\n```python\nimport geometrie\nprint(geometrie.aire_rectangle(5, 2))\nprint(geometrie.aire_rectangle(-5, 2))\n```\nQue se passe-t-il à l'exécution de `main.py` ?",
          "options": [
            {
              "text": "`10` s'affiche, puis le second appel lève `AssertionError`",
              "correct": true,
              "feedback": "Bonne réponse : le premier appel respecte la\nprécondition et affiche `10`. Au second,\n`longueur >= 0` est faux : l'assertion échoue,\nPython lève `AssertionError` et le programme\ns'arrête.\n"
            },
            {
              "text": "`10` puis `-10` : les assertions d'un module importé sont ignorées",
              "correct": false,
              "feedback": "Erreur : une fonction importée s'exécute\nexactement comme si elle était écrite dans\n`main.py`. L'assertion est vérifiée à chaque\nappel et échoue pour `-5`.\n"
            },
            {
              "text": "Rien ne s'affiche : l'erreur survient dès la ligne `import geometrie`",
              "correct": false,
              "feedback": "Erreur : l'import ne fait que définir la\nfonction ; l'assertion n'est évaluée qu'à\nl'appel. Le premier appel affiche donc `10`\navant que le second échoue.\n"
            },
            {
              "text": "`10` puis `0` : l'assertion remplace la longueur négative par zéro",
              "correct": false,
              "feedback": "Erreur : `assert` ne corrige aucune valeur. Elle\nvérifie une condition et arrête le programme\navec `AssertionError` si celle-ci est fausse.\n"
            }
          ],
          "explanation": "Une assertion placée en tête de fonction garantit sa\nprécondition, que la fonction soit appelée depuis son\npropre fichier ou depuis un programme qui importe le\nmodule. Un appel qui ne la respecte pas lève\n`AssertionError`."
        },
        {
          "statement": "Le fichier `geometrie.py` contient :\n```python\ndef aire_rectangle(longueur, largeur):\n    assert longueur >= 0 and largeur >= 0\n    return longueur * largeur\n```\nDans le même dossier, `main.py` contient :\n```python\nimport geometrie\nprint(geometrie.aire_rectangle(6, 3))\nprint(geometrie.aire_rectangle(-6, 3))\n```\nQue se passe-t-il à l'exécution de `main.py` ?",
          "options": [
            {
              "text": "`18` s'affiche, puis le second appel lève `AssertionError`",
              "correct": true,
              "feedback": "Bonne réponse : le premier appel respecte la\nprécondition et affiche `18`. Au second,\n`longueur >= 0` est faux : l'assertion échoue,\nPython lève `AssertionError` et le programme\ns'arrête.\n"
            },
            {
              "text": "`18` puis `-18` : les assertions d'un module importé sont ignorées",
              "correct": false,
              "feedback": "Erreur : une fonction importée s'exécute\nexactement comme si elle était écrite dans\n`main.py`. L'assertion est vérifiée à chaque\nappel et échoue pour `-6`.\n"
            },
            {
              "text": "Rien ne s'affiche : l'erreur survient dès la ligne `import geometrie`",
              "correct": false,
              "feedback": "Erreur : l'import ne fait que définir la\nfonction ; l'assertion n'est évaluée qu'à\nl'appel. Le premier appel affiche donc `18`\navant que le second échoue.\n"
            },
            {
              "text": "`18` puis `0` : l'assertion remplace la longueur négative par zéro",
              "correct": false,
              "feedback": "Erreur : `assert` ne corrige aucune valeur. Elle\nvérifie une condition et arrête le programme\navec `AssertionError` si celle-ci est fausse.\n"
            }
          ],
          "explanation": "Une assertion placée en tête de fonction garantit sa\nprécondition, que la fonction soit appelée depuis son\npropre fichier ou depuis un programme qui importe le\nmodule. Un appel qui ne la respecte pas lève\n`AssertionError`."
        }
      ]
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "documentation",
        "precondition",
        "random"
      ],
      "title": "La précondition de random.choice",
      "partie": "ecrire",
      "statement": "La documentation du module `random` indique :\n\n« `random.choice(seq)` renvoie un élément choisi au hasard dans\nla séquence **non vide** `seq`. »\n\nAprès `import random`, lequel de ces appels ne respecte **pas**\ncette précondition ?",
      "options": [
        {
          "text": "`random.choice([])`",
          "correct": true,
          "feedback": "Bonne réponse : la liste vide ne contient aucun\nélément à choisir. La précondition « non vide »\nn'est pas respectée, et Python lève `IndexError`.\n"
        },
        {
          "text": "`random.choice(\"abc\")`",
          "correct": false,
          "feedback": "Erreur : une chaîne de caractères est une\nséquence, indexée comme une liste. L'appel est\nvalide et renvoie `\"a\"`, `\"b\"` ou `\"c\"`.\n"
        },
        {
          "text": "`random.choice([\"pile\"])`",
          "correct": false,
          "feedback": "Erreur : une liste d'un seul élément n'est pas\nvide. L'appel est valide et renvoie toujours\n`\"pile\"`.\n"
        },
        {
          "text": "`random.choice([0, 0])`",
          "correct": false,
          "feedback": "Erreur : la liste contient deux éléments, même\ns'ils valent $0$. Elle n'est pas vide, et l'appel\nrenvoie `0`.\n"
        }
      ],
      "explanation": "Une précondition décrit ce que doivent vérifier les\narguments. Pour `random.choice`, seul compte le fait que\nla séquence contienne au moins un élément, quelles que\nsoient les valeurs qu'elle contient."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "shadowing"
      ],
      "title": "Deux imports étoilés",
      "partie": "import",
      "statement": "Pourquoi le code suivant peut-il être\nproblématique ?\n```python\nfrom random import *\nfrom math import *\n```",
      "options": [
        {
          "text": "Parce que des fonctions de même nom s'écrasent silencieusement",
          "correct": true,
          "feedback": "Bonne réponse : `from module import *` copie\ntous les noms du module dans le programme.\nSi deux modules, ou le programme lui-même,\ndéfinissent un même nom, le dernier import le\nremplace sans prévenir, et à la lecture on ne\nsait plus d'où vient chaque fonction.\n"
        },
        {
          "text": "Parce que ces importations sont beaucoup trop lentes à charger",
          "correct": false,
          "feedback": "Erreur : le coût de chargement est\nnégligeable, la performance n'est pas le\nproblème.\n"
        },
        {
          "text": "Parce que l'on ne peut pas importer deux modules dans un fichier",
          "correct": false,
          "feedback": "Erreur : on peut importer autant de\nmodules qu'on veut dans un même fichier.\n"
        },
        {
          "text": "Parce que `math` doit impérativement être importé avant `random`",
          "correct": false,
          "feedback": "Erreur : Python n'impose aucun ordre\nd'importation entre modules indépendants.\n"
        }
      ],
      "explanation": "Préférer `import math` et `import random`, puis\nécrire `math.sqrt` ou `random.randint` : le\npréfixe indique d'où vient chaque nom et évite\ntout écrasement."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "bonne-pratique"
      ],
      "title": "Ordre des imports",
      "partie": "ecrire",
      "statement": "Dans un fichier Python bien organisé, où\nplacer les `import` ?",
      "options": [
        {
          "text": "À l'intérieur d'une fonction `main` appelée au démarrage du programme",
          "correct": false,
          "feedback": "Erreur : c'est possible, mais exceptionnel.\nPar convention, les imports se placent en haut\ndu fichier.\n"
        },
        {
          "text": "À la fin du fichier",
          "correct": false,
          "feedback": "Erreur : tous les noms importés ne seraient\npas disponibles avant.\n"
        },
        {
          "text": "N'importe où dans le fichier",
          "correct": false,
          "feedback": "Techniquement possible, mais déconseillé.\n"
        },
        {
          "text": "En haut du fichier, avant le reste du code",
          "correct": true,
          "feedback": "Bonne réponse : placés en tête, les imports\nsont exécutés avant toute utilisation des noms\nimportés, et le lecteur voit tout de suite de\nquels modules dépend le programme.\n"
        }
      ],
      "explanation": "Convention de lecture : voir tout de suite\nles dépendances en haut du fichier. Un nom\nimporté plus bas ne serait pas disponible pour\nle code situé au-dessus."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "partie": "ecrire",
      "statement": "Parmi les affirmations suivantes, laquelle est\n**fausse** ?",
      "options": [
        {
          "text": "`from math import pi` importe la constante `pi` sans préfixe",
          "correct": false,
          "feedback": "Vrai : importation sélective.\n"
        },
        {
          "text": "La bibliothèque standard de Python contient des modules pour les fichiers, le réseau, les dates, le hasard, etc.",
          "correct": false,
          "feedback": "Vrai : « batteries included », l'une des\nforces de Python.\n"
        },
        {
          "text": "Un fichier `.py` écrit par l'élève ne peut pas être importé comme module",
          "correct": true,
          "feedback": "Faux (donc bonne réponse) : tout fichier `.py`\nest un module. Placé dans le dossier du\nprogramme, il s'importe avec\n`import nom_du_fichier`, sans l'extension.\n"
        },
        {
          "text": "`import math` rend les fonctions accessibles via le préfixe `math.`",
          "correct": false,
          "feedback": "Vrai : c'est le rôle de `import`.\n"
        }
      ],
      "explanation": "Les modules de la bibliothèque standard et ceux\nque l'on écrit soi-même s'importent de la même\nfaçon ; leurs fonctions s'utilisent avec le\npréfixe du module ou après un import sélectif."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "random",
        "randint"
      ],
      "title": "random.random et random.randint",
      "partie": "standard",
      "statement": "Quelle est la différence entre `random.random()` et\n`random.randint(a, b)` ?",
      "options": [
        {
          "text": "Les deux renvoient un entier, `random()` entre 0 et 1 et `randint(a, b)` entre `a` et `b`",
          "correct": false,
          "feedback": "Erreur : la fonction `random.random()` renvoie un\nflottant, pas un entier (un entier entre 0 et 1\nn'aurait d'ailleurs que deux valeurs possibles).\nSeule `randint(a, b)` renvoie un entier.\n"
        },
        {
          "text": "`random()` renvoie un flottant de $[0\\,;\\, 1[$, `randint(a, b)` un entier de $[a\\,;\\, b]$",
          "correct": true,
          "feedback": "Bonne réponse : `random.random()` produit un\nflottant tiré uniformément, strictement\ninférieur à $1$, tandis que\n`random.randint(a, b)` renvoie un entier avec\nles **deux** bornes incluses (alors que la\nplupart des intervalles Python excluent la\nborne droite).\n"
        },
        {
          "text": "`random.randint(a, b)` renvoie un entier de $[a\\,;\\, b[$, borne `b` exclue",
          "correct": false,
          "feedback": "Erreur : c'est précisément la particularité de\n`randint` parmi les fonctions du module : la borne\ndroite est **incluse**. Pour exclure la borne, il\nfaudrait utiliser `random.randrange(a, b)`.\n"
        },
        {
          "text": "`random.random()` renvoie un flottant de $[0\\,;\\, 1]$, borne $1$ comprise",
          "correct": false,
          "feedback": "Erreur : la borne droite est **exclue** :\n`random.random()` peut renvoyer $0$ mais jamais\nexactement $1$, comme la plupart des intervalles\nen Python.\n"
        }
      ],
      "explanation": "Pour mémoriser : `random.random()` $\\to [0\\,;\\, 1[$\n(flottant) ; `random.randint(a, b)` $\\to [a\\,;\\, b]$\n(entier) ; `random.randrange(a, b)` $\\to [a\\,;\\, b[$\n(entier) ; `random.choice(seq)` $\\to$ un élément de\nla séquence. Pour la reproductibilité d'un tirage,\nfixer `random.seed(...)` au début du programme."
    },
    {
      "id": "q27",
      "difficulty": 2,
      "skills": [
        "math",
        "fonctions-courantes"
      ],
      "title": "Fonctions du module math",
      "partie": "standard",
      "statement": "Parmi les expressions suivantes, laquelle ne renvoie\n**pas** $5$ après l'instruction `import math` ?",
      "options": [
        {
          "text": "`math.ceil(4.3)`",
          "correct": false,
          "feedback": "La fonction `ceil` arrondit vers le haut. Pour\n$4{,}3$, le résultat est $5$.\n"
        },
        {
          "text": "`math.floor(4.9)`",
          "correct": true,
          "feedback": "Bonne réponse : `math.floor` arrondit vers le\nbas, même quand la partie décimale est proche\nde $1$. Pour $4{,}9$, le résultat est $4$, et\nnon $5$.\n"
        },
        {
          "text": "`math.sqrt(25)`",
          "correct": false,
          "feedback": "La racine carrée de $25$ vaut bien $5$. Cette\nexpression renvoie donc $5$ (au flottant\n$5{,}0$ près).\n"
        },
        {
          "text": "`math.floor(5.7)`",
          "correct": false,
          "feedback": "La fonction `floor` arrondit vers le bas (partie\nentière inférieure). Pour $5{,}7$, le résultat\nest $5$.\n"
        }
      ],
      "explanation": "Quelques fonctions usuelles du module `math` :\n`sqrt` (racine carrée), `floor` et `ceil`\n(arrondis vers le bas et vers le haut).\nConstante : `math.pi`."
    },
    {
      "id": "q28",
      "difficulty": 2,
      "skills": [
        "time",
        "mesure-duree"
      ],
      "title": "Mesurer une durée d'exécution",
      "partie": "standard",
      "statement": "On souhaite mesurer la durée d'exécution d'une\nportion de code. Quelle structure utilise le module\n`time` de manière correcte ?",
      "options": [
        {
          "text": "```python\nimport time\ndebut = time.time()\n# ... code à mesurer ...\nduree = time.time()\n```\n",
          "correct": false,
          "feedback": "Erreur : il manque la **soustraction** finale.\nIci `duree` contient l'heure de fin (nombre de\nsecondes depuis 1970), pas la durée écoulée :\nil faut écrire `duree = time.time() - debut`.\n"
        },
        {
          "text": "```python\nimport time\ndebut = time.time()\n# ... code à mesurer ...\nduree = debut - time.time()\n```\n",
          "correct": false,
          "feedback": "Erreur : la soustraction est dans le mauvais\nsens. `debut` est antérieur à l'heure de fin,\ndonc `duree` est **négative**. Il faut écrire\n`time.time() - debut`.\n"
        },
        {
          "text": "```python\nimport time\ndebut = time.time()\n# ... code à mesurer ...\nduree = time.time() - time.time()\n```\n",
          "correct": false,
          "feedback": "Erreur : `debut` n'est jamais utilisé. Les deux\nappels à `time.time()` sont quasi simultanés :\n`duree` vaut presque 0 quel que soit le code\nmesuré.\n"
        },
        {
          "text": "```python\nimport time\ndebut = time.time()\n# ... code à mesurer ...\nduree = time.time() - debut\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : `time.time()` renvoie le nombre\nde secondes écoulées depuis le $1^{\\text{er}}$\njanvier $1970$. La différence entre deux\nappels donne la durée écoulée en secondes\n(avec une précision de l'ordre de la\nmicroseconde sur les systèmes modernes).\n"
        }
      ],
      "explanation": "La différence entre deux appels à `time.time()`\nmesure une durée réelle : elle peut varier\nlégèrement d'une exécution à l'autre, selon ce\nque fait l'ordinateur au même moment."
    },
    {
      "id": "q29",
      "difficulty": 2,
      "skills": [
        "from-import",
        "espace-de-noms"
      ],
      "title": "Le préfixe après un import sélectif",
      "partie": "import",
      "statement": "Après `from math import sqrt`, que produit `math.sqrt(16)` ?",
      "options": [
        {
          "text": "Une erreur, le nom `math` n'étant pas importé",
          "correct": true,
          "feedback": "Bonne réponse : `from math import sqrt` n'introduit que le nom\n`sqrt`. Le module `math` lui-même reste inconnu, et il faut\nécrire `sqrt(16)`.\n"
        },
        {
          "text": "`4.0`, `math` restant accessible",
          "correct": false,
          "feedback": "Erreur : `sqrt(16)` donnerait `4.0`, mais pas `math.sqrt`, le\nnom `math` étant absent.\n"
        },
        {
          "text": "`16`, la racine n'étant pas appliquée",
          "correct": false,
          "feedback": "Erreur : aucune racine n'est calculée, l'expression échoue avant.\n"
        },
        {
          "text": "`4`, la racine d'un entier étant un entier",
          "correct": false,
          "feedback": "Erreur : outre le nom manquant, `sqrt` renvoie un flottant.\n"
        }
      ],
      "explanation": "`import math` donne accès à `math.sqrt` ; `from math import sqrt`\ndonne accès à `sqrt` seul. Les deux formes ne se mélangent pas."
    },
    {
      "id": "q30",
      "difficulty": 2,
      "skills": [
        "import",
        "chargement"
      ],
      "title": "Un module importé deux fois",
      "partie": "import",
      "statement": "Un programme contient deux fois la ligne `import random`, dans deux\nfichiers différents du projet. Que se passe-t-il ?",
      "options": [
        {
          "text": "Le module n'est chargé qu'une fois, le second `import` le réutilisant",
          "correct": true,
          "feedback": "Bonne réponse : Python garde en mémoire les modules déjà\nchargés. Un nouvel `import` ne fait que rendre le nom\ndisponible dans le fichier courant.\n"
        },
        {
          "text": "Une erreur, un module ne pouvant être importé qu'une fois",
          "correct": false,
          "feedback": "Erreur : importer plusieurs fois est permis et courant dans un\nprojet à plusieurs fichiers.\n"
        },
        {
          "text": "Le module est chargé deux fois et occupe deux fois plus de mémoire",
          "correct": false,
          "feedback": "Erreur : le second `import` retrouve le module déjà chargé.\n"
        },
        {
          "text": "Les fonctions du module sont dupliquées sous deux noms",
          "correct": false,
          "feedback": "Erreur : les noms restent les mêmes, `random.randint` par\nexemple.\n"
        }
      ],
      "explanation": "Chaque fichier qui utilise un module l'importe ; Python se charge\nde ne le lire qu'une seule fois."
    },
    {
      "id": "q31",
      "difficulty": 2,
      "skills": [
        "math",
        "floor",
        "ceil"
      ],
      "title": "Arrondir vers le bas ou vers le haut",
      "partie": "standard",
      "statement": "Après `import math`, que valent `math.floor(3.7)` et\n`math.ceil(3.7)` ?",
      "options": [
        {
          "text": "`3` et `4`",
          "correct": true,
          "feedback": "Bonne réponse : `floor` arrondit vers le bas, `ceil` vers le\nhaut, et les deux renvoient un entier.\n"
        },
        {
          "text": "`4` et `3`",
          "correct": false,
          "feedback": "Erreur : `floor` est le plancher, `ceil` le plafond.\n"
        },
        {
          "text": "`3` et `3`",
          "correct": false,
          "feedback": "Erreur : `ceil` monte à l'entier supérieur dès que la partie\ndécimale n'est pas nulle.\n"
        },
        {
          "text": "`3.7` et `3.7`",
          "correct": false,
          "feedback": "Erreur : les deux fonctions renvoient des entiers.\n"
        }
      ],
      "explanation": "`math.floor`, `math.ceil` et `round` arrondissent différemment ; le\nchoix dépend de ce que l'on veut compter.",
      "variants": [
        {
          "statement": "Après `import math`, que valent `math.floor(3.7)` et\n`math.ceil(3.7)` ?",
          "options": [
            {
              "text": "`3` et `4`",
              "correct": true,
              "feedback": "Bonne réponse : `floor` arrondit vers le bas, `ceil` vers le\nhaut, et les deux renvoient un entier.\n"
            },
            {
              "text": "`4` et `3`",
              "correct": false,
              "feedback": "Erreur : `floor` est le plancher, `ceil` le plafond.\n"
            },
            {
              "text": "`3` et `3`",
              "correct": false,
              "feedback": "Erreur : `ceil` monte à l'entier supérieur dès que la partie\ndécimale n'est pas nulle.\n"
            },
            {
              "text": "`3.7` et `3.7`",
              "correct": false,
              "feedback": "Erreur : les deux fonctions renvoient des entiers.\n"
            }
          ],
          "explanation": "`math.floor`, `math.ceil` et `round` arrondissent différemment ; le\nchoix dépend de ce que l'on veut compter."
        },
        {
          "statement": "Après `import math`, que valent `math.floor(8.2)` et\n`math.ceil(8.2)` ?",
          "options": [
            {
              "text": "`8` et `9`",
              "correct": true,
              "feedback": "Bonne réponse : `floor` arrondit vers le bas, `ceil` vers le\nhaut, et les deux renvoient un entier.\n"
            },
            {
              "text": "`9` et `8`",
              "correct": false,
              "feedback": "Erreur : `floor` est le plancher, `ceil` le plafond.\n"
            },
            {
              "text": "`8` et `8`",
              "correct": false,
              "feedback": "Erreur : `ceil` monte à l'entier supérieur dès que la partie\ndécimale n'est pas nulle.\n"
            },
            {
              "text": "`8.2` et `8.2`",
              "correct": false,
              "feedback": "Erreur : les deux fonctions renvoient des entiers.\n"
            }
          ],
          "explanation": "`math.floor`, `math.ceil` et `round` arrondissent différemment ; le\nchoix dépend de ce que l'on veut compter."
        },
        {
          "statement": "Après `import math`, que valent `math.floor(5.5)` et\n`math.ceil(5.5)` ?",
          "options": [
            {
              "text": "`5` et `6`",
              "correct": true,
              "feedback": "Bonne réponse : `floor` arrondit vers le bas, `ceil` vers le\nhaut, et les deux renvoient un entier.\n"
            },
            {
              "text": "`6` et `5`",
              "correct": false,
              "feedback": "Erreur : `floor` est le plancher, `ceil` le plafond.\n"
            },
            {
              "text": "`5` et `5`",
              "correct": false,
              "feedback": "Erreur : `ceil` monte à l'entier supérieur dès que la partie\ndécimale n'est pas nulle.\n"
            },
            {
              "text": "`5.5` et `5.5`",
              "correct": false,
              "feedback": "Erreur : les deux fonctions renvoient des entiers.\n"
            }
          ],
          "explanation": "`math.floor`, `math.ceil` et `round` arrondissent différemment ; le\nchoix dépend de ce que l'on veut compter."
        }
      ]
    },
    {
      "id": "q32",
      "difficulty": 1,
      "skills": [
        "bibliotheque-standard",
        "csv"
      ],
      "title": "Le module pour lire un fichier CSV",
      "partie": "standard",
      "statement": "Quel module de la bibliothèque standard facilite la lecture d'un\nfichier CSV ?",
      "options": [
        {
          "text": "`csv`",
          "correct": true,
          "feedback": "Bonne réponse : `csv.reader` découpe chaque ligne en champs en\ntenant compte du séparateur et des guillemets.\n"
        },
        {
          "text": "`math`",
          "correct": false,
          "feedback": "Erreur : `math` fournit des fonctions mathématiques.\n"
        },
        {
          "text": "`random`",
          "correct": false,
          "feedback": "Erreur : `random` sert aux tirages aléatoires.\n"
        },
        {
          "text": "`time`",
          "correct": false,
          "feedback": "Erreur : `time` mesure des durées et donne l'heure.\n"
        }
      ],
      "explanation": "La bibliothèque standard couvre les besoins courants : `csv`,\n`math`, `random`, `time` sont parmi les modules les plus utilisés en\npremière."
    },
    {
      "id": "q33",
      "difficulty": 3,
      "skills": [
        "math",
        "sqrt",
        "erreur",
        "precondition"
      ],
      "title": "La racine d'un nombre négatif",
      "partie": "standard",
      "statement": "Après `import math`, que produit `math.sqrt(-1)` ?",
      "options": [
        {
          "text": "Une erreur, l'argument devant être positif",
          "correct": true,
          "feedback": "Bonne réponse : la fonction a pour précondition un argument\npositif ou nul. Elle refuse `-1` au lieu de renvoyer un résultat\nsans sens pour les réels.\n"
        },
        {
          "text": "`-1.0`, l'argument étant renvoyé tel quel",
          "correct": false,
          "feedback": "Erreur : aucun réel n'a pour carré `-1`, et la fonction ne\nrenvoie pas l'argument.\n"
        },
        {
          "text": "`1.0`, le signe étant ignoré",
          "correct": false,
          "feedback": "Erreur : `sqrt` n'ignore pas le signe, elle signale l'erreur.\n"
        },
        {
          "text": "`0.0`, valeur par défaut en cas de problème",
          "correct": false,
          "feedback": "Erreur : `0.0` est la racine de `0`, pas de `-1`.\n"
        }
      ],
      "explanation": "Les fonctions d'un module ont des préconditions, indiquées par leur\ndocumentation : les respecter évite les erreurs à l'exécution."
    },
    {
      "id": "q34",
      "difficulty": 1,
      "skills": [
        "ecrire-module",
        "fichier",
        "nom"
      ],
      "title": "Le nom d'un module écrit soi-même",
      "partie": "ecrire",
      "statement": "Lina a écrit ses fonctions dans un fichier `outils.py`, placé dans\nle même dossier que son programme. Comment les importe-t-elle ?",
      "options": [
        {
          "text": "`import outils`",
          "correct": true,
          "feedback": "Bonne réponse : le nom du module est le nom du fichier sans\nl'extension `.py`. Les fonctions s'appellent ensuite\n`outils.nom_de_fonction`.\n"
        },
        {
          "text": "`import outils.py`",
          "correct": false,
          "feedback": "Erreur : l'extension ne fait pas partie du nom du module ; cette\nécriture chercherait un sous-module `py`.\n"
        },
        {
          "text": "`import \"outils.py\"`",
          "correct": false,
          "feedback": "Erreur : `import` attend un nom, pas une chaîne de caractères.\n"
        },
        {
          "text": "`open(\"outils.py\")`",
          "correct": false,
          "feedback": "Erreur : `open` lit le texte du fichier, il n'exécute pas son\ncode.\n"
        }
      ],
      "explanation": "Tout fichier `.py` est un module : il suffit qu'il soit dans le\ndossier du programme pour être importé par son nom."
    },
    {
      "id": "q35",
      "difficulty": 2,
      "skills": [
        "ecrire-module",
        "import",
        "execution"
      ],
      "title": "Ce qui s'exécute à l'import",
      "partie": "ecrire",
      "statement": "Le fichier `outils.py` contient une définition de fonction suivie\nde `print(\"module chargé\")`. Que se passe-t-il quand un programme\nexécute `import outils` ?",
      "options": [
        {
          "text": "Le message s'affiche, le code du module étant exécuté",
          "correct": true,
          "feedback": "Bonne réponse : importer un module exécute son fichier de haut\nen bas. Les définitions créent les fonctions, et les autres\ninstructions, comme ce `print`, s'exécutent aussi.\n"
        },
        {
          "text": "Rien ne s'affiche, seules les définitions étant lues",
          "correct": false,
          "feedback": "Erreur : Python ne distingue pas les définitions du reste, il\nexécute tout.\n"
        },
        {
          "text": "Le message s'affiche à chaque appel d'une fonction du module",
          "correct": false,
          "feedback": "Erreur : le `print` est au niveau du module, pas dans une\nfonction ; il ne s'exécute qu'à l'import.\n"
        },
        {
          "text": "Une erreur, un module ne pouvant contenir que des fonctions",
          "correct": false,
          "feedback": "Erreur : un module peut contenir n'importe quelle instruction,\nmême si l'usage est d'y mettre surtout des définitions.\n"
        }
      ],
      "explanation": "Un module bien écrit ne contient que des définitions et des\nconstantes, pour que son import n'ait aucun effet visible."
    },
    {
      "id": "q36",
      "difficulty": 3,
      "skills": [
        "ecrire-module",
        "dependance",
        "erreur"
      ],
      "title": "Un module qui dépend d'un autre",
      "partie": "ecrire",
      "statement": "Le module `stats.py` de Sami commence par `import graphiques`, un\nmodule écrit par un camarade. Lina copie `stats.py` seul sur son\nordinateur et exécute `import stats`. Que se passe-t-il ?",
      "options": [
        {
          "text": "Une erreur dès l'import, `graphiques` étant introuvable",
          "correct": true,
          "feedback": "Bonne réponse : importer `stats` exécute sa première ligne, qui\ntente d'importer `graphiques`. Faute de ce fichier, l'import\néchoue avant que `stats` ne soit utilisable.\n"
        },
        {
          "text": "`stats` s'importe, mais ses fonctions échouent à l'appel",
          "correct": false,
          "feedback": "Erreur : l'échec survient dès la ligne `import graphiques`, donc\ndès l'import de `stats`.\n"
        },
        {
          "text": "Python recherche `graphiques.py` sur l'ordinateur de Sami",
          "correct": false,
          "feedback": "Erreur : Python ne cherche que dans les dossiers de la machine\noù il s'exécute.\n"
        },
        {
          "text": "`stats` s'importe normalement, `graphiques` étant ignoré",
          "correct": false,
          "feedback": "Erreur : un import qui échoue arrête l'exécution du module.\n"
        }
      ],
      "explanation": "Un module hérite des dépendances des modules qu'il importe ;\npartager son code suppose de fournir aussi les modules dont il\ndépend."
    }
  ]
}