freedzhost.cloud

Python module 4 : un vrai projet, testé et fini

Niveau DébutantTemps estimé 17 hÀ lire avant Python module 3 : fonctions, modules, bibliothèques, débogageUn mot inconnu ? Lexique

Dix-sept séances pour transformer tout l'acquis en un projet fini : structurer un programme en fichiers, lui donner une vraie ligne de commande, le tester avec pytest, puis construire le projet final — un outil qui lit vos exports bancaires CSV et résume vos dépenses. C'est ce module qu'on rogne si la vie s'en mêle ; mais c'est lui qui prouve que vous savez programmer.

Jour 43 — Structurer un projet : dossiers et point d'entrée

Objectif : passer du fichier unique au projet organisé, avec un point d'entrée propre.

Théorie (20 min)

Jusqu'ici, un programme = un fichier. Ça tient jusqu'à ~100 lignes ; au-delà, on découpe. La structure minimale d'un projet Python :

mon-projet/
├── outils.py        # les fonctions (calculs, lectures de fichiers)
├── main.py          # le programme principal (entrées, affichage)
└── requirements.txt # les dépendances (jour 35)

main.py utilise les fonctions d'outils.py grâce à un import local — importer son propre fichier marche comme importer un module de la bibliothèque standard :

import outils

total = outils.prix_ttc(100, 0.2)

Le détail qui surprend tout le monde une fois : importer un fichier exécute tout ce qui est à son niveau racine — y compris les print laissés au bas du fichier importé. Pour qu'un fichier serve à la fois de bibliothèque (importé) et de programme (lancé), on range le code de lancement sous le garde-fou standard :

def main():
    print("Programme lancé.")

if __name__ == "__main__":
    main()

__name__ est une variable magique : elle vaut "__main__" quand le fichier est lancé directement, et le nom du fichier quand il est importé. Traduction du garde-fou : « n'exécute main() que si c'est CE fichier qu'on a lancé ». Vous le verrez dans chaque projet Python sérieux ; désormais, vous savez le lire.

Pourquoi if __name__ == "__main__": ?

  1. C'est obligatoire dans tout fichier Python
  2. Pour que le code de lancement ne s'exécute pas quand le fichier est importé
  3. Pour accélérer le démarrage
Réponse

À l'import, __name__ vaut le nom du module : le bloc est sauté. Lancement direct : il vaut "__main__" et le bloc tourne.

Où vont les fonctions réutilisables dans un projet structuré ?

  1. Dans un module à part (outils.py), importé par le programme principal
  2. Toutes dans main.py
  3. Dans requirements.txt
Réponse

Séparer la bibliothèque (outils) du point d'entrée (main) rend les fonctions testables et réutilisables.

Pratique (30 min)

  1. Créez le dossier facturation avec les deux fichiers :
# outils.py
def prix_ttc(prix_ht, taux=0.20):
    """Rend le prix TTC (prix_ht en euros, taux entre 0 et 1)."""
    return prix_ht * (1 + taux)
# main.py
import outils

def main():
    prix = float(input("Prix HT ? "))
    print(f"TTC : {outils.prix_ttc(prix):.2f} €")

if __name__ == "__main__":
    main()
  1. Critère de réussite : python main.py demande un prix et affiche le TTC. Le programme est en deux fichiers, et chacun a un rôle lisible.
  2. Preuve du garde-fou : au REPL, import main — rien ne s'affiche, rien n'est demandé : main() n'a pas été lancé. Tapez ensuite main.main() : le programme répond.
  3. Enlevez le garde-fou de main.py (laissez l'appel main() au niveau racine), refaites import main au REPL : le programme se lance à l'import. Voilà le bug que le garde-fou prévient. Remettez-le.

Tips agent : « découpe ce script en outils.py + main.py avec le garde-fou __main__ » : refonte standard. Piège : il peut laisser du code au niveau racine de outils.py qui s'exécute à l'import. Preuve : python -c "import outils" ne doit rien afficher.

Synthèse (10 min)

Trois lignes : pourquoi découper, ce que fait import sur un fichier local, le sens exact du garde-fou. Sans notes : restructurez un ancien script en deux fichiers.

Jour 44 — argparse : des options en ligne de commande

Objectif : donner à vos scripts de vrais arguments : python script.py fichier.txt au lieu de input().

Théorie (20 min)

Les vrais outils ne posent pas de questions : ils prennent des arguments en ligne de commande, écrits après le nom du programme : python renommer.py photos/ --prefixe vacances. C'est ce qui rend un script enchaînable dans d'autres scripts. Le module argparse (bibliothèque standard) fait l'analyse :

import argparse

def main():
    parser = argparse.ArgumentParser(description="Compte les lignes d'un fichier.")
    parser.add_argument("fichier", help="le fichier à analyser")
    args = parser.parse_args()
    print(f"Analyse de {args.fichier}")

if __name__ == "__main__":
    main()

On décrit les arguments attendus, argparse fait le reste : il remplit args.fichier, il génère l'aide automatiquement (python compte.py --help affiche un mode d'emploi propre), et il refuse poliment un appel incomplet (usage: ... suivi d'une erreur, sans traceback). L'argument écrit sans tirets (fichier) est positionnel : obligatoire, reconnu à sa place.

Un argument qui commence par des tirets est une option : --prefixe vacances. On la déclare avec parser.add_argument("--prefixe", default="fichier") — si l'utilisateur ne la donne pas, la valeur par défaut s'applique.

Que produit gratuitement argparse ?

  1. Les tests du programme
  2. L'aide --help et le refus poli des appels incomplets
  3. La documentation PDF
Réponse

--help et le message d'usage sont générés à partir des déclarations.

Différence entre fichier et --fichier dans add_argument ?

  1. Positionnel obligatoire vs option nommée
  2. Aucune
  3. Le second est en lecture seule
Réponse

Sans tirets : positionnel, obligatoire. Avec tirets : option, qui peut avoir un défaut.

Pratique (30 min)

  1. Créez compte.py en complétant le squelette du cours :
import argparse
from pathlib import Path

def main():
    parser = argparse.ArgumentParser(description="Compte les lignes d'un fichier texte.")
    parser.add_argument("fichier", help="le fichier à analyser")
    parser.add_argument("--mot", default="", help="ne compter que les lignes contenant ce mot")
    args = parser.parse_args()

    lignes = Path(args.fichier).read_text(encoding="utf-8").splitlines()
    if args.mot:
        lignes = [l for l in lignes if args.mot in l]
    print(f"{len(lignes)} lignes.")

if __name__ == "__main__":
    main()
  1. La ligne [l for l in lignes if args.mot in l] est une compréhension de liste : la boucle-append du jour 18 en une ligne. Goûtez-la ; un cours entier lui serait nécessaire, retenez juste qu'elle existe.
  2. Critère de réussite : python compte.py memo.txt affiche le nombre de lignes ; python compte.py memo.txt --mot python ne compte que les lignes qui contiennent « python » ; python compte.py --help affiche l'aide ; python compte.py sans argument affiche l'usage et une erreur propre.
  3. Transformez le renommeur du jour 40 : le dossier devient un argument positionnel, et --prefixe remplace le préfixe en dur. Le script devient réutilisable sur n'importe quel dossier.

Tips agent : « ajoute une CLI argparse à ce script : fichier positionnel, --mot optionnel » : l'agent connaît argparse par cœur. Piège : il peut transformer vos input() en arguments et changer le comportement interactif que vous vouliez garder. Preuve : --help doit refléter exactement les options que VOUS avez demandées.

Synthèse (10 min)

Trois lignes : pourquoi arguments plutôt qu'input, positionnel vs option, --help gratuit. Sans notes : script qui prend un nombre en argument et affiche sa table de multiplication.

Jour 45 — argparse : flags, types et choix

Objectif : des options typées, des interrupteurs vrai/faux, des listes de choix.

Théorie (20 min)

Trois réglages argparse couvrent 95 % des besoins :

Le type. Par défaut, tout argument arrive en chaîne (comme input). parser.add_argument("--taux", type=float, default=0.2) convertit pour vous — et refuse poliment « abc » avec un message d'usage, sans try/except de votre part.

Le flag (interrupteur) : une option sans valeur, dont la seule présence signifie « oui » :

parser.add_argument("--reel", action="store_true", help="appliquer vraiment (sinon : essai à sec)")

args.reel vaut False si l'option est absente, True si elle est là. C'est la forme standard du dry-run du jour 40 : python ranger.py telechargements/ --reel.

Les choix : parser.add_argument("--tri", choices=["nom", "taille", "date"], default="nom") — toute autre valeur est refusée avec la liste des possibles. Fini les fautes de frappe silencieuses.

Convention d'écriture : les options longues prennent deux tirets (--tri), et on ajoute souvent un raccourci à un tiret : parser.add_argument("-t", "--tri", ...) — les deux formes marchent alors.

Que vaut args.reel (action="store_true") quand l'utilisateur n'écrit pas --reel ?

  1. Une erreur
  2. None
  3. False
Réponse

Absent : False. Présent : True. Parfait pour un dry-run par défaut.

À quoi sert choices=[...] ?

  1. À refuser toute valeur hors de la liste, avec un message clair
  2. À proposer une liste déroulante
  3. À trier les arguments
Réponse

La validation est faite par argparse : votre code ne voit que des valeurs valides.

Pratique (30 min)

  1. Reprenez compte.py d'hier : ajoutez --sensibles (store_true) qui rend la recherche de --mot sensible à la casse (par défaut, comparez en minuscules des deux côtés). Testez les deux modes.
  2. Reprennez renommer.py (version argumentée d'hier) : remplacez la variable mode_reel par un flag --reel. Critère de réussite : sans flag, le script n'affiche que le plan ; avec --reel, il renomme. Le dry-run est devenu un réglage de la ligne de commande, pas une modification du code.
  3. Ajoutez au renommeur --tri avec choices ["nom", "taille"] qui trie la liste des fichiers avant de les numéroter (sorted(dossier.glob(...)) par nom, ou key=lambda f: f.stat().st_size par taille).
  4. Vérifiez la validation : --tri vitesse doit être refusé poliment avec la liste des choix.

Tips agent : décrire la CLI AVANT le code (« positionnel : dossier ; options : --reel flag, --tri dans nom|taille ») donne à l'agent une spécification testable. Piège : il invente des options en plus « pour aider ». Preuve : --help ne doit contenir que vos options, et chaque choix invalide doit être refusé.

Synthèse (10 min)

Trois lignes : type=, store_true, choices=. Sans notes : CLI d'un convertisseur km↔miles avec valeur positionnel typé float et --vers à choix.

Jour 46 — logging : le carnet de bord du programme

Objectif : remplacer les prints de diagnostic par des messages horodatés et filtrables.

Théorie (20 min)

Les prints du jour 39 ont deux défauts : ils partent à la poubelle avec la console, et il faut les retirer à la main. Le module logging est le système de journal de bord de Python : des messages classés par niveau de gravité, qu'on active ou filtre sans changer le code :

import logging

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
logging.debug("détail technique")
logging.info("fichier traité : rapport.pdf")
logging.warning("doublon ignoré : facture.pdf")
logging.error("illisible : archive.zip")

Les cinq niveaux, par ordre croissant : DEBUG (détails pour la chasse aux bugs), INFO (le programme avance normalement), WARNING (anomalie non bloquante), ERROR (opération ratée), CRITICAL (le programme ne peut plus continuer). Le level= de basicConfig est un seuil : INFO affiche INFO et tout ce qui est plus grave, masque DEBUG. En chasse au bug, on baisse à DEBUG ; en usage normal, on reste à INFO — sans retirer une seule ligne.

Règle de répartition qui rend un programme lisible : print pour le résultat (ce que l'utilisateur est venu chercher), logging pour le déroulé (ce qui s'est passé en route). Le résultat va à l'écran ; le journal peut aller dans un fichier : logging.basicConfig(filename="ranger.log", ...).

Avec level=logging.WARNING, quels messages s'affichent ?

  1. Tous
  2. WARNING, ERROR et CRITICAL seulement
  3. Uniquement WARNING
Réponse

Le niveau est un seuil : on voit le niveau choisi et tout ce qui est plus grave.

Qu'affiche-t-on avec print, qu'écrit-on avec logging ?

  1. print : le résultat pour l'utilisateur ; logging : le déroulé pour le diagnostic
  2. L'inverse
  3. Peu importe, ce sont les mêmes
Réponse

Résultat à l'écran, journal en coulisses — et le journal peut aller dans un fichier sans toucher au code.

Pratique (30 min)

  1. Reprennez ranger.py (jours 41+45) : remplacez les prints de déplacement par logging.info(...), le cas « aucun fichier » par logging.warning(...), et gardez UN print final : le résumé « 8 fichiers rangés ».
  2. Configurez basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") au début de main.
  3. Critère de réussite : en dry-run, les lignes INFO ... racontent le plan et le print donne le résumé ; passez le seuil à DEBUG et ajoutez un logging.debug qui affiche la cible exacte de chaque rename : il n'apparaît qu'au niveau DEBUG.
  4. Changez basicConfig pour écrire dans ranger.log : l'écran ne montre plus que le résumé, le fichier garde la trace. Relisez le fichier : vous avez un outil qui se souvient de ses passages.

Tips agent : « remplace les prints de diagnostic par du logging à niveaux » : l'agent sait faire. Piège : il monte tout en ERROR ou laisse des print mêlés au logging. Preuve : à seuil WARNING, le programme normal ne dit presque rien ; à DEBUG, il raconte tout.

Synthèse (10 min)

Trois lignes : les cinq niveaux, le seuil, print vs logging. Sans notes : script avec un debug, un info, un warning, dont on change le seuil deux fois.

Jour 47 — pytest : votre premier test

Objectif : écrire et lancer un test automatique qui prouve qu'une fonction fait ce qu'elle promet.

Théorie (20 min)

Jusqu'ici, vous vérifiez vos programmes à la main : vous lancez, vous lisez, vous jugez. Un test automatique écrit ce jugement une fois pour toutes : un petit programme qui appelle votre fonction et vérifie le résultat. Quand vous modifierez le code dans trois mois, le test relancera la preuve en une seconde.

Le cadre standard est pytest (à installer dans la bulle : pip install pytest). Un test est une fonction dont le nom commence par test_, rangée dans un fichier test_*.py :

# test_outils.py
import outils

def test_prix_ttc_20_pourcent():
    assert outils.prix_ttc(100) == 120.0

def test_prix_ttc_taux_reduit():
    assert outils.prix_ttc(100, 0.055) == 105.0

assert (« affirmer ») est un mot-clé Python : si la condition est vraie, il ne fait rien ; si elle est fausse, il lève une erreur que pytest attrape et présente. On lance tous les tests du dossier d'une commande : python -m pytest. Un point par test réussi, un F par échec, et pour chaque échec un rapport qui montre la valeur attendue et la valeur obtenue — un traceback de comparaison.

La discipline qui va avec : quand vous trouvez un bug, écrivez d'abord le test qui le reproduit (il échoue, rouge), puis réparez (il passe, vert). Le test reste : ce bug précis ne reviendra jamais sans qu'on le sache.

Que fait assert x == 4 quand x vaut 5 ?

  1. Rien
  2. Il affiche 5
  3. Il lève une AssertionError que pytest rapporte comme un échec
Réponse

assert ne se manifeste que quand sa condition est fausse.

Comment pytest trouve-t-il vos tests ?

  1. Fichiers test_*.py, fonctions test_*
  2. Il faut les déclarer dans un fichier de config
  3. Il exécute toutes les fonctions du projet
Réponse

La convention de nommage suffit : pytest découvre seul.

Pratique (30 min)

  1. Dans le dossier facturation (jour 43), bulle active : pip install pytest, puis pip freeze > requirements.txt (réflexe du jour 35).
  2. Créez test_outils.py avec les deux tests du cours. Lancez python -m pytest.
  3. Critère de réussite : « 2 passed » s'affiche en vert.
  4. Provoquez l'échec : ajoutez def test_faux(): assert outils.prix_ttc(100) == 999, relancez, lisez le rapport (attendu vs obtenu). Supprimez ce test.
  5. Le bug-reproductible : un collègue affirme « prix_ttc(0) plante ». Écrivez def test_prix_zero(): assert outils.prix_ttc(0) == 0.0 — il passe : le collègue se trompait, et la preuve reste dans le projet.

Tips agent : « écris les tests pytest de cette fonction » est une excellente délégation — le code testé existe déjà, l'agent n'invente rien. Piège : il écrit des tests qui vérifient son propre code plutôt que votre intention. Preuve : cassez volontairement la fonction (taux × 2) ; ses tests doivent devenir rouges.

Synthèse (10 min)

Trois lignes : ce qu'est un test, assert, la danse rouge-vert. Sans notes : trois tests pour votre fonction repeter() du jour 30.

Jour 48 — pytest : cas limites et fichiers temporaires

Objectif : tester les bords (vide, zéro, erreur attendue) et les fonctions qui touchent des fichiers.

Théorie (20 min)

Un bon test ne vérifie pas que le cas facile : il traque les cas limites — les bords où le code se trompe en premier. Liste classique à passer en revue pour chaque fonction : entrée vide (liste [], texte ""), zéro, un seul élément, valeur négative, très grand nombre, et l'erreur attendue.

Pour l'erreur attendue — vérifier qu'une fonction plante bien quand elle doit — pytest fournit pytest.raises :

import pytest

def test_division_par_zero():
    with pytest.raises(ZeroDivisionError):
        outils.diviser(10, 0)

Le test passe si l'erreur arrive, échoue si elle n'arrive pas. C'est l'envers du décor : on prouve que le garde-fou tient.

Pour tester une fonction qui lit ou écrit des fichiers, ne touchez jamais vos vrais fichiers : pytest offre tmp_path, un dossier temporaire neuf pour chaque test, détruit après :

def test_compter_lignes(tmp_path):
    f = tmp_path / "notes.txt"
    f.write_text("a\nb\nc\n", encoding="utf-8")
    assert outils.compter_lignes(f) == 3

Déclarer tmp_path en paramètre du test suffit : pytest le crée et le remplit à la demande. C'est une fixture — un outil fourni à la demande. Chaque test repart d'un dossier vide : les tests ne se salissent pas entre eux.

Que prouve un test avec pytest.raises(FileNotFoundError) ?

  1. Que la fonction ne plante jamais
  2. Que la fonction lève bien cette erreur dans ce cas
  3. Que le fichier existe
Réponse

Le test réussit quand l'erreur attendue est levée : on vérifie les garde-fous.

Pourquoi tmp_path plutôt qu'un dossier de test fixe ?

  1. Chaque test repart d'un dossier vide et isolé, sans toucher aux vrais fichiers
  2. C'est plus rapide
  3. pytest l'exige
Réponse

Isolation : pas de reste d'un test précédent, pas de vrai fichier en danger.

Pratique (30 min)

  1. Ajoutez à outils.py :
def compter_lignes(chemin):
    """Rend le nombre de lignes d'un fichier texte."""
    from pathlib import Path
    return len(Path(chemin).read_text(encoding="utf-8").splitlines())

def diviser(a, b):
    """Rend a / b ; lève ZeroDivisionError si b vaut 0."""
    return a / b
  1. Écrivez les tests : le test du cours pour compter_lignes (tmp_path), le test raises pour diviser, et trois cas limites de votre choix (fichier vide → 0 ; une ligne sans \n final → 1 ; diviser 7, 2 → 3.5).
  2. Critère de réussite : python -m pytest affiche 5+ passed, dont les cas limites.
  3. Volontairement, cassez compter_lignes (+ 1 au retour), relancez : deux tests rouges montrent la valeur obtenue vs attendue. Réparez.

Tips agent : « liste les cas limites de cette fonction » : l'agent est bon pour énumérer les bords. Piège : il teste le cas nominal deux fois sous deux noms. Preuve : chaque test doit échouer si vous cassez la ligne qu'il vise — faites-en l'expérience sur un.

Synthèse (10 min)

Trois lignes : qu'est-ce qu'un cas limite, raises, tmp_path. Sans notes : tests d'une fonction mot_de_passe_valide (8 caractères mini) : ok, trop court, vide.

Jour 49 — raise : signaler une erreur soi-même

Objectif : lever une erreur claire quand une fonction reçoit l'impossible, plutôt que de rendre un résultat boiteux.

Théorie (20 min)

Vous attrapez les erreurs depuis le jour 26 ; aujourd'hui vous en créez. Quand une fonction reçoit une entrée avec laquelle elle ne peut pas travailler, le pire est de rendre un résultat plausible mais faux ; le mieux est de lever une erreur immédiatement, avec raise :

def taux_de(qualite):
    """Rend le taux de pourboire pour une qualité de service."""
    if qualite == "super":
        return 0.15
    if qualite == "correct":
        return 0.10
    if qualite == "mauvais":
        return 0.0
    raise ValueError(f"qualité inconnue : {qualite!r}")

Le programme s'arrête sur un message qui dit exactement ce qui cloche, avec la valeur fautive ({qualite!r} l'affiche avec ses guillemets — pratique pour voir un espace en trop). Principe directeur : échouer tôt, échouer fort. Une erreur levée à la source est facile à réparer ; une valeur fausse qui voyage dans dix fonctions avant d'exploser est un cauchemar de traceback.

Où raise, où except ? Règle simple : la fonction outil lève (elle ne sait pas quoi faire d'autre) ; le programme principal attrape et traduit pour l'utilisateur (« qualité attendue : super, correct ou mauvais »). La bibliothèque signale, l'interface console.

Et ça se teste : c'était le pytest.raises d'hier. Un raise documenté + un test qui le prouve = un contrat solide.

Pourquoi préférer raise à un return -1 pour signaler l'impossible ?

  1. C'est plus rapide
  2. raise est plus joli
  3. Le -1 peut être utilisé comme un vrai résultat par un appelant distrait ; l'erreur, non
Réponse

Une valeur-sentinel se mélange aux vraies données ; une exception interrompt et force à traiter le cas.

Qui lève, qui attrape ?

  1. La fonction outil lève, le programme principal attrape et console l'utilisateur
  2. L'inverse
  3. Tout le monde attrape partout
Réponse

Signaler au plus près de la cause, traiter au plus près de l'utilisateur.

Pratique (30 min)

  1. Ajoutez taux_de (ci-dessus) à votre outils.py de facturation.
  2. Écrivez les tests : les trois qualités rendent les bons taux, et with pytest.raises(ValueError): outils.taux_de("bof").
  3. Critère de réussite : 4 tests verts, dont le raises.
  4. Côté programme : dans main.py, appelez taux_de avec une saisie utilisateur, entourez de try/except ValueError qui affiche « qualité attendue : super, correct, mauvais » et redemande (boucle du jour 26). Testez avec « bof » puis « super » : message poli, puis calcul.
  5. Relisez : le raise est dans outils (la bibliothèque signale), le except est dans main (l'interface console). C'est la séparation du jour 43, appliquée aux erreurs.

Tips agent : « où ce code devrait-il lever une erreur plutôt que rendre une valeur douteuse ? » : bonne question de relecture pour un agent. Piège : il ajoute des raise partout, y compris sur des cas normaux (fin de fichier, liste vide légitime). Preuve : chaque raise proposé doit correspondre à un test raises qui aurait du sens.

Synthèse (10 min)

Trois lignes : échouer tôt et fort, qui lève / qui attrape, !r dans les messages. Sans notes : fonction aire_rectangle(longueur, largeur) qui lève ValueError sur un côté négatif, testée.

Jour 50 — Le module csv, pour de vrai

Objectif : lire et écrire du CSV avec le module dédié, guillemets et séparateurs compris.

Théorie (20 min)

Au jour 27, vous avez découpé un CSV à la main et touché sa limite : un champ qui contient une virgule casse le split. Le module csv de la bibliothèque standard gère tous ces cas (guillemets, virgules internes, séparateurs autres) :

import csv

with open("compte.csv", encoding="utf-8", newline="") as f:
    lecteur = csv.DictReader(f)
    for ligne in lecteur:
        print(ligne["libellé"], ligne["montant"])

Trois nouveautés. csv.DictReader lit chaque ligne comme un dictionnaire dont les clés sont prises dans la ligne d'en-tête : plus d'indices, plus de déballage fragile — ligne["montant"] se lit tout seul, et l'ordre des colonnes peut changer sans casser le programme. newline="" est le réglage officiel recommandé pour les fichiers CSV (certains champs peuvent contenir des retours à la ligne ; ce réglage laisse le module csv gérer). Et chaque valeur reste une chaîne : float(ligne["montant"]) reste à votre charge.

Pour écrire : csv.DictWriter avec la liste des colonnes, writeheader() pour l'en-tête, writerow(dict) par ligne.

Le séparateur point-virgule des exports français : csv.DictReader(f, delimiter=";"). Pour détecter le bon : ouvrez le fichier dans l'éditeur et regardez la première ligne — dix secondes qui évitent une heure de débogage.

Avantage de DictReader sur split(",") ?

  1. Il est plus rapide
  2. Il gère les guillemets et nomme les colonnes : ligne["montant"] au lieu d'indices fragiles
  3. Il convertit les nombres tout seul
Réponse

Robustesse + lisibilité. Les valeurs restent des chaînes : la conversion reste à vous.

Votre export bancaire utilise des points-virgules. Que faire ?

  1. csv.DictReader(f, delimiter=";")
  2. Remplacer les ; par des virgules dans le fichier
  3. Revenir à split(";")
Réponse

Le module prend le séparateur en paramètre ; ne modifiez jamais le fichier source.

Pratique (30 min)

  1. Créez banque.csv (notez le point-virgule et le libellé avec virgule) :
date;libellé;montant
2026-10-01;courses supermarché;-42.50
2026-10-02;"restaurant, Le Petit";-23.00
2026-10-05;salaire;2100.00
  1. Écrivez lire_banque.py : DictReader avec delimiter=";", boucle qui affiche date | libellé | montant par ligne, puis le solde avec un accumulateur.
  2. Critère de réussite : trois lignes correctes — le libellé « restaurant, Le Petit » sort ENTIER (la virgule interne est restée dans son champ) — et un solde de 2034.50.
  3. Refaites la même lecture avec split(";") en deux lignes de code et constatez le dégât sur la ligne du restaurant. C'est pour ça que le module existe.
  4. Écriture : créez resume.csv avec DictWriter, deux colonnes (categorie, total) et deux lignes de votre choix. Rouvrez-le dans un tableur si vous en avez un : c'est un vrai tableau.

Tips agent : « lis ce CSV » : exigez DictReader, pas un split. Piège : l'agent devine le séparateur sans ouvrir le fichier. Preuve : collez-lui les trois premières lignes réelles et vérifiez qu'un champ à virgule interne ressort entier.

Synthèse (10 min)

Trois lignes : DictReader/DictWriter, newline="", delimiter. Sans notes : CSV prénom;ville à trois lignes, programme qui affiche « prénom (ville) » par ligne.

Jour 51 — Projet final 1 : la conception

Objectif : concevoir l'outil final sur le papier avant d'écrire une ligne : depenses, le résumé de vos exports bancaires.

Théorie (20 min)

Le projet final : un outil en ligne de commande qui lit un export de compte bancaire en CSV et affiche les dépenses par catégorie, le total et le plus gros poste. Tout ce qu'il faut est acquis : DictReader (J50), le motif compteur (J22), argparse (J44-45), pytest (J47-48), la structure en fichiers (J43).

La conception d'abord, avec les trois questions du jour 28, écrites au carnet :

  1. Les données. Un CSV à trois colonnes : date;categorie;montant. En mémoire : une liste de dicts (une dépense = un dict), puis un dict catégorie → total pour le résumé.
  2. Les actions. lire le fichier (fonction), totaliser par catégorie (fonction), trouver la plus grosse dépense (fonction), afficher (programme principal). Chaque fonction fait une chose et se teste seule.
  3. L'interface. python depenses.py banque.csv affiche le résumé ; --categorie courses filtre ; --seuil 50 n'affiche que les catégories au-delà d'un montant.

Et la règle d'or du projet : une marche à la fois, et chaque marche tient debout. Planning des six prochains jours : lire (J52), totaliser (J53), tester (J54), la CLI (J55), la robustesse (J56), la documentation (J57). Un projet qui marche à moitié vaut mieux qu'un projet parfait qui ne tourne pas — et c'est celui-là qu'on rogne si des jours ont sauté.

Pourquoi séparer lire / totaliser / afficher en trois fonctions ?

  1. Pour faire plus professionnel
  2. Chacune se teste seule, et le programme principal ne fait qu'orchestrer
  3. Python l'exige au-delà de 50 lignes
Réponse

Des fonctions testables et un main mince : c'est toute l'architecture du projet.

Pratique (30 min)

  1. Créez le dossier depenses : bulle (python -m venv .venv, activation), pip install pytest, pip freeze > requirements.txt.
  2. Créez le fichier de données banque.csv, huit lignes variées, séparateur point-virgule, trois catégories au moins (courses, transport, loisirs), montants négatifs pour les dépenses :
date;categorie;montant
2026-10-01;courses;-42.50
2026-10-02;transport;-30.00
2026-10-03;courses;-17.80
2026-10-04;loisirs;-12.00
2026-10-05;salaire;2100.00
2026-10-06;transport;-4.20
2026-10-07;courses;-55.10
2026-10-08;loisirs;-9.99
  1. Écrivez au carnet les signatures des trois fonctions (nom, paramètres, ce qu'elles rendent) et les trois options de la CLI. Pas de code aujourd'hui : la conception est le livrable.
  2. Critère de réussite : vous pouvez raconter le parcours d'une ligne du CSV jusqu'à son apparition dans le résumé, sans regarder de code : lecture → dict → accumulateur → affichage.

Tips agent : faites challenger votre conception : « voici mon plan de projet en trois questions, trouve deux faiblesses ». Piège : l'agent propose d'ajouter des fonctionnalités (graphiques ! base de données !) — refusez, le périmètre est le vôtre. Preuve : ses deux faiblesses doivent toucher VOS fonctions, pas des ajouts.

Synthèse (10 min)

Trois lignes : les données, les fonctions, l'interface. Demain on lit le fichier.

Jour 52 — Projet final 2 : lire les dépenses

Objectif : écrire lire_depenses(), la fonction qui transforme le CSV en liste de dicts propres.

Théorie (20 min)

Première marche : la lecture. La fonction prend un chemin, rend une liste de dicts bien propres — la date en texte, la catégorie en minuscules nettoyées, le montant en float :

import csv

def lire_depenses(chemin):
    """Lit le CSV (date;categorie;montant) et rend la liste des dépenses.

    Chaque dépense est un dict : date (str), categorie (str), montant (float).
    """
    depenses = []
    with open(chemin, encoding="utf-8", newline="") as f:
        for ligne in csv.DictReader(f, delimiter=";"):
            depenses.append({
                "date": ligne["date"].strip(),
                "categorie": ligne["categorie"].strip().lower(),
                "montant": float(ligne["montant"]),
            })
    return depenses

Remarquez la philosophie : nettoyer à l'entrée. Strip, lower et float se font une fois, ici ; toutes les fonctions en aval travaillent sur des données propres et n'ont plus à se méfier. C'est le même principe que le .lower() des comparaisons du jour 8, appliqué à tout un pipeline.

Pourquoi rendre une liste plutôt qu'afficher ? Parce qu'une fonction qui rend des données se teste (J47) et se réutilise ; une fonction qui affiche ne fait qu'une chose. L'affichage viendra, au sommet, dans main().

Pratique (30 min)

  1. Créez depenses.py (la bibliothèque du projet) avec la fonction du cours.
  2. Créez main.py minimal qui appelle la fonction et affiche le nombre de lignes lues :
from depenses import lire_depenses

def main():
    depenses = lire_depenses("banque.csv")
    print(f"{len(depenses)} lignes lues.")
    print(depenses[0])

if __name__ == "__main__":
    main()
  1. Critère de réussite : « 8 lignes lues. » puis le premier dict, montant en float (vérifiez : -42.5 sans guillemets dans l'affichage du dict).
  2. Ajoutez une neuvième ligne au CSV et relancez : rien à changer, le programme suit.
  3. Testez la fragilité volontaire : mettez « abc » comme montant d'une ligne, relancez, lisez la ValueError — J56 la traitera proprement ; retirez-la pour l'instant.

Tips agent : cette fonction, un agent l'écrit parfaitement — MAIS il suppose souvent le séparateur et les noms de colonnes sans voir le fichier. Piège classique des CSV. Preuve : le premier dict affiché doit correspondre champ par champ à la première ligne de VOTRE banque.csv.

Synthèse (10 min)

Trois lignes : nettoyer à l'entrée, rendre des données plutôt qu'afficher, la structure du dict dépense. Sans notes : réécrire la boucle DictReader de mémoire.

Jour 53 — Projet final 3 : les totaux par catégorie

Objectif : écrire totaux_par_categorie() et plus_grosse_depense() — le cœur du résumé.

Théorie (20 min)

Deuxième marche : les calculs. Le premier est le motif compteur du jour 22, adapté : au lieu de compter des occurrences, on additionne des montants. Même squelette, autre opération :

def totaux_par_categorie(depenses):
    """Rend un dict categorie -> total des montants (négatifs = dépenses)."""
    totaux = {}
    for d in depenses:
        totaux[d["categorie"]] = totaux.get(d["categorie"], 0.0) + d["montant"]
    return totaux

La deuxième fonction cherche la dépense au montant le plus bas (le plus négatif). Deux façons : une boucle qui garde le minimum « à la main », ou la fonction min() avec une clé — min(depenses, key=lambda d: d["montant"]). Le lambda est une mini-fonction anonyme en une ligne ; lisez-le « pour chaque d, comparer d["montant"] ». La version boucle est plus longue mais entièrement à votre portée — écrivez celle-là, regardez l'autre en curiosité.

L'affichage du résumé, lui, vit dans main() : trier le dict des totaux (sorted(totaux.items(), key=lambda paire: paire[1]) trie par montant croissant) et afficher ligne par ligne avec la f-string :.2f du jour 12.

Pratique (30 min)

  1. Ajoutez les deux fonctions à depenses.py (totaux ci-dessus ; plus_grosse_depense en boucle : initialiser à None, parcourir, remplacer si d["montant"] < pire["montant"]).
  2. Complétez main() :
def main():
    depenses = lire_depenses("banque.csv")
    totaux = totaux_par_categorie(depenses)
    print("Dépenses par catégorie :")
    for categorie, total in sorted(totaux.items(), key=lambda paire: paire[1]):
        print(f"  {categorie} : {total:.2f} €")
    pire = plus_grosse_depense(depenses)
    print(f"Plus grosse dépense : {pire['categorie']} ({pire['montant']:.2f} €) le {pire['date']}")
  1. Critère de réussite : sur le banque.csv du jour 51 : courses -115.40 €, transport -34.20 €, loisirs -21.99 €, salaire 2100.00 €, et la plus grosse dépense est courses (-55.10 €) le 2026-10-07. Vérifiez chaque total à la calculatrice — c'est votre vrai test d'aujourd'hui.
  2. Ajoutez une dépense « courses » au CSV, relancez : le total et peut-être le champion changent, le code non.

Tips agent : « trouve la plus grosse dépense » : l'agent sortira min(..., key=...). Piège : sur une liste VIDE, min plante — demandez-lui le comportement liste vide, ou imposez « rend None si vide ». Preuve : testez avec un CSV sans dépenses.

Synthèse (10 min)

Trois lignes : le compteur devenu somme, min avec clé vs boucle, trier un dict par valeur. Sans notes : totaux par catégorie sur une liste de trois dicts écrite à la main.

Jour 54 — Projet final 4 : tester les fonctions

Objectif : couvrir le projet de tests pytest : lecture avec tmp_path, calculs, cas limites.

Théorie (20 min)

Troisième marche : la preuve. Vérifier à la calculatrice (hier) est exact mais ne se rejoue pas ; les tests, si. Règle de couverture pour ce projet : chaque fonction a au moins un test heureux et un test de bord.

Le plan de tests, écrit avant le code de test :

  • lire_depenses : un CSV écrit dans tmp_path → la liste a le bon nombre de dicts, le montant est un float, la catégorie est en minuscules. Bord : un fichier vide (en-tête seule) → liste vide, pas d'erreur.
  • totaux_par_categorie : liste construite à la main (pas de fichier !) → les totaux sont bons. Bord : liste vide → dict vide.
  • plus_grosse_depense : trois dépenses → la bonne revient. Bord : décidez et testez le comportement liste vide (rendre None est un choix propre — mais alors main() doit le gérer : « aucune dépense »).

Notez l'astuce des tests de calcul : on construit les données en mémoire, une liste de dicts écrite dans le test. Pas de fichier, pas de dépendance au CSV : le test vise la fonction et rien d'autre. Un test doit échouer pour UNE seule raison possible.

Pratique (30 min)

  1. Créez test_depenses.py :
from depenses import lire_depenses, totaux_par_categorie, plus_grosse_depense

CSV = "date;categorie;montant\n2026-10-01;Courses;-42.50\n2026-10-02;transport;-30.00\n"

def test_lire_depenses(tmp_path):
    f = tmp_path / "b.csv"
    f.write_text(CSV, encoding="utf-8")
    depenses = lire_depenses(f)
    assert len(depenses) == 2
    assert depenses[0]["montant"] == -42.50
    assert depenses[0]["categorie"] == "courses"

def test_totaux():
    depenses = [{"categorie": "courses", "montant": -10.0, "date": "2026-10-01"},
                {"categorie": "courses", "montant": -5.0, "date": "2026-10-02"},
                {"categorie": "loisirs", "montant": -3.0, "date": "2026-10-03"}]
    assert totaux_par_categorie(depenses) == {"courses": -15.0, "loisirs": -3.0}

def test_plus_grosse():
    depenses = [{"categorie": "a", "montant": -10.0, "date": "2026-10-01"},
                {"categorie": "b", "montant": -30.0, "date": "2026-10-02"}]
    assert plus_grosse_depense(depenses)["categorie"] == "b"

def test_liste_vide():
    assert totaux_par_categorie([]) == {}
  1. Implémentez le comportement liste vide de plus_grosse_depense (rend None), écrivez son test, et gérez-le dans main().
  2. Critère de réussite : python -m pytest : 5 tests verts, et python main.py affiche toujours le bon résumé.
  3. Cassez une fonction (+ 1 sur un total), relancez pytest : le bon test devient rouge. Réparez.

Tips agent : « génère les tests de ces trois fonctions » : délégation idéale, le code existe. Piège : des tests qui passent toujours (assert True déguisé). Preuve : le test de casse d'hier — cassez la fonction, exigez du rouge.

Synthèse (10 min)

Trois lignes : un heureux + un bord par fonction, données en mémoire pour les calculs, tmp_path pour les fichiers. Sans notes : écrire le test d'une fonction qui double les montants.

Jour 55 — Projet final 5 : la ligne de commande

Objectif : transformer main() en vraie CLI : fichier en argument, filtres en options.

Théorie (20 min)

Quatrième marche : l'outil devient commandable. La conception du jour 51 disait : fichier positionnel, --categorie pour filtrer, --seuil pour n'afficher que les grosses catégories. Avec argparse, c'est mécanique :

import argparse

def main():
    parser = argparse.ArgumentParser(description="Résumé des dépenses d'un export CSV.")
    parser.add_argument("fichier", help="le CSV (date;categorie;montant)")
    parser.add_argument("--categorie", default="", help="ne montrer que cette catégorie")
    parser.add_argument("--seuil", type=float, default=0.0, help="cacher les catégories dont le total dépasse -seuil € (dépenses en négatif)")
    args = parser.parse_args()

Le filtrage, ensuite, se dit avec les outils du jour 18 : construire une nouvelle liste en ne gardant que ce qui correspond. Pour --categorie : une boucle qui ne garde que les dépenses de cette catégorie (comparaison en minuscules, déjà normalisées à la lecture). Pour --seuil : au moment d'afficher les totaux, sauter les catégories dont le total est supérieur au seuil négatif (if total > -args.seuil: continue — les montants sont négatifs, le seuil se compare avec un signe moins ; dessinez-le sur un exemple : seuil 50 → on cache courses si son total est -115 ? Non : -115 < -50, donc on l'affiche. On cache les PETITES dépenses, celles au-dessus de -50).

La CLI est la dernière couche : les fonctions, elles, n'ont pas changé. C'est la récompense de la structure du jour 43 : une interface se remplace sans toucher au moteur.

Pratique (30 min)

  1. Réécrivez main() avec l'argparse ci-dessus et les deux filtres.
  2. Critère de réussite, trois commandes :
    • python main.py banque.csv → le résumé complet (mêmes chiffres qu'au jour 53) ;
    • python main.py banque.csv --categorie courses → seule la catégorie courses, total -115.40 € ;
    • python main.py banque.csv --seuil 50 → courses et salaire restent, transport et loisirs disparaissent (leurs totaux sont entre 0 et -50).
  3. python main.py --help : relisez l'aide comme un utilisateur qui découvre l'outil. Corrigez les help= qui ne disent rien.
  4. Cassez l'usage : oubliez le fichier → usage poli ; --seuil abc → refus typé. argparse travaille pour vous.

Tips agent : « ajoute --categorie et --seuil à cette CLI » : l'agent excelle. Piège : la logique de seuil avec des montants négatifs est un piège à signes pour lui comme pour vous. Preuve : le jeu d'essai du critère de réussite, chiffres vérifiés à la main.

Synthèse (10 min)

Trois lignes : la CLI comme couche interchangeable, les deux filtres, le piège des montants négatifs. Sans notes : ajouter --mois (filtre sur les 7 premiers caractères de la date) à l'outil.

Jour 56 — Projet final 6 : la robustesse

Objectif : faire survivre l'outil aux fichiers réels : absent, ligne cassée, mauvais encodage.

Théorie (20 min)

Cinquième marche : le monde réel. Votre banque.csv d'exercice est propre ; un vrai export ne l'est jamais. Trois fautes à traiter, avec les outils connus :

  • Le fichier n'existe pas : FileNotFoundError → try/except dans main(), message « fichier introuvable : … », fin propre. Pas de traceback pour un utilisateur.
  • Une ligne a un montant illisible : float("abc") lève ValueError. Deux politiques honnêtes : stricte (le programme s'arrête en nommant la ligne) ou souple (il ignore la ligne en la comptant : « 2 lignes ignorées » affiché à la fin). La souple est plus utile au quotidien, à condition que le compte des ignorées soit AFFICHÉ — une ligne avalée en silence est un total faux en toute confiance.
  • L'encodage : un export Windows en latin-1 ouvre des accents cassés (UnicodeDecodeError) en utf-8. Le traitement propre dépasse ce parcours ; le minimum vital : attraper UnicodeDecodeError et dire « l'encodage du fichier n'est pas utf-8 — ré-exportez ou ré-enregistrez en utf-8 ».

Et chaque robustesse se teste : fichier absent (un chemin bidon suffit), montant cassé (une ligne « abc » dans le CSV du test), compte des ignorées vérifié. La robustesse non testée est une intention, pas une fonctionnalité.

Pratique (30 min)

  1. Dans lire_depenses, entourez le float() d'un try/except ValueError : ligne ignorée, compteur ignorees += 1 ; la fonction rend désormais le tuple (depenses, ignorees) — adaptez main() et les tests (le déballage du jour 20 : depenses, ignorees = lire_depenses(f)).
  2. Dans main(), try/except FileNotFoundError autour de l'appel, message poli, et return pour sortir sans traceback.
  3. Critère de réussite :
    • python main.py inexistant.csv → « fichier introuvable », pas de traceback ;
    • une ligne « 2026-10-09;courses;abc » dans banque.csv → le résumé s'affiche avec « 1 ligne ignorée » en dernière ligne ;
    • pytest : tout reste vert, plus deux nouveaux tests (montant cassé → ignoré et compté ; fichier en-tête seul → 0 lignes, 0 ignorées).
  4. Testez aussi le cas mixte : une ligne bonne après la ligne cassée doit bien être lue (le except n'a pas interrompu la boucle).

Tips agent : « rends ce script robuste » est trop vague : donnez la liste exacte des fautes à traiter (absent, montant illisible, encodage). Piège : l'agent attrape Exception entier « au cas où ». Preuve : provoquez chaque faute une par une et vérifiez le message exact.

Synthèse (10 min)

Trois lignes : les trois fautes du monde réel, stricte vs souple, une ligne ignorée est toujours comptée. Sans notes : ajouter le try UnicodeDecodeError avec son message.

Jour 57 — Projet final 7 : la documentation

Objectif : finir l'outil : README, docstrings, requirements — le colis complet qu'on peut donner à quelqu'un.

Théorie (20 min)

Sixième marche : l'emballage. Un outil fini se donne : à un collègue, à un forum, à vous-même dans six mois. Trois pièces :

Le README.md (texte brut, format Markdown) : la première chose qu'on lit. Cinq rubriques suffisent :

  1. Quoi : une phrase. « depenses résume un export bancaire CSV par catégorie. »
  2. Installation : les commandes exactes — création de la bulle, activation, pip install -r requirements.txt.
  3. Utilisation : la commande de base et chaque option, avec un exemple d'entrée et la sortie réelle.
  4. Le format du CSV attendu : colonnes, séparateur, encodage — c'est le contrat d'entrée.
  5. Tests : python -m pytest.

Les docstrings : passez sur chaque fonction — ce qu'elle fait, ce qu'elle attend, ce qu'elle rend (jour 31). requirements.txt : pip freeze > requirements.txt une dernière fois, à jour.

Critère d'un bon README : quelqu'un qui n'a jamais vu le projet arrive à la sortie attendue en suivant seulement les rubriques 2 et 3. Testez-le : fermez tout, ouvrez un terminal neuf, suivez votre README à la lettre dans un dossier neuf. Ce que vous y oubliez, vous le découvrirez — le chemin du fichier, le séparateur, la bulle.

Pratique (30 min)

  1. Écrivez README.md avec les cinq rubriques. Pour la sortie d'exemple, copiez la vraie sortie de python main.py banque.csv.
  2. Passez les docstrings : chaque fonction de depenses.py en a une (ce qu'elle rend, cas de la liste vide compris).
  3. pip freeze > requirements.txt.
  4. Critère de réussite (le test du collègue) : copiez le dossier du projet (sans .venv) dans un dossier neuf, suivez votre README comme un inconnu : bulle, requirements, lancement, pytest. Tout doit marcher sans rien demander à l'auteur. Chaque accroc = une ligne à ajouter au README.
  5. Supprimez le dossier témoin. Le projet est fini.

Tips agent : « rédige le README de ce projet » : l'agent le fait bien À CONDITION de lui donner les fichiers — sinon il invente des options. Piège : un exemple de sortie inventé qui ne correspond pas au vrai programme. Preuve : le test du collègue, exécuté à la lettre, sur SA version du README.

Synthèse (10 min)

Trois lignes : les cinq rubriques, le contrat d'entrée, le test du collègue. Votre premier outil fini existe.

Jour 58 — Révision générale : le défi

Objectif : prouver l'autonomie : un programme complet, à cahier des charges imposé, écrit seul de A à Z.

Théorie (20 min)

Pas de cours : un défi. Cahier des charges de mots.py, un analyseur de texte :

  • Prend un fichier texte en argument positionnel (argparse), et une option --top N (type int, défaut 5).
  • Lit le fichier en utf-8 (message poli si absent).
  • Affiche : le nombre de mots, le nombre de mots distincts, et les N mots les plus fréquents avec leur compte (casse normalisée, ponctuation .,;:!? ignorée — .strip(".,;:!?") sur chaque mot suffit).
  • Les fonctions (lire, compter) sont dans un module séparé, testées par pytest (fichier temporaire tmp_path).

Exemple : sur un fichier contenant « le chat et le chien. Le chat dort. », --top 2 affiche 8 mots, 5 distincts, puis « le : 3 » et « chat : 2 ».

Méthode conseillée, toute apprise ici : conception en trois questions au carnet (5 min), squelette en deux fichiers, lecture d'abord, compteur ensuite, CLI après, tests à chaque marche. Chronométrez-vous : moins de deux heures est un excellent score.

Pratique (30 min + débordement assumé)

  1. Écrivez le cahier des charges en plan au carnet, sans coder.
  2. Codez, marche par marche. Aucune correction fournie : les tests et l'exemple sont vos arbitres.
  3. Critère de réussite : l'exemple du cours donne exactement la sortie annoncée, pytest est vert, et --top 1 sur le même fichier n'affiche que « le : 3 ».
  4. Coincé plus de 20 minutes sur un point ? Relisez la séance de ce point (compteur : J22 ; tri par valeur : J53 ; argparse : J44) — c'est le but du carnet : savoir où retrouver.

Tips agent : aujourd'hui, résistez. Demain, faites écrire le même programme par un agent et comparez les deux : structure, lisibilité, pièges. Le vôtre sera plus simple, et c'est un compliment. Preuve : les deux passent le même jeu de tests — dont un texte vide et un mot à casse mélangée.

Synthèse (10 min)

Trois lignes : ce qui a été fluide, ce qui a résisté, ce que vous relisez demain si besoin.

Jour 59 — Bilan du parcours

Objectif : boucler : la check-list, la rétrospective, et la suite.

Théorie (20 min)

Relisez la check-list de la page d'accueil du parcours et cochez honnêtement. Puis mesurez le chemin : au jour 1, ouvrir un terminal était une épreuve ; au jour 59, vous avez un outil structuré, testé, documenté, qui traite de vraies données. Les notions qui restent floues ne sont pas un échec : elles sont votre programme des deux prochaines semaines, et vous savez maintenant qu'un point flou se résout par une séance d'une heure, pas par un livre entier.

Pour continuer, trois pistes honnêtes :

  • Approfondir : le tutoriel officiel, là où vous l'aviez laissé — vous le lirez désormais comme une revue de détails, pas comme une langue étrangère.
  • Automatiser davantage : Automate the Boring Stuff with Python, chapitres sur le web scraping, Excel et les PDF — le prolongement direct de vos modules 3 et 4.
  • Construire : reprenez une tâche répétitive de votre vraie vie (tris, exports, renommages, relances) et outillez-la. Un outil personnel utilisé chaque semaine entretient mieux que dix exercices.

Pratique (30 min)

  1. Cochez la check-list d'accueil. Pour chaque case décochée, notez la séance à refaire.
  2. La rétrospective au carnet : la séance la plus dure, celle qui a le plus servi, l'erreur que vous ne ferez plus.
  3. Critère de réussite : présentez votre projet final à quelqu'un (ou à voix haute, seul) en cinq minutes : ce qu'il fait, comment il est structuré, comment ses tests le prouvent. Si la présentation tient, le parcours est réussi.
  4. Rangez le dossier du projet quelque part de durable. Vous y reviendrez.

Tips agent : vous avez désormais le super-pouvoir de ce site : savoir ce qu'un agent fait quand il « code pour vous ». La règle finale ne changera plus : déléguez ce que vous savez relire, exigez la preuve qui s'exécute, gardez la main sur la conception. Les encadrés Tips des 59 séances tiennent en cette phrase.

Synthèse (10 min)

Trois lignes : ce que vous savez faire, ce que vous ferez la semaine prochaine, à qui vous montrerez l'outil. Bravo.

Utile ? Dites-nous ce qu'il faut mesurer ensuite.

Commentaires

Ni compte ni traceur : un commentaire est lu par un humain avant d'apparaître.