{
  "chapter": {
    "id": "fonctions",
    "level": "premiere",
    "theme": "Programmation",
    "title": "Fonctions",
    "description": "Définition de fonctions en Python, paramètres et valeur\nde retour, portée locale et globale, fonctions natives,\nfonctions sans retour, paramètres par défaut, documentation\n(docstrings).",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition"
      ],
      "title": "Définir une fonction",
      "statement": "Comment définit-on une fonction `carre` qui\nprend `x` en paramètre et renvoie `x * x` ?",
      "options": [
        {
          "text": "```python\ndef carre(x) -> x * x\n```\n",
          "correct": false,
          "feedback": "Erreur : `->` indique le type de retour\n(annotation), pas l'expression. Il manque\nle corps de la fonction.\n"
        },
        {
          "text": "```python\ncarre = x * x\n```\n",
          "correct": false,
          "feedback": "Erreur : on définirait une variable, pas\nune fonction.\n"
        },
        {
          "text": "```python\nfunction carre(x) {\n    return x * x\n}\n```\n",
          "correct": false,
          "feedback": "Erreur : syntaxe JavaScript, pas Python.\n"
        },
        {
          "text": "```python\ndef carre(x):\n    return x * x\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : mot-clé `def`, parenthèses,\ndeux-points, indentation, `return`.\n"
        }
      ],
      "explanation": "Syntaxe canonique : `def nom(params): corps`.\nLe mot-clé `return` produit la valeur que la\nfonction renvoie à l'appelant."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "appel"
      ],
      "title": "Appel de fonction",
      "statement": "Après `def carre(x): return x * x`, comment\nappelle-t-on cette fonction sur la valeur 5 ?",
      "options": [
        {
          "text": "`carre(5)`",
          "correct": true,
          "feedback": "Bonne réponse : parenthèses obligatoires,\nmême pour un seul paramètre. Renvoie 25.\n"
        },
        {
          "text": "`carre 5`",
          "correct": false,
          "feedback": "Erreur : il faut des parenthèses pour\nappeler une fonction en Python.\n"
        },
        {
          "text": "`call carre(5)`",
          "correct": false,
          "feedback": "Erreur : pas de mot-clé `call` en Python.\n"
        },
        {
          "text": "`carre[5]`",
          "correct": false,
          "feedback": "Erreur : les crochets sont pour\nl'indexation, pas l'appel de fonction.\n"
        }
      ],
      "explanation": "Sans parenthèses, `carre` désigne la fonction\nelle-même (un objet). Avec parenthèses,\n`carre(...)` invoque la fonction."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "return"
      ],
      "title": "Mot-clé return",
      "statement": "À quoi sert l'instruction `return` ?",
      "options": [
        {
          "text": "À redémarrer la fonction",
          "correct": false,
          "feedback": "Le mot-clé `return` met\nfin à l'exécution de la\nfonction et fournit une\nvaleur à l'appelant. Il\nne relance jamais la\nfonction depuis le début.\n"
        },
        {
          "text": "À renvoyer une valeur à l'appelant et terminer la fonction",
          "correct": true,
          "feedback": "Bonne réponse : la fonction s'arrête au\n`return`, et la valeur passée est\ndisponible pour l'appelant.\n"
        },
        {
          "text": "À afficher le résultat sur la console",
          "correct": false,
          "feedback": "Erreur : c'est `print`. `return` produit\nune valeur de retour, sans affichage.\n"
        },
        {
          "text": "À déclarer une variable globale",
          "correct": false,
          "feedback": "Erreur : c'est `global`.\n"
        }
      ],
      "explanation": "Différence essentielle entre `print` et\n`return` : `print` affiche un message à\nl'utilisateur ; `return` produit une valeur\nutilisable dans le code (par exemple,\n`y = carre(3)`)."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "parametres"
      ],
      "title": "Paramètres",
      "statement": "Quelle fonction a **deux** paramètres ?",
      "options": [
        {
          "text": "```python\ndef f(x):\n    return x\n```\n",
          "correct": false,
          "feedback": "Erreur : un seul paramètre.\n"
        },
        {
          "text": "```python\ndef f():\n    return 0\n```\n",
          "correct": false,
          "feedback": "Erreur : zéro paramètre.\n"
        },
        {
          "text": "```python\ndef f(x; y):\n    return x + y\n```\n",
          "correct": false,
          "feedback": "Erreur : le séparateur est la virgule, pas\nle point-virgule.\n"
        },
        {
          "text": "```python\ndef f(x, y):\n    return x + y\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : deux paramètres `x` et `y`\nséparés par une virgule.\n"
        }
      ],
      "explanation": "Vocabulaire : on parle de **paramètres** d'une\nfonction, à la fois pour leur nom dans la\ndéfinition (`def f(x, y):`) et pour les valeurs\nqu'on lui passe à l'appel (`f(3, 5)`)."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "sans-retour"
      ],
      "title": "Fonction sans return",
      "statement": "Que renvoie une fonction qui ne contient **aucun\n`return`** ?",
      "options": [
        {
          "text": "`None`",
          "correct": true,
          "feedback": "Bonne réponse : Python renvoie\nimplicitement `None` (la valeur « rien »).\nC'est le cas typique des fonctions qui se\ncontentent d'afficher ou de modifier\nquelque chose, sans renvoyer de valeur.\n"
        },
        {
          "text": "Une erreur",
          "correct": false,
          "feedback": "Erreur : pas d'erreur.\n"
        },
        {
          "text": "Une chaîne vide `\"\"`",
          "correct": false,
          "feedback": "Erreur : la valeur par défaut est `None`.\n"
        },
        {
          "text": "`0`",
          "correct": false,
          "feedback": "Erreur : ce n'est pas la valeur par défaut.\n"
        }
      ],
      "explanation": "Souvent, on n'utilise pas la valeur de retour\nde telles fonctions (on ne fait pas\n`x = print(...)`). Mais elle existe (`None`)."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "fonctions-natives"
      ],
      "title": "Fonctions natives",
      "statement": "Parmi les suivants, lequel n'est **pas** une\nfonction native de Python ?",
      "options": [
        {
          "text": "`carre(...)`",
          "correct": true,
          "feedback": "Bonne réponse : `carre` n'est pas natif,\nc'est une fonction qu'il faut définir\nsoi-même.\n"
        },
        {
          "text": "`type(...)`",
          "correct": false,
          "feedback": "C'est bien une fonction native (renvoie le\ntype).\n"
        },
        {
          "text": "`len(...)`",
          "correct": false,
          "feedback": "C'est bien une fonction native (longueur).\n"
        },
        {
          "text": "`max(...)`",
          "correct": false,
          "feedback": "C'est bien une fonction native (maximum).\n"
        }
      ],
      "explanation": "Fonctions natives utiles : `print`, `input`,\n`len`, `range`, `type`, `int`, `float`, `str`,\n`bool`, `min`, `max`, `sum`, `abs`, `round`,\n`sorted`, etc."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "appel-multiple"
      ],
      "title": "Plusieurs appels",
      "statement": "```python\ndef double(x):\n    return x * 2\n\na = double(3)\nb = double(double(5))\n```\nQue valent `a` et `b` ?",
      "options": [
        {
          "text": "a = 6, b = 20",
          "correct": true,
          "feedback": "Bonne réponse : a = double(3) = 6 ;\nb = double(double(5)) = double(10) = 20.\n"
        },
        {
          "text": "a = 6, b = 25",
          "correct": false,
          "feedback": "Erreur : 25 = 5², mais on multiplie par 2,\npas un carré.\n"
        },
        {
          "text": "a = 3, b = 5",
          "correct": false,
          "feedback": "Erreur : on n'a pas appelé la fonction.\n"
        },
        {
          "text": "a = 6, b = 10",
          "correct": false,
          "feedback": "Erreur sur b : `double(double(5))` =\n`double(10)` = 20.\n"
        }
      ],
      "explanation": "Composition de fonctions : `f(g(x))` évalue\nd'abord `g(x)` puis applique `f`. Lecture de\nl'intérieur vers l'extérieur."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "docstring"
      ],
      "title": "Docstring",
      "statement": "Que fait la première ligne en triples\nguillemets dans une fonction ?\n```python\ndef carre(x):\n    \"\"\"Renvoie le carré de x.\"\"\"\n    return x * x\n```",
      "options": [
        {
          "text": "C'est une docstring : la documentation de la fonction, accessible par `help(carre)` ou `carre.__doc__`",
          "correct": true,
          "feedback": "Bonne réponse : standard Python pour\ndocumenter ses fonctions. Très utile pour\nla maintenance et l'aide automatique.\n"
        },
        {
          "text": "C'est obligatoire pour que la fonction marche",
          "correct": false,
          "feedback": "Erreur : facultatif.\n"
        },
        {
          "text": "C'est un commentaire ignoré par Python",
          "correct": false,
          "feedback": "Erreur : c'est plus qu'un commentaire, c'est\nune **docstring** accessible\ndynamiquement.\n"
        },
        {
          "text": "C'est un test unitaire",
          "correct": false,
          "feedback": "Erreur : un test serait un `assert`.\n"
        }
      ],
      "explanation": "Bonne pratique : toujours documenter ses\nfonctions. PEP 257 décrit les conventions\n(description courte sur la première ligne,\npuis détails)."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "scope"
      ],
      "title": "Variable locale",
      "statement": "Le code suivant lève-t-il une erreur ?\n```python\ndef f():\n    x = 5\n\nf()\nprint(x)\n```",
      "options": [
        {
          "text": "Non, il affiche `0`",
          "correct": false,
          "feedback": "Erreur : aucune valeur par défaut.\n"
        },
        {
          "text": "Non, mais il affiche `None`",
          "correct": false,
          "feedback": "Erreur : pas de retour silencieux.\n"
        },
        {
          "text": "Non, il affiche 5",
          "correct": false,
          "feedback": "Erreur : `x` est **locale** à `f`, pas\naccessible à l'extérieur.\n"
        },
        {
          "text": "Oui : `NameError` car `x` n'est pas définie hors de `f`",
          "correct": true,
          "feedback": "Bonne réponse : la **portée** des variables\nlocales est limitée à la fonction.\n"
        }
      ],
      "explanation": "Notion clé : la **portée** (ou scope). Une\nvariable créée dans une fonction n'existe\nqu'à l'intérieur de cette fonction."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "exemple-simple"
      ],
      "title": "Trace simple",
      "statement": "Que fait ce code ?\n```python\ndef saluer(nom):\n    print(\"Bonjour\", nom)\n\nsaluer(\"Alice\")\n```",
      "options": [
        {
          "text": "Lève une erreur",
          "correct": false,
          "feedback": "Erreur : code valide.\n"
        },
        {
          "text": "Affiche `Bonjour Alice`",
          "correct": true,
          "feedback": "Bonne réponse : la fonction reçoit `nom =\n\"Alice\"`, puis `print` affiche les deux\nparamètres séparés par un espace.\n"
        },
        {
          "text": "Renvoie la chaîne `\"Bonjour Alice\"` mais sans afficher",
          "correct": false,
          "feedback": "Erreur : `print` affiche, et la fonction n'a\npas de `return`.\n"
        },
        {
          "text": "N'affiche rien",
          "correct": false,
          "feedback": "Erreur : `print` produit un affichage.\n"
        }
      ],
      "explanation": "Distinguer `print` (afficher quelque chose à\nl'écran) de `return` (renvoyer une valeur à\nl'appelant). Ici, la fonction se contente\nd'afficher."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "params-defaut"
      ],
      "title": "Paramètre par défaut",
      "statement": "Que donne ce code ?\n```python\ndef saluer(nom=\"ami\"):\n    return \"Bonjour \" + nom\n\nprint(saluer())\nprint(saluer(\"Alice\"))\n```",
      "options": [
        {
          "text": "```\nErreur\nBonjour Alice\n```\n",
          "correct": false,
          "feedback": "Erreur : avec un paramètre par défaut,\nl'appel sans paramètre est valide.\n"
        },
        {
          "text": "```\nBonjour ami\nBonjour Alice\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : `nom=\"ami\"` est la valeur\npar défaut. Si on n'en passe pas,\nle paramètre vaut `\"ami\"`.\n"
        },
        {
          "text": "```\nBonjour ami\nBonjour ami\n```\n",
          "correct": false,
          "feedback": "Erreur : le second appel passe `\"Alice\"`,\nqui remplace la valeur par défaut.\n"
        },
        {
          "text": "```\nBonjour\nBonjour Alice\n```\n",
          "correct": false,
          "feedback": "Erreur : la valeur par défaut est `\"ami\"`,\npas une chaîne vide.\n"
        }
      ],
      "explanation": "Les paramètres par défaut rendent les\nfonctions plus flexibles. Convention : les\nparamètres avec valeur par défaut viennent\n**après** les paramètres sans."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "args-positionnel-nomme"
      ],
      "title": "Paramètres nommés",
      "statement": "Quelle est la différence entre\n`puissance(2, 10)` et\n`puissance(base=2, exposant=10)` ?",
      "options": [
        {
          "text": "La seconde forme ne marche que pour les fonctions natives",
          "correct": false,
          "feedback": "Erreur : les paramètres nommés marchent\npour toutes les fonctions.\n"
        },
        {
          "text": "La première forme est obsolète",
          "correct": false,
          "feedback": "Erreur : très utilisée.\n"
        },
        {
          "text": "La seconde forme est plus rapide",
          "correct": false,
          "feedback": "Erreur : aucune différence de performance.\n"
        },
        {
          "text": "Aucune, les deux sont équivalents si la fonction est `def puissance(base, exposant):`",
          "correct": true,
          "feedback": "Bonne réponse : on peut passer des\nparamètres **positionnellement** (par\nordre) ou **nommés** (par mot-clé).\nL'ordre n'importe pas pour les nommés.\n"
        }
      ],
      "explanation": "Les paramètres nommés améliorent la lisibilité,\nsurtout quand un appel a beaucoup de\nparamètres (par exemple `plt.plot(x, y,\ncolor=\"red\", linestyle=\"--\")`)."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "scope-global"
      ],
      "title": "Variable globale",
      "statement": "Quel est le résultat ?\n```python\nx = 10\n\ndef f():\n    return x + 1\n\nprint(f())\n```",
      "options": [
        {
          "text": "Erreur (x non défini)",
          "correct": false,
          "feedback": "Erreur : `f` peut **lire** les variables du\nscope englobant.\n"
        },
        {
          "text": "11",
          "correct": true,
          "feedback": "Bonne réponse : `x` est une variable\n**globale**. La fonction peut la lire,\nmais pas la modifier (sans `global`).\n"
        },
        {
          "text": "Aucune sortie",
          "correct": false,
          "feedback": "Erreur : `print(f())` affiche.\n"
        },
        {
          "text": "10",
          "correct": false,
          "feedback": "Erreur : on ajoute 1.\n"
        }
      ],
      "explanation": "Règle d'or : préférer **passer en paramètre**\nplutôt qu'utiliser des variables globales.\nCode plus testable et plus prévisible."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "global-modification"
      ],
      "title": "Modifier une globale",
      "statement": "Pour modifier une variable globale dans une\nfonction, quel mot-clé utiliser ?",
      "options": [
        {
          "text": "`nonlocal`",
          "correct": false,
          "feedback": "Pas tout à fait : `nonlocal` est pour les\nfonctions imbriquées (variable du scope\nintermédiaire), pas pour les globales.\n"
        },
        {
          "text": "`global`",
          "correct": true,
          "feedback": "Bonne réponse : `global x` au début de la\nfonction permet d'**écrire** dans `x`\nglobal. Sans cela, `x = ...` créerait une\nlocale qui masque la globale.\n"
        },
        {
          "text": "`outer`",
          "correct": false,
          "feedback": "Erreur : pas de mot-clé `outer` en Python.\n"
        },
        {
          "text": "`var`",
          "correct": false,
          "feedback": "Erreur : pas en Python (JavaScript, oui).\n"
        }
      ],
      "explanation": "Bonne pratique : limiter l'usage de `global`.\nPréférer le passage par paramètre + retour\nexplicite, beaucoup plus clair et testable."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "retour-multiple"
      ],
      "title": "Retour multiple",
      "statement": "Que renvoie cette fonction ?\n```python\ndef min_max(liste):\n    return min(liste), max(liste)\n```",
      "options": [
        {
          "text": "Un tuple `(min, max)`",
          "correct": true,
          "feedback": "Bonne réponse : Python utilise les tuples\npour les retours multiples. À la\nréception : `mn, mx = min_max(liste)`\n(déballage).\n"
        },
        {
          "text": "Une liste de deux éléments",
          "correct": false,
          "feedback": "Erreur : virgule sans crochets = tuple.\n"
        },
        {
          "text": "Un dictionnaire",
          "correct": false,
          "feedback": "Erreur : un dict s'écrit avec `{}`.\n"
        },
        {
          "text": "Erreur : on ne peut pas avoir deux retours",
          "correct": false,
          "feedback": "Erreur : Python le permet via le tuple.\n"
        }
      ],
      "explanation": "Idiome courant : renvoyer plusieurs valeurs\nsans définir de classe ad hoc. Pratique pour\nles petites fonctions « utilitaires »."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "callable"
      ],
      "title": "Fonction comme valeur",
      "statement": "```python\ndef carre(x): return x * x\n\nf = carre\nprint(f(3))\n```\nQue se passe-t-il ?",
      "options": [
        {
          "text": "Erreur : on ne peut pas affecter une fonction",
          "correct": false,
          "feedback": "Erreur : Python permet d'affecter des\nfonctions à des variables.\n"
        },
        {
          "text": "Affiche 3",
          "correct": false,
          "feedback": "Erreur : la fonction est bien appelée.\n"
        },
        {
          "text": "Affiche 9",
          "correct": true,
          "feedback": "Bonne réponse : en Python, les fonctions\nsont des **objets de première classe**.\n`f` pointe vers la même fonction que\n`carre`. `f(3)` appelle la fonction sur 3.\n"
        },
        {
          "text": "Affiche `<function carre>`",
          "correct": false,
          "feedback": "Erreur : `f(3)` appelle, donc renvoie 9.\n"
        }
      ],
      "explanation": "Cette propriété rend possibles : les\ncallbacks, les décorateurs, la programmation\nfonctionnelle (`map`, `filter`, etc.)."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "portee-locale"
      ],
      "title": "Portée locale",
      "statement": "```python\nx = 10\n\ndef f():\n    x = 20\n    print(x)\n\nf()\nprint(x)\n```\nQu'affiche ce code ?",
      "options": [
        {
          "text": "```\n10\n10\n```\n",
          "correct": false,
          "feedback": "Erreur : la fonction affiche bien 20 (sa\nlocale).\n"
        },
        {
          "text": "Une exception",
          "correct": false,
          "feedback": "Erreur : code valide.\n"
        },
        {
          "text": "```\n20\n20\n```\n",
          "correct": false,
          "feedback": "Erreur : le `x` interne à `f` est une\nvariable **locale** distincte de la\nvariable globale.\n"
        },
        {
          "text": "```\n20\n10\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : l'affectation `x = 20`\ndans la fonction crée une variable\n**locale** ; elle masque la globale le\ntemps de l'appel mais ne la modifie pas.\nAprès l'appel, la globale vaut toujours\n10.\n"
        }
      ],
      "explanation": "Notion de **portée** (scope) : une variable\ndéfinie dans une fonction n'existe qu'à\nl'intérieur. C'est précieux pour\nl'isolation, mais source de confusion\nclassique."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "retour-vs-affichage"
      ],
      "title": "Renvoyer ou afficher ?",
      "statement": "Quelle est la différence entre une fonction qui\n**renvoie** une valeur et une fonction qui\n**affiche** une valeur ?",
      "options": [
        {
          "text": "`print` est plus rapide que `return`",
          "correct": false,
          "feedback": "Erreur : la performance n'est pas le sujet.\n"
        },
        {
          "text": "Une fonction qui renvoie produit une valeur réutilisable par l'appelant via `return` ; une fonction qui affiche écrit à l'écran avec `print` mais ne renvoie rien d'utilisable",
          "correct": true,
          "feedback": "Bonne réponse : une valeur renvoyée peut être\nstockée dans une variable, transmise à une\nautre fonction, comparée. `print` se contente\nd'écrire un texte à l'écran. Si on a besoin\nde la valeur, il faut un `return`.\n"
        },
        {
          "text": "`return` n'existe pas en Python",
          "correct": false,
          "feedback": "Erreur : `return` est l'instruction standard\npour renvoyer une valeur depuis une\nfonction.\n"
        },
        {
          "text": "Aucune différence",
          "correct": false,
          "feedback": "Erreur : la confusion entre les deux est l'un\ndes bugs les plus classiques. `print` ne\nrend rien d'exploitable.\n"
        }
      ],
      "explanation": "Bug classique : écrire `def carre(x): print(x*x)`\nau lieu de `def carre(x): return x*x`. Avec la\npremière version, on ne peut pas faire\n`y = carre(3) * 2` : `y` vaut `None`. La\nseconde version permet bien `y = 18`."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "debug-print-vs-return"
      ],
      "title": "print vs return",
      "statement": "Pourquoi ce code n'affiche-t-il **rien** sur la\nconsole ?\n```python\ndef carre(x):\n    return x * x\n\ncarre(5)\n```",
      "options": [
        {
          "text": "Parce que la fonction n'est pas appelée",
          "correct": false,
          "feedback": "Erreur : `carre(5)` est bien un appel.\n"
        },
        {
          "text": "Parce que `5` n'est pas un entier",
          "correct": false,
          "feedback": "Erreur : 5 est un entier.\n"
        },
        {
          "text": "Parce que `return` ne fait pas d'affichage : la valeur 25 est calculée puis ignorée",
          "correct": true,
          "feedback": "Bonne réponse : pour voir un affichage,\nécrire `print(carre(5))` ou modifier la\nfonction pour utiliser `print` (mais\nalors plus de retour).\n"
        },
        {
          "text": "Parce que la fonction est buggée",
          "correct": false,
          "feedback": "Erreur : la fonction est correcte, elle\nrenvoie 25.\n"
        }
      ],
      "explanation": "Confusion classique des débutants : penser\nque `return` affiche. `print` et `return`\nsont **deux mécanismes différents**."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "decomposition"
      ],
      "title": "Décomposer",
      "statement": "Pourquoi est-il recommandé de **décomposer** un\nprogramme en fonctions ?",
      "options": [
        {
          "text": "Pour économiser de la mémoire",
          "correct": false,
          "feedback": "Erreur : pas un avantage notable.\n"
        },
        {
          "text": "Parce que le compilateur le demande",
          "correct": false,
          "feedback": "Erreur : Python est interprété, et il ne\nl'exige pas.\n"
        },
        {
          "text": "Pour la lisibilité, la réutilisation et la testabilité : chaque fonction fait une chose, on peut la tester séparément",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'un des principes\nfondamentaux du génie logiciel\n(« divide and conquer »). Une fonction\ncourte et bien nommée vaut mieux qu'un\ngros bloc de code.\n"
        },
        {
          "text": "Parce que c'est plus court d'écrire",
          "correct": false,
          "feedback": "Pas forcément plus court : c'est même\nparfois plus long.\n"
        }
      ],
      "explanation": "Maxime : « une fonction = une responsabilité ».\nSi une fonction fait plusieurs choses, la\ndécouper en plusieurs petites."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "scope-piege"
      ],
      "title": "Piège de portée",
      "statement": "```python\nx = 10\n\ndef f():\n    x = 20\n    return x\n\nprint(f())\nprint(x)\n```\nQuel est le résultat ?",
      "options": [
        {
          "text": "```\n10\n10\n```\n",
          "correct": false,
          "feedback": "Erreur : `f()` renvoie sa propre `x` locale\n(= 20).\n"
        },
        {
          "text": "```\n20\n20\n```\n",
          "correct": false,
          "feedback": "Erreur : sans `global x`, le `x = 20` dans\n`f` crée une variable **locale**, qui ne\nmodifie pas la globale.\n"
        },
        {
          "text": "```\n20\n10\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : `x` à l'intérieur de `f`\nest local et masque la globale. À l'extérieur,\nla globale `x` reste à 10.\n"
        },
        {
          "text": "Erreur",
          "correct": false,
          "feedback": "Erreur : aucune erreur.\n"
        }
      ],
      "explanation": "Cas typique d'**ombrage** (shadowing). Source\nde confusion fréquente. Pour éviter,\nprivilégier le passage en paramètre."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "retour-conditionnel"
      ],
      "title": "Retours multiples",
      "statement": "```python\ndef signe(x):\n    if x > 0:\n        return 1\n    if x < 0:\n        return -1\n    return 0\n```\nPourquoi ce code marche-t-il sans `elif` ?",
      "options": [
        {
          "text": "Parce que `return` interrompt la fonction : si `x > 0`, le premier `return 1` empêche l'exécution des suivants",
          "correct": true,
          "feedback": "Bonne réponse : `return` est plus fort\nqu'un break, il quitte la fonction\nentièrement. Pas besoin d'`elif`. Code\nplus plat et lisible.\n"
        },
        {
          "text": "Parce que Python comprend automatiquement",
          "correct": false,
          "feedback": "Python n'a aucune capacité\nd'inférence sur l'intention\ndu code. Si chaque branche\nfonctionne correctement\nici, c'est uniquement parce\nque `return` quitte la\nfonction immédiatement.\n"
        },
        {
          "text": "Parce que les `if` sont équivalents à des `elif`",
          "correct": false,
          "feedback": "Erreur : ils ne sont pas mutuellement\nexclusifs sans `return` (chaque `if`\nserait testé).\n"
        },
        {
          "text": "Parce qu'il n'y a qu'un seul `else`",
          "correct": false,
          "feedback": "Erreur : il n'y a aucun `else`.\n"
        }
      ],
      "explanation": "Idiome « early return » : sortir tôt des cas\nparticuliers, simplifier la logique."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "parametre-mutable"
      ],
      "title": "Paramètre mutable",
      "statement": "```python\ndef ajouter(liste, x):\n    liste.append(x)\n\nma_liste = [1, 2]\najouter(ma_liste, 3)\nprint(ma_liste)\n```\nQue produit ce code ?",
      "options": [
        {
          "text": "`[3]`",
          "correct": false,
          "feedback": "Erreur : on **ajoute**, on ne remplace pas.\n"
        },
        {
          "text": "`[1, 2, 3]`",
          "correct": true,
          "feedback": "Bonne réponse : les listes sont passées\npar **référence**. La fonction modifie\ndonc directement la liste de l'appelant,\nce qui peut surprendre.\n"
        },
        {
          "text": "Erreur",
          "correct": false,
          "feedback": "Erreur : code valide.\n"
        },
        {
          "text": "`[1, 2]`",
          "correct": false,
          "feedback": "Erreur : la liste **est** modifiée par la\nfonction.\n"
        }
      ],
      "explanation": "En Python, lorsqu'on passe une variable à une\nfonction, on transmet une référence vers la\nmême valeur. Pour les types **mutables**\n(listes, dicts), les modifications dans la\nfonction affectent donc l'original. Pour les\n**immuables** (int, str, tuple), aucun effet\nde bord n'est possible."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "piege-default-mutable"
      ],
      "title": "Piège du défaut mutable",
      "statement": "```python\ndef ajouter(x, liste=[]):\n    liste.append(x)\n    return liste\n\nprint(ajouter(1))\nprint(ajouter(2))\n```\nQuel résultat (surprenant) ?",
      "options": [
        {
          "text": "Erreur",
          "correct": false,
          "feedback": "Erreur : code valide.\n"
        },
        {
          "text": "```\n[1]\n[1, 2]\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'un des **pièges\ncélèbres** de Python. Le défaut `[]` est\npartagé entre tous les appels. Solution :\nutiliser `liste=None` puis\n`if liste is None: liste = []`.\n"
        },
        {
          "text": "```\n[1, 2]\n[1, 2]\n```\n",
          "correct": false,
          "feedback": "Erreur : seulement le premier appel reçoit\njuste `[1]`.\n"
        },
        {
          "text": "```\n[1]\n[2]\n```\n",
          "correct": false,
          "feedback": "On pourrait s'y attendre, mais c'est faux.\nPython évalue la valeur par défaut **une\nseule fois**, à la définition de la\nfonction.\n"
        }
      ],
      "explanation": "Bonne pratique : ne jamais utiliser un\nmutable comme valeur par défaut. Préférer\n`None` et tester explicitement."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "statement": "Parmi les affirmations suivantes sur les\nfonctions Python, laquelle est **fausse** ?",
      "options": [
        {
          "text": "Les fonctions Python peuvent être passées comme paramètres d'autres fonctions",
          "correct": false,
          "feedback": "Vrai : objets de première classe.\n"
        },
        {
          "text": "On peut renvoyer plusieurs valeurs avec un tuple (ex. `return a, b`).",
          "correct": false,
          "feedback": "Vrai : idiome Python pour les retours\nmultiples.\n"
        },
        {
          "text": "Une fonction sans `return` renvoie implicitement `None`",
          "correct": false,
          "feedback": "Vrai : convention Python.\n"
        },
        {
          "text": "Les variables locales d'une fonction sont accessibles depuis l'extérieur",
          "correct": true,
          "feedback": "Faux (donc bonne réponse) : les variables\nlocales sont confinées à la fonction.\nC'est le principe de la **portée** (scope).\n"
        }
      ],
      "explanation": "Mnémonique : portée = « ce qui est défini\ndans une fonction reste dans la fonction »\n(sauf retour explicite)."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "composition",
        "appels-imbriques"
      ],
      "title": "Composition d'appels de fonctions",
      "statement": "On considère le code suivant :\n\n```python\ndef carre(x):\n    return x * x\n\ndef incremente(x):\n    return x + 1\n\na = carre(incremente(3))\n```\n\nQuelle est la valeur de `a` après l'exécution ?",
      "options": [
        {
          "text": "$10$",
          "correct": false,
          "feedback": "Erreur : on a additionné le résultat de\n`incremente(3) = 4` à un autre nombre. Mais\n`carre` calcule un carré, pas une somme. La bonne\nopération est $4 \\times 4$.\n"
        },
        {
          "text": "$7$",
          "correct": false,
          "feedback": "Erreur : confusion sur l'ordre des opérations.\nPython évalue toujours les fonctions de\nl'intérieur vers l'extérieur, donc\n`incremente(3)` est évalué d'abord, puis le\nrésultat est passé à `carre`.\n"
        },
        {
          "text": "$9$",
          "correct": false,
          "feedback": "Erreur : on a appliqué seulement `carre(3)`, sans\ntenir compte de `incremente`. Or `incremente`\nest appliqué en premier, à $3$, ce qui donne $4$.\n"
        },
        {
          "text": "$16$",
          "correct": true,
          "feedback": "Bonne réponse : Python évalue d'abord l'argument\nle plus interne, soit `incremente(3) = 4`, puis\napplique `carre` à ce résultat : $4 \\times 4 =\n16$.\n"
        }
      ],
      "explanation": "Pour évaluer une expression `f(g(x))`, Python\ncalcule d'abord `g(x)`, puis applique `f` à ce\nrésultat. Cette composition est l'un des moyens\nles plus simples de combiner des fonctions\nréutilisables, sans variables intermédiaires."
    },
    {
      "id": "q27",
      "difficulty": 2,
      "skills": [
        "annotations",
        "type-hint"
      ],
      "title": "Annotations de type",
      "statement": "On lit la signature suivante :\n\n```python\ndef aire(longueur: float, largeur: float) -> float:\n```\n\nQue signifie la flèche `->` et l'annotation `float`\nqui la suit ?",
      "options": [
        {
          "text": "C'est une annotation indiquant que la fonction est censée renvoyer un flottant ; cela documente l'intention sans imposer de vérification à l'exécution",
          "correct": true,
          "feedback": "Bonne réponse : la flèche introduit\nl'annotation du type renvoyé. Comme les\nannotations de paramètres, elle est optionnelle\net purement informative ; les outils comme\n`mypy` peuvent ensuite vérifier la cohérence\ndes types statiquement.\n"
        },
        {
          "text": "La syntaxe est invalide en Python",
          "correct": false,
          "feedback": "Erreur : les annotations de type sont\nparfaitement valides depuis Python $3{.}5$. Elles\nsont aujourd'hui largement utilisées pour\naméliorer la lisibilité et la sûreté du code.\n"
        },
        {
          "text": "La flèche transforme le résultat en flottant avant de le renvoyer",
          "correct": false,
          "feedback": "Erreur : les annotations n'effectuent **aucune\nconversion**. La fonction renvoie strictement la\nvaleur retournée par son `return`, sans\nmodification.\n"
        },
        {
          "text": "La fonction renvoie obligatoirement un nombre flottant, sinon Python lève une exception",
          "correct": false,
          "feedback": "Erreur : Python ne vérifie pas les annotations à\nl'exécution. La fonction peut tout à fait\nrenvoyer un autre type sans erreur. Les\nannotations servent uniquement à la\ndocumentation et aux outils d'analyse statique\n(`mypy`, IDE).\n"
        }
      ],
      "explanation": "L'annotation `param: type` documente le type\nattendu d'un paramètre, l'annotation `-> type`\ndocumente celui de la valeur de retour. Ces\nindications n'ont aucun effet à l'exécution mais\nsont précieuses pour la lecture du code et pour les\nanalyseurs statiques."
    },
    {
      "id": "q28",
      "difficulty": 3,
      "skills": [
        "effet-de-bord",
        "mutabilite"
      ],
      "title": "Effet de bord sur une liste paramètre",
      "statement": "On exécute le code suivant :\n\n```python\ndef ajouter_zero(t):\n    t.append(0)\n\nma_liste = [1, 2, 3]\najouter_zero(ma_liste)\nprint(ma_liste)\n```\n\nQue va afficher `print` ?",
      "options": [
        {
          "text": "`[1, 2, 3, 0]`",
          "correct": true,
          "feedback": "Bonne réponse : `t` et `ma_liste` désignent le\n**même objet liste** en mémoire. La méthode\n`append` modifie cet objet ; après l'appel,\n`ma_liste` reflète donc le changement.\n"
        },
        {
          "text": "Le code lève une erreur car `t` n'est pas connu en dehors de la fonction",
          "correct": false,
          "feedback": "Erreur : `t` n'est utilisé qu'à l'intérieur de\nla fonction, mais la liste qu'il référence\ncontinue d'exister à l'extérieur. Python passe\nles paramètres par **référence d'objet**.\n"
        },
        {
          "text": "`[1, 2, 3]`",
          "correct": false,
          "feedback": "Erreur : la fonction modifie effectivement la\nliste passée en paramètre. La méthode `append`\nagit sur l'objet partagé entre l'appelant et la\nfonction, donc la modification est visible à\nl'extérieur.\n"
        },
        {
          "text": "`[0, 1, 2, 3]`",
          "correct": false,
          "feedback": "Erreur : `append` ajoute en fin de liste, pas\nen début. Pour insérer à l'index $0$, il\nfaudrait écrire `t.insert(0, 0)`.\n"
        }
      ],
      "explanation": "Quand un paramètre est un objet **mutable** (liste,\ndictionnaire, ensemble), une fonction peut le\nmodifier en place sans avoir à le renvoyer. C'est un\neffet de bord, à utiliser avec parcimonie : il rend\nle comportement moins prévisible et complique la\nlecture du code."
    },
    {
      "id": "q29",
      "difficulty": 2,
      "skills": [
        "predicat",
        "booleen"
      ],
      "title": "Fonction prédicat",
      "statement": "On souhaite écrire une fonction `est_pair(n)` qui\nrenvoie `True` si l'entier `n` est pair, `False`\nsinon. Quelle écriture est la plus claire et la plus\nidiomatique ?",
      "options": [
        {
          "text": "```python\ndef est_pair(n):\n    if n % 2 == 0:\n        return True\n    else:\n        return False\n```\n",
          "correct": false,
          "feedback": "Code correct mais redondant : on construit un\nbooléen à partir d'un test booléen. Préférer\n`return n % 2 == 0`, qui exprime directement\nl'idée.\n"
        },
        {
          "text": "```python\ndef est_pair(n):\n    return n % 2 == 0\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : on renvoie directement la\nvaleur du test booléen, sans passer par un\n`if`. C'est plus court, plus lisible, et c'est\nl'idiome Python pour ce qu'on appelle un\n**prédicat**.\n"
        },
        {
          "text": "```python\ndef est_pair(n):\n    return n / 2\n```\n",
          "correct": false,
          "feedback": "Erreur : la division `n / 2` renvoie un\nflottant (par exemple $1{,}5$ ou $2{,}0$),\npas un booléen. Pour tester la parité, il faut\nutiliser le **reste** (modulo `%`), pas le\nquotient.\n"
        },
        {
          "text": "```python\ndef est_pair(n):\n    print(n % 2 == 0)\n```\n",
          "correct": false,
          "feedback": "Erreur : la fonction ne renvoie rien (elle\nrenvoie `None`). Elle ne peut donc pas être\nutilisée dans un test comme\n`if est_pair(x):`. Préférer `return` plutôt\nque `print`.\n"
        }
      ],
      "explanation": "Une fonction qui renvoie `True` ou `False` est\nappelée **prédicat**. Convention : nommer ces\nfonctions avec un préfixe `est_`, `a_`, `verifie_`\nou similaire, pour signaler immédiatement qu'elles\nrenvoient un booléen."
    }
  ]
}