Python module 3 : fonctions, modules, bibliothèques, débogage
Quatorze séances pour passer du script au programme organisé : découper son code en fonctions, utiliser les centaines de modules livrés avec Python, installer des bibliothèques externes proprement, lire un traceback sans peur, et écrire ses premières vraies automatisations de fichiers.
Jour 29 — Les fonctions : def, paramètres, return
Objectif : créer des blocs de code réutilisables avec des entrées et une sortie.
Théorie (20 min)
Une fonction est un bloc de code nommé, qu'on définit une fois et qu'on appelle autant de fois qu'on veut. Vous en utilisez depuis le jour 2 : print(), len(), input() sont des fonctions écrites par d'autres. Aujourd'hui, vous écrivez les vôtres :
def saluer(nom):
return f"Bonjour {nom} !"
message = saluer("Alice")
print(message)
print(saluer("Bob"))
Trois idées. def + nom + parenthèses + deux-points, puis un bloc indenté : la définition — elle ne s'exécute pas tout de suite, elle enregistre la recette. nom entre parenthèses est un paramètre : une variable qui recevra la valeur donnée à l'appel (l'argument). Et return est la sortie : il rend un résultat à l'appelant et termine la fonction — tout ce qui suit un return dans la fonction est ignoré.
La distinction qui change tout : print affiche, return rend. Une fonction qui affiche ne peut pas être réutilisée dans un calcul ; une fonction qui rend une valeur, si. Réflexe durable : les fonctions calculent et rendent ; le programme principal affiche.
Pourquoi découper en fonctions ? Pour ne pas se répéter (un calcul écrit une fois, appelé dix fois), pour nommer les morceaux (calculer_total(panier) se lit tout seul), et pour tester chaque morceau séparément — ce sera le jour 47.
Que rend une fonction sans return ?
- 0
- None
- Une erreur
Réponse
None : la valeur « rien » de Python. Si votre variable contient None alors que vous attendiez un résultat, cherchez un return oublié.
Différence entre print et return dans une fonction ?
- Aucune, les deux affichent
- print rend la valeur, return l'affiche
- print affiche à l'écran ; return rend la valeur au code appelant
Réponse
Seul return permet de réutiliser le résultat : dans un calcul, un test, un fichier.
Pratique (30 min)
- Créez
outils.py:
def prix_ttc(prix_ht, taux):
return prix_ht * (1 + taux)
def est_pair(n):
return n % 2 == 0
print(f"{prix_ttc(100, 0.2):.2f}")
print(est_pair(7))
- Critère de réussite : affiche
120.00puisFalse— deux fonctions définies en haut, appelées en bas, qui rendent des valeurs utilisées par le programme principal. - Enlevez le return de prix_ttc (mettez un print à la place) et relancez :
Noneapparaît dans la f-string. Vous venez de voir le bug du return oublié — remettez-le. - Réécrivez la calculatrice de pourboire du jour 12 avec une fonction
taux_pourboire(qualite)qui rend 0.15, 0.10 ou 0.0. Le programme principal ne change presque pas — c'est le but.
Tips agent : « découpe ce script de 40 lignes en fonctions nommées, sans changer ce qu'il affiche » : excellente refonte à déléguer. Piège : il peut transformer un print en return et casser l'affichage. Preuve : sortie identique avant/après, aux caractères près.
Synthèse (10 min)
Trois lignes : def / paramètre / argument / return, print vs return, le bug du None. Sans notes : fonction moyenne(a, b) qui rend la moyenne, appelée trois fois.
Jour 30 — Portée et arguments par défaut
Objectif : comprendre où une variable existe, et donner des valeurs par défaut aux paramètres.
Théorie (20 min)
Une variable créée dans une fonction n'existe que dans cette fonction : c'est la portée (scope). Elle est locale. De l'extérieur, elle n'existe pas ; une variable du même nom à l'extérieur est une autre boîte :
def calculer():
total = 42
return total
resultat = calculer()
print(resultat)
print(total) hors de la fonction lèverait un NameError : total est local. C'est une protection, pas une contrainte : cent fonctions peuvent avoir chacune leur variable total sans se marcher dessus. Pour faire sortir une valeur : return. Pour en faire entrer : un paramètre. Tout passe par les portes.
Les arguments par défaut rendent des paramètres optionnels :
def saluer(nom, formule="Bonjour"):
return f"{formule} {nom} !"
print(saluer("Alice"))
print(saluer("Bob", "Salut"))
Premier appel : formule vaut "Bonjour" ; deuxième : l'argument donné remplace le défaut. Et les arguments nommés rendent les appels lisibles : saluer(nom="Chloé", formule="Coucou") — l'ordre ne compte plus quand on nomme.
Une variable créée dans une fonction est visible…
- Uniquement dans cette fonction
- Partout dans le fichier
- Dans les fonctions appelées après
Réponse
Elle est locale : dehors, elle n'existe pas. On fait sortir les valeurs avec return.
def f(a, b=2): return a * b — que rend f(3) ?
- Une erreur : il manque un argument
- 3
- 6
Réponse
b prend sa valeur par défaut : 3 × 2 = 6.
Pratique (30 min)
- Reproduisez l'exemple de portée au REPL : définissez la fonction, appelez-la, puis tentez
print(total)hors de la fonction — lisez le NameError. - Créez
remises.py:
def prix_final(prix, remise=0.10):
return prix * (1 - remise)
print(f"{prix_final(80):.2f}")
print(f"{prix_final(80, remise=0.25):.2f}")
print(f"{prix_final(80, 0.50):.2f}")
- Critère de réussite : affiche 72.00, 60.00, 40.00 — le même paramètre, utilisé par défaut, nommé, et positionnel.
- Écrivez
def repeter(texte, fois=3):qui rend le texte répété avec des espaces ((texte + " ") * foispuis.strip()).repeter("ha", 4)doit rendre "ha ha ha ha".
Tips agent : « pourquoi ma variable n'existe plus hors de la fonction ? » : l'agent explique la portée en une phrase. Piège : il suggère global — presque toujours une mauvaise idée, refusez et utilisez return. Preuve : le programme sans global qui passe par return.
Synthèse (10 min)
Trois lignes : portée locale, argument par défaut, argument nommé. Sans notes : fonction total(prix, quantite=1, tva=0.2) testée avec un, deux et trois arguments.
Jour 31 — Bien nommer et documenter : les docstrings
Objectif : écrire des fonctions qu'un inconnu (ou vous dans un mois) comprend sans les lire en détail.
Théorie (20 min)
Un programme est relu dix fois plus souvent qu'il n'est écrit. Deux outils de clarté aujourd'hui.
Le nommage : une fonction est un verbe d'action (calculer_total, est_valide, lire_fichier), une variable est un nom de chose (total, scores au pluriel pour une liste), un booléen se lit comme une question (est_pair, a_permis). Un nom doit répondre à « qu'est-ce que ça contient / ça fait ? » sans qu'on ait à lire le corps.
La docstring : une chaîne en triple guillemets placée en première ligne d'une fonction, qui dit ce qu'elle fait, ce qu'elle attend, ce qu'elle rend :
def prix_ttc(prix_ht, taux=0.20):
"""Rend le prix TTC à partir du prix HT et du taux de TVA.
prix_ht : nombre positif, en euros.
taux : 0.20 pour 20 %. Rend un float.
"""
return prix_ht * (1 + taux)
Ce n'est pas un commentaire : la docstring est rangée dans la fonction et accessible par help(prix_ttc) au REPL — c'est le système d'aide officiel de Python, celui de toutes les bibliothèques que vous installerez.
Règle pratique : une fonction tient en un écran et fait une chose. Si sa docstring contient « et puis… et ensuite… », il y a probablement deux fonctions à extraire.
Où se place une docstring ?
- Au-dessus de la fonction, avec #
- En première ligne à l'intérieur de la fonction, entre triples guillemets
- Après le return
Réponse
Première ligne du corps de la fonction, en triple guillemets : help() la retrouve.
Quel nom de fonction est le meilleur ?
- calculer_moyenne
- traiter
- fonction2
Réponse
Un verbe précis qui dit ce que la fonction rend.
Pratique (30 min)
- Reprenez vos fonctions des jours 29-30 : ajoutez à chacune une docstring de deux lignes (ce qu'elle fait, ce qu'elle rend).
- Au REPL :
from outils import prix_ttc— non, pas encore vu : ouvrez plutôt le REPL dans le dossier et faitesexec(open("outils.py", encoding="utf-8").read())… plus simple : collez la fonction au REPL, puishelp(prix_ttc). Votre docstring s'affiche : vous venez de documenter comme un professionnel. - Critère de réussite :
help()affiche vos docstrings sans erreur. - Exercice de renommage : voici du code volontairement mal nommé — réécrivez-le avec de bons noms :
def f(x, y):
z = x * y
return z + z * 0.2
- (Solution attendue :
def total_ttc(prix_unitaire, quantite)qui calculetotal_htpuis rend le TTC. Vérifiez : f(10, 3) rend 36.0 dans les deux versions.)
Tips agent : « écris la docstring de cette fonction » : parfait pour un agent — il décrit ce qu'il voit. Piège : il peut décrire ce que la fonction DEVRAIT faire plutôt que ce qu'elle fait (bug inclus). Preuve : un appel de test dont le résultat contredit ou confirme la docstring.
Synthèse (10 min)
Trois lignes : verbes pour les fonctions, docstring vs commentaire, la règle « une fonction = une chose ». Sans notes : réécrire def g(a): return a * 1.2 proprement, docstring comprise.
Jour 32 — import : la bibliothèque standard
Objectif : utiliser les modules livrés avec Python : math, random, datetime.
Théorie (20 min)
Python arrive « piles comprises » : des centaines de modules — des boîtes à outils thématiques — sont installés avec lui. C'est la bibliothèque standard. On les active avec import :
import math
print(math.sqrt(25))
print(math.pi)
On accède aux outils d'un module avec le point : module.outil. Variante fréquente, from math import sqrt importe un outil seul, utilisable sans préfixe : sqrt(9). Restez sur import module au début : l'origine de chaque outil reste visible à la lecture.
Trois modules pour aujourd'hui :
math: sqrt, pi, floor (plancher), ceil (plafond) ;random:random.randint(1, 6)un entier au hasard entre 1 et 6 inclus,random.choice(liste)un élément au hasard,random.shuffle(liste)mélange sur place ;datetime:datetime.date.today()la date du jour,datetime.date(2026, 12, 25) - datetime.date.today()un écart en jours.
Le hasard d'un ordinateur est pseudo-aléatoire : produit par une formule. Pour les jeux et les tests il est parfait ; il ne doit jamais servir à la sécurité (mots de passe, clés) — là, c'est le module secrets.
Où trouver la liste complète ? docs.python.org/fr/3/library. Feuilletez-la ce soir comme un catalogue : savoir qu'un outil existe suffit, on le recherche le jour du besoin.
Après from math import sqrt, comment appelle-t-on la racine carrée ?
- math.sqrt(9)
- sqrt(9)
- import sqrt(9)
Réponse
from X import Y rend Y utilisable sans préfixe.
Quel module pour tirer un élément au hasard d'une liste ?
- math
- datetime
- random
Réponse
random.choice(liste). Et random n'est jamais pour la sécurité : là, c'est secrets.
Pratique (30 min)
- Au REPL :
import mathpuismath.sqrt(2),import randompuis cinq foisrandom.randint(1, 6)— un dé. - Créez
compte_a_rebours.py:
import datetime
cible = datetime.date(2026, 12, 25)
aujourdhui = datetime.date.today()
reste = (cible - aujourdhui).days
print(f"Il reste {reste} jours avant Noël 2026.")
- Critère de réussite : le nombre de jours affiché est juste — vérifiez-le grossièrement sur un calendrier. Changez la date cible pour votre prochain anniversaire.
- Améliorez le plus-ou-moins du jour 15 : remplacez
secret = 7parsecret = random.randint(1, 100), bornez à 1-100, jouez une vraie partie.
Tips agent : « quel module de la bibliothèque standard fait X ? » : l'agent connaît le catalogue par cœur. Piège : il propose parfois une bibliothèque externe là où la standard suffit (ex. arrow pour les dates). Preuve : exigez « sans rien installer » et un exemple qui tourne au REPL.
Synthèse (10 min)
Trois lignes : import vs from-import, les trois modules du jour, pourquoi random n'est pas pour les secrets. Sans notes : tirer trois prénoms au hasard dans une liste de cinq (random.choice en boucle).
Jour 33 — pathlib : les chemins sans douleur
Objectif : manipuler fichiers et dossiers avec pathlib au lieu de chaînes bricolées.
Théorie (20 min)
Un chemin est l'adresse d'un fichier : Documents/python/memo.txt. Piège historique : Windows écrit Documents\python avec des antislashs, macOS/Linux avec des slashs — un chemin écrit en dur ne marche que sur un système. Le module pathlib règle ça : il représente un chemin par un objet Path, portable par construction :
from pathlib import Path
dossier = Path("Documents") / "python"
fichier = dossier / "memo.txt"
print(fichier.exists())
print(fichier.suffix)
L'opérateur / assemble les morceaux dans le bon sens pour votre système. Les outils qui changent la vie, sur un Path :
.exists()le chemin existe-t-il ;.is_file()/.is_dir()précisent ;.namele nom du fichier,.stemle nom sans extension,.suffixl'extension (.txt),.parentle dossier parent ;dossier.iterdir()liste le contenu d'un dossier — une boucle for dessus et vous visitez chaque fichier ;dossier.glob("*.txt")ne garde que les fichiers correspondant au motif (l'étoile remplace n'importe quoi) ;dossier.mkdir(exist_ok=True)crée le dossier sans planter s'il existe déjà ;fichier.read_text(encoding="utf-8")etfichier.write_text("...", encoding="utf-8"): lire/écrire en une ligne, sans with — parfait pour les petits fichiers.
Les renommages et déplacements : fichier.rename(cible) déplace ou renomme (cible = le nouveau chemin complet). C'est la brique des automatisations des jours 40-41.
Que vaut Path("photo.JPEG").suffix ?
- ".JPEG"
- "photo"
- ".jpeg" en minuscules
Réponse
L'extension telle quelle, casse comprise : pensez à .lower() avant de comparer.
Pourquoi préférer Path à "dossier" + "/" + "fichier" ?
- C'est plus court à taper
- Le / de pathlib choisit le bon séparateur selon le système, et Path offre exists(), glob(), etc.
- Les chaînes sont interdites pour les chemins
Réponse
Portabilité + outils intégrés. Un chemin n'est pas du texte, c'est une structure.
Pratique (30 min)
- Créez un dossier
terrainavec trois fichiers dedans :photo1.jpg,notes.txt,photo2.png(contenu quelconque, créés dans l'éditeur). - Créez
explorer.py:
from pathlib import Path
dossier = Path("terrain")
for f in dossier.iterdir():
print(f"{f.name} — suffixe {f.suffix} — {f.stat().st_size} octets")
print("\nUniquement les images :")
for f in dossier.glob("*.jp*g"):
print(f.name)
- Critère de réussite : les trois fichiers sont listés avec taille et suffixe, puis seule photo1.jpg ressort du glob. Ajoutez un quatrième fichier et relancez : rien à changer dans le code — c'est la force du parcours de dossier.
- Ajoutez
Path("terrain/archives").mkdir(exist_ok=True)puis déplacez-y notes.txt avecPath("terrain/notes.txt").rename("terrain/archives/notes.txt"). Vérifiez dans l'explorateur de fichiers.
Tips agent : « liste les .pdf de ce dossier » : l'agent écrit le glob. Piège : glob("*.PDF") ne trouve pas les .pdf en minuscules — la casse compte. Preuve : créez un fichier de chaque casse et comptez ce que le programme voit.
Synthèse (10 min)
Trois lignes : pourquoi Path, iterdir vs glob, rename pour déplacer. Sans notes : lister les .txt d'un dossier et afficher celui qui porte le nom le plus long.
Jour 34 — venv : une bulle par projet
Objectif : créer un environnement virtuel et comprendre pourquoi on ne l'oublie jamais.
Théorie (20 min)
Demain, vous installerez des bibliothèques écrites par d'autres. Problème classique : le projet A veut la version 1 d'une bibliothèque, le projet B la version 2 — une seule installation système ne peut pas servir les deux. La solution de Python : un environnement virtuel (venv), un dossier qui contient sa propre copie isolée de Python et des bibliothèques du projet. Une bulle par projet : ce qui s'installe dans la bulle n'existe pas en dehors.
Création (une fois par projet), dans le dossier du projet :
python -m venv .venv
-m venv demande à Python de lancer son module venv ; .venv est le nom du dossier créé (le point le rend discret ; la convention s'appelle comme ça, ne cherchez pas à l'inventer).
Puis activation, à refaire à chaque session de travail :
- Windows (Invite de commandes) :
.venv\Scripts\activate - Windows (PowerShell) :
.venv\Scripts\Activate.ps1 - macOS/Linux :
source .venv/bin/activate
Signe que la bulle est active : (.venv) apparaît au début de la ligne du terminal. Tant qu'il est là, python et pip pointent dans la bulle. deactivate en sort. Le dossier .venv ne se partage jamais (il est gros et propre à votre machine) : on partage la liste des bibliothèques, demain.
À quoi sert un venv ?
- À accélérer Python
- À isoler les bibliothèques d'un projet des autres projets
- À cacher son code
Réponse
Chaque projet a sa bulle : pas de conflit de versions entre projets.
Comment savoir que la bulle est active ?
- (.venv) apparaît au début de la ligne du terminal
- Python affiche un message au lancement
- Un voyant vert dans l'éditeur
Réponse
Le préfixe (.venv) dans l'invite. Pas de préfixe = pip installe ailleurs.
Pratique (30 min)
- Créez un dossier
bibliotheques, placez-vous dedans au terminal. python -m venv .venv— quelques secondes, un dossier .venv apparaît.- Activez-le avec la commande de votre système. Critère de réussite : la ligne du terminal commence par
(.venv). - Vérifiez l'isolation :
python -c "import sys; print(sys.prefix)"affiche un chemin qui finit par.venv— votre Python est bien celui de la bulle. pip list: presque vide (pip et setuptools). C'est l'intérêt : une bulle neuve ne contient rien.deactivate, relancez la commande sys.prefix : le chemin système revient. Réactivez pour la suite du parcours — d'ici la fin du module 4, tout se passe dans des bulles.
Tips agent : un agent qui code dans votre projet doit travailler dans votre bulle : dites-lui « active d'abord .venv ». Piège : il lance pip install hors de la bulle et pollue le système. Preuve : pip list dans la bulle montre la nouvelle bibliothèque, et sys.prefix finit par .venv.
Synthèse (10 min)
Trois lignes : le problème que venv résout, les deux commandes (créer, activer), le signe visuel d'activation. Sans notes : détruisez le dossier .venv et recréez-le — c'est jetable, c'est fait pour.
Jour 35 — pip et PyPI : installer une bibliothèque
Objectif : installer requests dans la bulle, et figer la liste des dépendances.
Théorie (20 min)
PyPI (Python Package Index, pypi.org) est le magasin officiel des bibliothèques Python : plus de 500 000 paquets gratuits. pip est le livreur, installé avec Python :
pip install requests
(Bulle active, évidemment.) pip télécharge le paquet et ceux dont il dépend, et les range dans .venv. pip show requests affiche version et description ; pip list tout ce qui est installé ; pip uninstall requests enlève.
Deux gestes de professionnel :
- Figer les dépendances :
pip freeze > requirements.txtécrit la liste exacte des paquets et versions installés dans un fichierrequirements.txt. N'importe qui (y compris vous sur une autre machine) recrée la bulle à l'identique avecpip install -r requirements.txt. C'est ce fichier qu'on partage, jamais .venv. - Choisir un paquet populaire : sur PyPI, regardez la date de la dernière version et le nombre d'utilisateurs. Un paquet abandonné depuis cinq ans est un risque ; requests, téléchargé des millions de fois par jour, est un standard.
Hygiène de sécurité, une phrase : n'installez que des paquets que vous avez cherchés et reconnus, jamais un nom recopié d'un message inconnu — les fautes de frappe sur les noms de paquets sont un piège classique (typosquatting : reqeusts au lieu de requests).
Que partage-t-on pour qu'un collègue retrouve le même environnement ?
- Le dossier .venv zippé
- Le fichier requirements.txt produit par pip freeze
- La liste des commandes tapées
Réponse
requirements.txt : léger, portable, rejouable. .venv se recrée, il ne voyage pas.
Qu'est-ce que le typosquatting ?
- Un bug de pip
- Une faute de frappe dans votre code
- Un paquet malveillant nommé presque comme un paquet célèbre
Réponse
D'où la règle : vérifier le nom exact sur pypi.org avant d'installer.
Pratique (30 min)
- Dans le dossier
bibliothequesd'hier, bulle active :pip install requests. pip list: requests est là, avec ses dépendances (urllib3, certifi… — pip a installé la famille au complet).- Critère de réussite :
python -c "import requests; print(requests.__version__)"affiche un numéro de version, par exemple 2.32.x. pip freeze > requirements.txtpuis ouvrez le fichier : chaque ligne est un paquet épinglé à sa version exacte.- Preuve de rejouabilité :
pip uninstall -y requests, vérifiez que l'import plante, puispip install -r requirements.txt— tout revient. C'est la manoeuvre que vous ferez sur chaque nouveau projet.
Tips agent : « installe requests » : l'agent le fera — vérifiez qu'il l'a fait DANS la bulle (pip list) et qu'il a mis à jour requirements.txt. Piège : les deux oublis les plus fréquents. Preuve : détruisez .venv, recréez-la, pip install -r requirements.txt, lancez le programme.
Synthèse (10 min)
Trois lignes : PyPI et pip, freeze/requirements, la règle anti-typosquatting. Sans notes : installez puis désinstallez un petit paquet au choix, requirements.txt à jour à chaque étape.
Jour 36 — requests : parler à une API web
Objectif : récupérer des données depuis une API publique avec requests.
Théorie (20 min)
Une API (interface de programmation) est une porte d'entrée d'un service web destinée aux programmes, pas aux humains : au lieu d'une page HTML jolie, elle rend des données brutes, le plus souvent en JSON (demain). Votre navigateur fait des requêtes GET quand vous visitez une page ; requests fait la même chose depuis Python :
import requests
reponse = requests.get("https://api.github.com", timeout=10)
print(reponse.status_code)
Trois choses à connaître dans la réponse :
status_code: le verdict HTTP. 200 = succès, 404 = adresse inconnue, 403 = refusé, 500 = le serveur a planté. Vérifiez-le toujours avant d'utiliser les données ;.text: le contenu brut, en texte ;.json(): le contenu décodé en dictionnaires et listes Python — le cadeau du jour 37 ;- et l'argument
timeout=10: sans lui, une requête peut attendre le réseau pour toujours. Toujours un timeout.
Le réseau est capricieux : il faut internet, le service peut être en panne, et les adresses changent. Un programme qui parle au réseau doit donc toujours gérer l'échec poliment — on combinera try/except et status_code en pratique.
Un status_code 404 signifie…
- Succès
- Adresse inconnue du serveur
- Le serveur a planté
Réponse
404 : la ressource demandée n'existe pas à cette adresse. 200 est le succès.
Pourquoi toujours passer timeout= à requests.get ?
- Sinon le programme peut attendre le réseau indéfiniment
- Pour accélérer la requête
- C'est obligatoire syntaxiquement
Réponse
Sans timeout, un réseau muet bloque votre programme sans fin.
Pratique (30 min)
- Bulle active, requests installé. Au REPL : la requête du cours, vérifiez le 200, puis
reponse.text[:100]pour voir le début du contenu. - Créez
meteo.pyavec l'API ouverte wttr.in (pas de compte, pas de clé) :
import requests
ville = input("Ville ? ")
url = f"https://wttr.in/{ville}?format=3"
reponse = requests.get(url, timeout=10)
if reponse.status_code == 200:
print(reponse.text)
else:
print(f"Erreur {reponse.status_code}")
- Critère de réussite : tapez « Lyon » : une ligne « Lyon: ☀️ +18°C » (selon le temps réel) s'affiche. Tapez une ville inexistante : votre else répond proprement.
- Coupez le wifi, relancez : requests lève une erreur de connexion. Ajoutez
try: ... except requests.RequestException:autour du get pour afficher « réseau injoignable » au lieu de planter. Rebranchez.
Tips agent : « appelle cette API et extrais le champ X » : l'agent écrit la requête, mais il invente parfois des champs qui n'existent pas dans la réponse réelle. Piège classique. Preuve : exigez un print de la réponse brute (ou de response.json()) et vérifiez les champs vous-même.
Synthèse (10 min)
Trois lignes : ce qu'est une API, status_code à vérifier toujours, timeout obligatoire. Sans notes : requête GET, test du 200, affichage du texte, except réseau.
Jour 37 — json : le format des API
Objectif : lire et écrire du JSON, le format d'échange du web.
Théorie (20 min)
JSON (JavaScript Object Notation) est le format texte dans lequel les services s'échangent des données : il ressemble à un dictionnaire Python — accolades, crochets, deux-points — et c'est fait exprès :
{"nom": "Alice", "age": 30, "langues": ["fr", "en"]}
Différences avec Python : les chaînes sont en guillemets doubles uniquement, et les valeurs spéciales s'écrivent true, false, null (minuscules) au lieu de True, False, None.
Le module json de la bibliothèque standard traduit dans les deux sens :
json.loads(texte): texte JSON → objets Python (loads = load string) ;json.dumps(objet): objets Python → texte JSON (dumps = dump string) ;- et requests fait le premier sens tout seul :
reponse.json().
Après décodage, ce sont vos vieux amis des modules 1 et 2 : des dictionnaires et des listes, qu'on lit avec les crochets. Une réponse d'API est souvent un gros dict imbriqué : on navigue étage par étage, data["main"]["temp"], en affichant les clés à chaque étage (data.keys()) pour se repérer.
Pour sauvegarder des données Python dans un fichier : json.dumps(data, indent=2, ensure_ascii=False) produit un texte lisible, indenté, avec les accents intacts — le format idéal pour les petites sauvegardes de vos programmes.
Différence entre json.loads et json.dumps ?
- Aucune, même fonction
- loads : texte JSON → objets Python ; dumps : objets Python → texte JSON
- loads lit un fichier, dumps l'écrit
Réponse
le 's' final veut dire « string » : les deux travaillent sur du texte.
En JSON, la valeur « rien » s'écrit…
- None
- vide
- null
Réponse
null, en minuscules. json.loads le traduit en None.
Pratique (30 min)
- Au REPL :
import json, puisjson.loads('{"a": 1}'), puisjson.dumps({"prénom": "Chloé"}, ensure_ascii=False). - Créez
repos.py— l'API publique de GitHub, sans compte :
import requests
url = "https://api.github.com/repos/python/cpython"
reponse = requests.get(url, timeout=10)
if reponse.status_code == 200:
data = reponse.json()
print(f"Projet : {data['full_name']}")
print(f"Étoiles : {data['stargazers_count']}")
print(f"Langage : {data['language']}")
else:
print(f"Erreur {reponse.status_code}")
- Critère de réussite : trois lignes réelles s'affichent (projet python/cpython, un grand nombre d'étoiles, langage Python). Changez l'URL pour un autre dépôt que vous connaissez.
- Explorez : ajoutez
print(sorted(data.keys()))pour voir tous les champs disponibles, choisissez-en un autre (description ?) et affichez-le. - Sauvegarde : écrivez
datadansdepot.jsonavec json.dumps + Path.write_text, rouvrez le fichier dans l'éditeur : c'est du JSON lisible.
Tips agent : « extrais les champs X, Y de cette réponse JSON » : collez la réponse RÉELLE (ou son début) — sinon l'agent invente la structure. Piège n°1 du travail avec les API. Preuve : ses accès data["..."] ne doivent lever aucune KeyError sur la vraie réponse.
Synthèse (10 min)
Trois lignes : JSON vs dict Python, loads/dumps, naviguer étage par étage. Sans notes : lire une réponse d'API, extraire deux champs, sauvegarder en JSON indenté.
Jour 38 — Lire un traceback sans peur
Objectif : déchiffrer un traceback complet : la pile d'appels, la ligne fautive, la cause.
Théorie (20 min)
Au jour 7, vous avez appris à lire un message d'erreur simple. Avec les fonctions, les erreurs traversent plusieurs étages : le traceback (« trace arrière ») raconte tout le chemin. Exemple :
Traceback (most recent call last):
File "app.py", line 12, in <module>
afficher_total("abc")
File "app.py", line 8, in afficher_total
total = additionner(prix)
File "app.py", line 4, in additionner
return float(a) + float(b)
~~~~~^^^
ValueError: could not convert string to float: 'abc'
Lecture, toujours de bas en haut. La dernière ligne : le type d'erreur et la cause précise (on ne peut pas convertir 'abc'). La ligne au-dessus : l'endroit exact où ça a explosé, fichier + numéro de ligne + le code fautif, avec des chapeaux ^^^ sous le coupable (nouveauté bienvenue de Python 3.11+). Les lignes du dessus : la pile d'appels — qui a appelé qui pour en arriver là, du programme principal vers la fonction coupable.
Le réflexe professionnel : l'erreur se répare rarement là où elle explose. Ici, float("abc") est innocent : c'est l'appelant, ligne 12, qui a passé une mauvaise valeur. Remontez la pile jusqu'à trouver d'où vient la donnée fautive.
Et la technique qui fait gagner des heures : copier la dernière ligne du traceback telle quelle dans un moteur de recherche. Vous n'êtes jamais le premier à la rencontrer.
Dans un traceback, où est la cause ?
- Tout en bas : le type d'erreur et son message
- Tout en haut
- Au milieu de la pile
Réponse
Most recent call LAST : la fin raconte l'erreur, le haut raconte le chemin.
La fonction où l'erreur explose est-elle toujours la coupable ?
- Oui, toujours
- Non : souvent c'est l'appelant qui a fourni la mauvaise donnée — remontez la pile
- Seulement pour les ValueError
Réponse
La pile montre qui a appelé qui : la donnée fautive vient souvent d'un étage au-dessus.
Pratique (30 min)
- Créez
casse.py, avec une vraie pile de trois fonctions :
def convertir(texte):
return float(texte)
def moyenne(a, b):
return (convertir(a) + convertir(b)) / 2
def principale():
print(moyenne("10", "quinze"))
principale()
- Lancez. Critère de réussite : vous identifiez sans aide, dans le traceback affiché : la dernière ligne (ValueError), la ligne fautive (convertir), et l'origine de la mauvaise donnée (principale, qui passe "quinze").
- Réparez à la source ("15"), relancez : 12.5.
- Réparez autrement : gardez "quinze" et ajoutez un try/except ValueError dans convertir qui rend 0.0 — observez comme le programme survit, et demandez-vous si c'est une bonne idée (un 0 silencieux fausse la moyenne : parfois planter vaut mieux).
Tips agent : collez le traceback ENTIER plus le code : « cite la ligne fautive et la ligne qui lui a passé la mauvaise donnée ». Piège : sans le code, il devine la cause la plus fréquente, pas la vôtre. Preuve : sa réparation doit faire disparaître le traceback chez vous.
Synthèse (10 min)
Trois lignes : structure d'un traceback (cause / lieu / pile), pourquoi remonter la pile, le coup de la recherche web. Sans notes : provoquez une KeyError à deux étages et racontez son traceback à voix haute.
Jour 39 — Déboguer : print stratégique et pdb
Objectif : trouver un bug qui ne plante pas — un résultat faux — avec le print de diagnostic et le débogueur.
Théorie (20 min)
Le bug le plus dur n'est pas celui qui plante : c'est celui qui donne un résultat faux en silence. Pas de traceback, juste un mauvais chiffre. La méthode a un nom : diviser pour régner. On coupe le programme en deux avec une sonde, on regarde de quel côté le mensonge commence, on recoupe, jusqu'à la ligne fautive.
La sonde la plus simple est le print de diagnostic, avec un préfixe qui le distingue :
def calculer_total(prix_liste):
total = 0
for prix in prix_liste:
total = total + prix
print(f"DEBUG total={total}")
return total
On observe les valeurs intermédiaires, on trouve où ça déraille, on retire les sondes. C'est rudimentaire et ça résout la majorité des bugs du quotidien.
L'outil intégré pour les cas têtus : pdb, le débogueur de Python. Insérez breakpoint() dans le code, lancez le programme : il se gèle à cette ligne et vous donne un prompt (Pdb) où vous inspectez à loisir : tapez le nom d'une variable pour voir sa valeur, n (next) pour avancer d'une ligne, c (continue) pour reprendre, q pour quitter. C'est un REPL au beau milieu de l'exécution.
Le programme rend un résultat faux sans planter. Première étape ?
- Tout réécrire
- Demander à une IA
- Placer une sonde (print DEBUG ou breakpoint) au milieu et vérifier les valeurs
Réponse
Diviser pour régner : localiser la moitié fautive avant de chercher la ligne.
Dans pdb, que fait la commande n ?
- Exécute la ligne suivante et s'arrête
- Quitte le débogueur
- Affiche la valeur de n
Réponse
next : une ligne de plus. c continue jusqu'au prochain breakpoint, q quitte.
Pratique (30 min)
- Créez
bug.pyavec ce programme volontairement faux (il doit donner la moyenne des prix positifs) :
def moyenne_positifs(prix):
total = 0
for p in prix:
total = total + p
return total / len(prix)
print(moyenne_positifs([10, -5, 20]))
- Lancez : affiche 8.33… mais la moyenne des positifs (10 et 20) est 15. Le programme a sommé le -5 et compté trois prix. Trouvez-le avec des sondes, pas en relisant : placez
print(f"DEBUG p={p} total={total}")dans la boucle, observez, puis corrigez (il faut filtrerif p > 0et compter les positifs séparément). - Critère de réussite : la version corrigée affiche 15.0.
- Rejouez la même chasse avec pdb : remettez le bug, insérez
breakpoint()avant la boucle, lancez, avancez avecnen affichantpettotalà chaque tour,qpour sortir. Même verdict, autre outil.
Tips agent : « le résultat attendu est 15, j'obtiens 8.33, voici le code » : donner attendu vs constaté est la moitié du travail de l'agent. Piège : sans le résultat attendu, il « améliore » le code sans réparer le bug. Preuve : le chiffre 15.0 après sa correction.
Synthèse (10 min)
Trois lignes : diviser pour régner, print DEBUG, breakpoint()/n/c/q. Sans notes : réparez un accumulateur qui oublie le premier élément (initialisé à tort à 1) en une chasse aux sondes.
Jour 40 — Automatisation : renommer des fichiers en masse
Objectif : renommer des dizaines de fichiers d'un coup, avec un mode d'essai (dry-run) avant tout déplacement.
Théorie (20 min)
Première vraie automatisation : renommer une collection de fichiers selon une règle. Le cas d'école : des photos nommées IMG_0001.JPG qu'on veut en vacances-01.jpg. Tout est déjà vu : iterdir (jour 33), méthodes de chaînes (jour 19), rename (jour 33).
La règle de sécurité qui fait la différence entre un script utile et un désastre : le dry-run (essai à sec). Le programme affiche d'abord ce qu'il ferait — ancien nom → nouveau nom — sans rien toucher ; vous vérifiez ; vous relancez en mode réel. Tout script qui déplace, renomme ou supprime mérite un dry-run.
Second garde-fou : travailler sur une copie. Les essais se font dans un dossier de test rempli de faux fichiers, jamais sur les vraies photos.
Deux méthodes de chaînes utiles pour les noms : .zfill(2) complète un nombre avec des zéros ("3".zfill(2) → "03"), et la concaténation de stem + nouveau suffixe se fait proprement avec les attributs de Path : f.stem, f.suffix.
Pratique (30 min)
- Fabrication du terrain : créez un dossier
photos, puis générez des faux fichiers par script (la machine travaille pour vous) :
from pathlib import Path
dossier = Path("photos")
dossier.mkdir(exist_ok=True)
for i in range(1, 6):
(dossier / f"IMG_000{i}.JPG").write_text("faux", encoding="utf-8")
- Le renommeur,
renommer.py:
from pathlib import Path
dossier = Path("photos")
mode_reel = False # passez à True pour exécuter
photos = sorted(dossier.glob("*.JPG"))
for i, f in enumerate(photos, 1):
nouveau = dossier / f"vacances-{str(i).zfill(2)}{f.suffix.lower()}"
print(f"{f.name} -> {nouveau.name}")
if mode_reel:
f.rename(nouveau)
- Critère de réussite (dry-run) : le programme affiche cinq lignes de correspondance
IMG_0001.JPG -> vacances-01.jpget le dossier n'a pas changé — vérifiez avec l'explorateur. enumerate(photos, 1)est nouveau : il donne à chaque tour l'index (en partant de 1) ET l'élément — fini le compteur à la main.- Passez
mode_reel = True, relancez, vérifiez le dossier. Remettez False. - Piège à constater : relancez le dry-run maintenant : glob("*.JPG") ne trouve plus rien (tout est en .jpg). Un bon script signale « rien à faire » au lieu de ne rien dire : ajoutez
if not photos: print("Aucun fichier trouvé.").
Tips agent : « renomme ces fichiers selon telle règle » : exactement le genre de tâche à déléguer — avec dry-run EXIGÉ dans la consigne, et sur une copie. Piège : un agent lancé sans dry-run peut renommer 300 fichiers avec une règle fausse. Preuve : la liste prévue, affichée, relue, avant le moindre rename.
Synthèse (10 min)
Trois lignes : dry-run, enumerate, zfill. Sans notes : script qui préfixe tous les .txt d'un dossier par « lu- », dry-run d'abord.
Jour 41 — Automatisation : ranger un dossier
Objectif : trier un dossier en sous-dossiers par type de fichier — la base du projet final.
Théorie (20 min)
Le dossier Téléchargements de tout le monde : 200 fichiers mélangés. La règle de rangement : les images dans images/, les PDF dans documents/, les archives dans archives/, le reste dans divers/. Traduction en structures connues : un dictionnaire extension → sous-dossier (jour 21), un parcours de dossier (jour 33), rename pour déplacer (jour 33), dry-run (jour 40).
REGLES = {
".jpg": "images", ".jpeg": "images", ".png": "images",
".pdf": "documents", ".txt": "documents",
".zip": "archives",
}
Pour chaque fichier : destination = REGLES.get(f.suffix.lower(), "divers") — le .get avec défaut du jour 22, qui range tout ce qui n'est pas prévu dans « divers ». Le .lower() absorbe les .JPG du jour 33.
Un détail de robustesse réel : les collisions de noms. Si documents/facture.pdf existe déjà, rename l'écrase silencieusement sous Linux. Avant de déplacer, testez cible.exists() ; si c'est le cas, ajoutez un suffixe -1, -2… au stem jusqu'à trouver un nom libre. Cinq lignes qui évitent de perdre un fichier.
Pratique (30 min)
- Terrain : dossier
telechargements+ script de génération de 8 faux fichiers variés (2 .jpg, 1 .JPG, 2 .pdf, 1 .zip, 1 .mp3, 1 .txt). - Écrivez
ranger.py(à faire seul à partir du plan ci-dessus ; aide si bloqué : boucle iterdir, filtrerif f.is_file(), get sur REGLES, mkdir(exist_ok=True) du sous-dossier, cible = sous-dossier / f.name, print puis rename si mode réel). - Critère de réussite (dry-run) : huit lignes de correspondance, chacune vers le bon sous-dossier, le .JPG majuscule rangé dans images/, le .mp3 (non prévu) dans divers/.
- Mode réel, vérifiez le dossier : huit fichiers rangés en quatre sous-dossiers.
- Test de collision : recréez un
facture.pdfà la racine alors quedocuments/facture.pdfexiste, ajoutez la gestioncible.exists()avec suffixe, relancez : vous obtenezdocuments/facture-1.pdf, rien n'est écrasé.
Tips agent : ce script est le favori des démos d'agents ; le piège est toujours le même : il écrase les doublons sans prévenir. Exigez la gestion de collision dans la consigne. Preuve : le test du point 5, qui doit produire facture-1.pdf sans perdre l'original.
Synthèse (10 min)
Trois lignes : dict extension → dossier, get avec défaut, le danger des collisions. Sans notes : variantes de règles (par date ? par taille ?) — réfléchissez à ce qui rendrait l'outil utile POUR VOUS : c'est l'ébauche du projet final.
Jour 42 — Bilan du module 3
Objectif : prouver les acquis du module : fonctions, modules, venv/pip, traceback, automatisation.
Théorie (20 min)
Relisez vos notes des jours 29 à 41. Le tableau des acquis :
| Notion | Forme | Séance |
|---|---|---|
| Fonction | def / paramètres / return / docstring | J29-31 |
| Import | import math, random, datetime, pathlib, json | J32-33, 37 |
| Bulle de projet | venv + activate + pip + requirements.txt | J34-35 |
| Réseau | requests.get + status_code + timeout + .json() | J36-37 |
| Erreurs | traceback de bas en haut, print DEBUG, breakpoint() | J38-39 |
| Automatisation | iterdir + glob + rename + dry-run + collisions | J40-41 |
Une notion floue = une séance à refaire avant le bilan, pas après.
Pratique (30 min)
Le programme bilan, veille.py, écrit seul : un script qui interroge une API et archive la réponse.
- Dans un dossier neuf avec sa bulle (venv, pip install requests, freeze) :
- Écrivez une fonction
recuperer(url)qui fait le GET avec timeout et rend le dict JSON, ou None si la requête échoue (try/except + status_code). Docstring comprise. - Écrivez une fonction
sauvegarder(data, chemin)qui écrit le JSON indenté dans un fichier daté (veille-2026-11-03.jsonavecdatetime.date.today()et une f-string). - Programme principal : appelez
https://api.github.com/repos/python/cpython, extrayez trois champs dans un petit dict (nom, étoiles, description), sauvegardez.
Critère de réussite : le fichier JSON daté existe, est lisible dans l'éditeur, et le programme tourne deux fois sans erreur en gérant le cas réseau coupé (testez wifi coupé : message poli, pas de traceback). Chaque brique vient d'une séance du module — si l'une résiste, retournez à sa séance.
Tips agent : faites relire votre bilan : « trouve deux fragilités dans ce script, cite les lignes, ne réécris rien ». Piège : il liste des améliorations cosmétiques au lieu de vrais risques (réseau, encodage). Preuve : chaque fragilité citée doit être déclenchable par un test concret.
Synthèse (10 min)
Trois lignes : ce que vous savez faire maintenant qui était impossible au jour 28. Module 4 demain : on assemble tout dans un projet fini, testé et documenté.
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.