Solutions — série 10-3 (lire du code, lire la doc, demander de l'aide)

Rappel de la règle d'or : on n'ouvre ce fichier qu'après avoir terminé (ou sérieusement séché sur) la série. Particularité de cette série : pour les recherches doc, compare aussi le CHEMIN parcouru (par quelle page es-tu passé ?) ; pour les explorations, le test n'est pas la conformité à ma carte, mais « quelqu'un d'autre pourrait-il naviguer avec la tienne ? ».


Exercice facile a — trois réponses, doc officielle uniquement

Raisonnement — Pour chaque question, on choisit d'abord la PAGE probable de la Library Reference (méthodes de str → page « Built-in Types » ; fonction native → page « Built-in Functions » ; module → sa page), puis on balaie. On note la page source : c'est la moitié de l'exercice.

Solution

1. str.removesuffix(suffix)
   PAGE : Library Reference → Built-in Types → « String Methods ».
   RÉPONSE : renvoie une copie de la chaîne SANS le suffixe s'il est
   présent, sinon la chaîne inchangée (jamais d'erreur si absent).
   L'encadré dit : « Added in version 3.9 » — voilà pourquoi les vieux
   tutos font encore s[:-len(suffix)] à la main.

2. sorted(iterable, /, *, key=None, reverse=False)
   PAGE : Library Reference → « Built-in Functions » → sorted.
   RÉPONSE : le paramètre est reverse=True. NON, on ne peut pas le passer
   en positionnel : il est à droite du « * », donc mot-clé uniquement —
   sorted(data, True) lève un TypeError.

3. csv.DictReader
   PAGE : Library Reference → « csv — CSV File Reading and Writing ».
   RÉPONSE : csv.DictReader lit chaque ligne comme un dictionnaire dont
   les clés viennent de la ligne d'en-tête (ou du paramètre fieldnames).
   Bonus doc : son symétrique csv.DictWriter existe juste en dessous.

Pourquoi ça marche — Les trois réponses sont sur trois pages différentes de la MÊME Library Reference : savoir laquelle ouvrir (types natifs / fonctions natives / page du module) est la vraie compétence, et elle s'acquiert en dix recherches. help(sorted) dans le terminal donnait d'ailleurs la réponse 2 sans navigateur.

Erreur classique sur cet exercice — Taper la question dans un moteur de recherche et cliquer le premier résultat : un blog de 2019, où removesuffix n'existe pas encore. L'énoncé imposait docs.python.org précisément pour t'obliger à APPRENDRE la géographie de la doc — c'est un investissement, pas un détour.

Variante plus difficile — Toujours doc uniquement : quelle est la différence entre list.sort() et sorted() (deux différences) ? Et que signifie l'encadré « This method sorts the list in place » ? (Piste : la page « Sorting Techniques » du HOWTO officiel les compare — les HOWTO sont le quatrième étage, méconnu, de docs.python.org.)


Exercice facile b — décoder deux signatures

Raisonnement — On repère / et *, qui découpent la signature en trois zones : positionnel uniquement | au choix | mot-clé uniquement. Puis les =valeur disent ce qui est facultatif.

Solution

sorted(iterable, /, *, key=None, reverse=False)
  « iterable se passe en positionnel uniquement (sorted(data), jamais
  sorted(iterable=data)) ; key et reverse se passent par mot-clé
  uniquement, et sont facultatifs (défauts : None et False). »
  Il n'y a AUCUN paramètre "au choix" : le / et le * sont côte à côte.

json.dumps(obj, *, skipkeys=False, ensure_ascii=True, ...)
  « obj peut se passer en positionnel ; TOUT le reste est mot-clé
  uniquement, avec des valeurs par défaut. »

json.dumps(data, 2)      → TypeError : dumps ne prend qu'UN positionnel,
                           le 2 essaie d'être un deuxième → interdit par *.
json.dumps(data, indent=2) → OK : indent passe par mot-clé, comme exigé.

Pourquoi ça marche/ et * ne sont pas des paramètres mais des séparateurs de zones. Les auteurs de la stdlib forcent le mot-clé pour les options nombreuses (dumps en a huit !) : dumps(data, 2) serait illisible — 2 quoi ? Le mot-clé obligatoire rend chaque appel auto-documenté. Tu peux appliquer la même idée à tes propres fonctions dès qu'elles ont plus de deux options.

Erreur classique sur cet exercice — Lire * comme « et caetera » ou comme l'*args du niveau 04. Seul, entre deux virgules, * est un séparateur — c'est un piège de lecture connu, et il disparaît dès qu'on a décodé trois signatures.

Variante plus difficile — Dans la doc, trouve la signature de open(). Combien de paramètres acceptent le positionnel ? Pourquoi crois-tu que open(path, "r", encoding="utf-8") est la forme recommandée alors que mode pourrait aussi se passer en mot-clé ?


Exercice facile c — réécrire la question

Raisonnement — On reconstruit les quatre pièces à partir du soupir « python veut pas ouvrir mon fichier » : le but (contexte), un programme minimal complet, le traceback exact (ici un FileNotFoundError plausible), et les pistes tentées.

Solution — une version au gabarit (les détails sont inventés, comme l'énoncé l'autorisait — c'est la STRUCTURE qui est corrigée) :

CONTEXTE — Python 3.14, Windows 11, VS Code. Mon script lit un fichier de
notes situé dans le même dossier que lui, mais il ne le trouve pas quand je
le lance depuis VS Code.

CODE MINIMAL (reproduit le problème seul) :
    # read_notes.py — dans C:\Users\jean\notes-app\
    with open("notes.txt", encoding="utf-8") as f:
        print(f.read())
Le fichier notes.txt existe bien dans C:\Users\jean\notes-app\ (vérifié).

ERREUR EXACTE :
    Traceback (most recent call last):
      File "C:\Users\jean\notes-app\read_notes.py", line 2, in <module>
        with open("notes.txt", encoding="utf-8") as f:
    FileNotFoundError: [Errno 2] No such file or directory: 'notes.txt'

DÉJÀ TENTÉ —
    - Lancé depuis le terminal DANS le dossier : ça marche. Depuis VS Code
      (bouton Run) : ça plante. Le problème dépend donc d'où je lance.
    - print(os.getcwd()) dans le script : VS Code affiche C:\Users\jean,
      pas le dossier du script.
    Ma question exacte : comment faire pour que le script trouve son
    fichier QUEL QUE SOIT l'endroit d'où on le lance ?

Pourquoi ça marche — Regarde ce que la pièce 4 a produit : en documentant les essais, l'auteur a DIAGNOSTIQUÉ le problème (répertoire de travail ≠ dossier du script — le classique du niveau 05) et transformé un soupir en question précise et technique. C'est l'effet canard : la moitié du travail de réponse est déjà faite, et n'importe qui (humain ou IA) peut maintenant répondre en une ligne (pathlib.Path(__file__).parent / "notes.txt").

Erreur classique sur cet exercice — Réécrire poliment sans les pièces : « Bonjour, j'ai un souci d'ouverture de fichier en Python, quelqu'un aurait-il une idée ? Merci d'avance » — plus courtois, toujours irrépondable. La politesse n'est pas le problème ; l'information manquante, si.

Variante plus difficile — Prends une vraie question au hasard sur un forum d'entraide (Stack Overflow, section Python) et note les pièces manquantes. Puis lis les commentaires : tu verras les répondeurs réclamer EXACTEMENT ces pièces — le gabarit n'est pas une invention pédagogique, c'est le standard de fait.


Exercice moyen a — la carte du paquet json

Raisonnement — Passes 3 et 4 de la méthode : squelette (les def et class de chaque module), puis suivre le fil de json.load(f) à travers les fichiers. On ne lit PAS les corps de fonctions, sauf sur le fil suivi.

Solution — la carte de référence :

CARTE — paquet json (stdlib, ~1 400 lignes)
Dossier : <install Python>\Lib\json\

__init__.py  la FAÇADE : dump(), dumps(), load(), loads(), detect_encoding.
             C'est le seul module que l'utilisateur touche. En bas,
             _default_decoder et _default_encoder : des instances toutes
             prêtes, partagées, pour les appels sans options.
__main__.py  point d'entrée de `python -m json` : 6 lignes utiles, qui
             délèguent tout à json.tool.main().
tool.py      le CLI (argparse : --sort-keys, --indent, [infile], [outfile]).
decoder.py   classe JSONDecoder (+ JSONDecodeError) : texte JSON → objets.
encoder.py   classe JSONEncoder : objets → texte JSON.
scanner.py   make_scanner : la machine bas niveau qui découpe le texte,
             utilisée par decoder. (73 lignes, on n'a PAS besoin de la lire.)

LE FIL de json.load(f) :
1. __init__.py:278  def load(fp, ...)  → une seule vraie ligne :
                    return loads(fp.read(), ...)   # lit TOUT le fichier
2. __init__.py:304  def loads(s, ...)  → cas sans options :
                    return _default_decoder.decode(s)
3. decoder.py:340   JSONDecoder.decode(s)  → appelle raw_decode(s) puis
                    vérifie qu'il ne reste rien après le JSON
4. decoder.py:351   raw_decode() → self.scan_once(s, idx) — construit par
                    scanner.make_scanner : c'est LÀ que le texte devient
                    dicts/listes/str/nombres.

TROIS DÉCOUVERTES au passage :
- load() n'est qu'un emballage de loads() : une ligne. Les façades
  minces sont partout dans le bon code.
- les instances par défaut partagées (_default_decoder) : on ne recrée
  pas un objet à chaque appel sans options — idée volable.
- la doc de chaque fonction est DANS le docstring, formaté comme la doc
  en ligne : help(json.loads) et docs.python.org disent la même chose,
  et pour cause.

Pourquoi ça marche — Le fil ne demande d'ouvrir que DEUX fichiers sur six, et une vingtaine de lignes en tout. La carte, elle, se construit avec les docstrings de tête et les def/class — c'est tout l'intérêt des quatre passes : l'effort est proportionnel au besoin, pas à la taille du projet.

Erreur classique sur cet exercice — S'enfoncer dans scanner.py (ou pire, découvrir qu'il existe une version C cachée — c_make_scanner — et vouloir la comprendre aussi). C'était le piège annoncé : le fil s'arrête quand on peut DIRE où la conversion se fait ; comprendre COMMENT elle se fait octet par octet est un autre projet, non demandé. « Je sais où c'est » suffit.

Variante plus difficile — Suis le fil INVERSE : json.dumps(data, indent=2) jusqu'à la ligne qui écrit réellement les espaces de l'indentation (encoder.py). Plus dur : l'encodeur a deux chemins (avec et sans accélération C) — reste sur le chemin Python pur.


Exercice moyen b — la question parfaite, rétroactive

Raisonnement — Pas de solution unique (c'est TON bug), mais la grille d'auto-correction, pièce par pièce.

Solution — la grille :

□ CONTEXTE : le BUT y est (pas seulement la technique) ; version Python.
□ CODE MINIMAL : il est COMPLET (exécutable tel quel par un inconnu),
  et tu l'as VRAIMENT exécuté — il reproduit le bug. S'il fait plus de
  ~15 lignes, il reste sûrement du gras à retirer.
□ TRACEBACK : intégral, en texte, non paraphrasé.
□ PISTES : 2-3 essais AVEC leur résultat (« j'ai essayé X → toujours
  pareil », pas juste « j'ai essayé des trucs »).
□ LA NOTE FINALE : as-tu vu quelque chose de neuf en rédigeant ?

Sur la dernière case : la réponse honnête est presque toujours OUI — en général au moment de construire le code minimal (le bug disparaît quand on retire tel morceau → le coupable était ce morceau). Si tu as répondu non, vérifie que ton code minimal a vraiment été RÉDUIT, pas juste copié.

Pourquoi ça marche — L'exercice rejoue un bug RÉSOLU précisément pour que tu observes le phénomène sans la pression du blocage : la rédaction structurée EST une technique de débogage, pas une corvée sociale. La prochaine fois, tu la déclencheras pendant le blocage, pas après.

Erreur classique sur cet exercice — Écrire le « code minimal » de mémoire sans le ré-exécuter. Un code minimal qui ne reproduit pas le bug est pire qu'inutile : il enverrait un répondeur chercher au mauvais endroit. La règle : pas de question postée sans avoir fait tourner le minimal une dernière fois.

Variante plus difficile — Poste réellement la question (à Claude, par exemple) et compare la réponse obtenue avec ta solution de l'époque. Puis applique la règle du niveau : chaque ligne de la réponse que tu ne peux pas réexpliquer déclenche un « explique-moi cette ligne ».


Exercice moyen c — un module inconnu, doc seule

Raisonnement — Pas de solution unique (le module dépend de ton projet final). Voici l'exemple de référence avec datetime — le choix le plus fréquent, car tout projet qui date des choses en a besoin.

Solution — script d'exemple, trois usages sourcés dans la doc :

# discover_datetime.py — trois fonctions de datetime, doc officielle seule
from datetime import date, timedelta

# 1. date.today() — « Return the current local date. »
#    (Library Reference → datetime → classe date, méthodes de classe)
today = date.today()
print(f"today: {today}")                    # today: 2026-07-05

# 2. date.fromisoformat() — « Return a date corresponding to a date_string
#    given in the ISO 8601 format » — parfait pour lire mes CSV.
start = date.fromisoformat("2026-06-20")
print(f"start: {start}")

# 3. timedelta — « A duration expressing the difference between two
#    dates » : la soustraction de deux dates en renvoie un.
elapsed = today - start
print(f"days elapsed: {elapsed.days}")      # days elapsed: 15
streak_limit = today - timedelta(days=1)    # « hier » calculé, pas bricolé
print(f"yesterday: {streak_limit}")

Pourquoi ça marche — Chaque usage est appuyé sur une phrase PRÉCISE de la doc (recopiée en commentaire — c'était la consigne) : on apprend le module par son contrat, pas par un exemple de blog approximatif. Et le choix des trois fonctions est guidé par le besoin réel (dater, relire, calculer un écart), pas par l'ordre de la page.

Erreur classique sur cet exercice — Choisir le module « au hasard » plutôt que par besoin : la consigne demandait un candidat utile à TON projet final. Un module appris sans usage prévu s'oublie en une semaine ; le même, branché sur un besoin, se retient tout seul. Autre classique avec datetime précisément : confondre le MODULE datetime et la CLASSE datetime.datetime — la doc le signale, les débutants le vivent.

Variante plus difficile — Refais l'exercice avec un module dont la doc est réputée aride : re (expressions régulières) ou argparse en profondeur (groupes mutuellement exclusifs, sous-parseurs). Objectif identique : trois usages, trois citations. C'est l'entraînement réel de l'après-méthode.


Exercice difficile a — l'exploration hors les murs

Raisonnement — Pas de solution unique (chacun son projet), mais la grille de validation de la carte, et les réponses aux blocages prévus par les indices.

Solution — la grille :

□ STRUCTURE : chaque dossier/module de premier niveau a sa ligne de rôle.
□ ENTRÉE : le point d'entrée est identifié ET vérifié (tu as lancé le
  projet au moins une fois — sinon c'est une hypothèse, pas une carte).
□ LE FIL : une fonctionnalité suivie avec la liste des sauts
  (fichier → fonction → fichier → fonction), 3 à 6 sauts typiquement.
□ TROIS VOLS : trois pratiques concrètes à réutiliser (structure de
  dossiers, style de messages d'erreur, organisation des tests, façade
  mince à la json.load…), formulées comme des actions pour TON projet.
□ ZONES DE BRUME : la section « pas compris » existe et est assumée.
□ LE CHRONO : tu peux expliquer le projet à voix haute en 10 minutes.

Sur la technique du « bout du fil » (indice 2) : chercher le texte EXACT d'un message affiché par le programme (grep/Ctrl+Maj+F) est la méthode universelle d'entrée dans un code inconnu — elle marche sur un projet de 300 lignes comme sur un projet de 300 000.

Pourquoi ça marche — La carte n'est pas un résumé du code, c'est un outil de NAVIGATION : la grille vérifie qu'on peut s'en servir (retrouver où sont les choses, refaire le fil), pas qu'elle est exhaustive. La section « pas compris » est obligatoire parce qu'elle inverse le rapport à l'ignorance : cartographiée, une zone de brume est un futur sujet d'apprentissage ; cachée, c'est une source de honte qui fait abandonner.

Erreur classique sur cet exercice — Choisir un projet trop gros (le framework célèbre plutôt que le petit outil) : à 3 000 lignes et plus, les quatre passes demandent des heures et l'exercice décourage. L'autre extrême existe aussi : un script de 80 lignes ne fait pas travailler la méthode. La bonne taille : plusieurs modules, un vrai README, moins de 3 000 lignes.

Variante plus difficile — Écris une issue (réelle ou fictive) pour le projet exploré : un petit bug repéré ou une amélioration du README, formulée au gabarit de la bonne question. Si elle est réelle et que le projet est actif… poste-la. C'est le premier pas de la contribution open source, et ta carte t'a rendu légitime pour le faire.


Exercice difficile b — le plan de progression post-méthode

Raisonnement — Trois listes (sujets tirés des besoins, un projet par sujet, un rythme), plus les mécanismes conservés. Voici un exemple de référence complet — le TIEN doit différer, c'est le principe.

Solution — un plan rempli :

# Plan post-méthode — rédigé le 2026-07-05, à relire le 2026-10-05

## Sujets (choisis parce que mes projets en ont besoin)
1. SQLite (module sqlite3) — mes outils CLI stockent tout en JSON/CSV ;
   dès que je veux filtrer/trier vite, ça coince. Besoin réel, borné.
2. Interfaces web minimales (Flask) — transformer UN outil CLI existant
   en petite page locale. Pas « apprendre le web » : servir MES données.
3. (plus tard) empaquetage : pyproject.toml, pip install -e . — pour que
   mes outils s'appellent par leur nom (extension 2 de la fiche 12).

## Un projet par sujet
1. spendlog v2 : migrer le stockage CSV → SQLite, SANS changer la CLI
   (les tests pytest existants doivent passer inchangés — c'est le filet).
2. spendlog-web : la page « rapport mensuel » servie par Flask en local.
3. packager habitude et l'installer sur ma machine.

## Rythme
- 3 sessions de 45 min par semaine, planifiées (matin).
- 1 revue mensuelle au journal : qu'est-ce qui a avancé, qu'est-ce qui
  bloque, le plan est-il encore le bon ?

## Mécanismes de la méthode que je garde à vie
journal de session · révisions espacées sur les notions neuves · commits
disciplinés (GIT_WORKFLOW) · cadrage avant code pour tout projet > 3 jours
· la règle « ne coller que ce qu'on peut réexpliquer ».

Pourquoi ça marche — Chaque sujet est justifié par une DOULEUR réelle d'un projet existant (le JSON qui coince, l'outil qu'on veut consulter au navigateur) : la motivation est structurelle, pas volontariste. Les projets réutilisent l'existant (spendlog v2 plutôt qu'un projet neuf) — on capitalise. Et la date de relecture en tête transforme le document en rendez-vous, pas en bonne résolution.

Erreur classique sur cet exercice — La liste de courses technologique : « apprendre Django, le machine learning, Rust et les algos » — quatre continents, zéro besoin, zéro projet attaché. C'est le sujet-trop-gros de la leçon 10-1, version carrière. Un plan tient sur une page et sur trois mois ; le reste est du rêve (légitime, mais pas un plan).

Variante plus difficile — Ajoute à ton plan une section « signaux d'abandon » : à quoi reconnaîtras-tu, dans trois mois, que le plan a déraillé (zéro session en deux semaines, un sujet sans projet commencé…), et quelle est la réaction prévue (réduire le plan, pas se flageller). Prévoir sa propre défaillance est une compétence de méthode — la dernière que ce parcours t'enseigne.