← Bibliothèque · PRO · Automatiser mon quotidien
0/0 ⬇
Collection « Challenges » · Apprendre à coder en Python · Adultes débutants · Pro

Automatiser mon quotidien — 4 semaines pour confier vos tâches répétitives à un script que vous savez corriger

En quatre semaines, vous automatisez vos tâches répétitives : vous faites répéter le travail par une boucle, vous rangez et réutilisez votre code, vous renommez, triez et fusionnez des fichiers (toujours sur une copie), et vous lisez un fichier Excel. Votre boîte à outils est prête.

20 épisodes · 4 semaines1 épisode par jour, du lundi au vendrediBasthon, puis Thonny≈ 25 min / épisode
BOOST · 5 JOURSComprendre ce qu'est un programme et écrire un premier script qui vous fait gagner du temps.
PRO · 20 JOURSAutomatiser vos tâches répétitives (fichiers, tableaux) avec des scripts que vous savez corriger.
MASTER · 4 MOISFaire tourner un outil fiable et documenté, apprendre seul et transmettre à un collègue.
Le fil rouge. Nadia, 47 ans, assistante de gestion dans une PME (personnage fictif), a terminé le Boost. Chaque fin de mois, elle renomme une à une une quarantaine de factures fournisseurs, les range par fournisseur et recopie les montants des relevés dans un récapitulatif : une demi-journée. Dans quatre semaines, un script fera ce rituel à sa place.
Chaque épisode = un scénario, une notion ou une méthode, ce que disent les sources, des idées reçues, un quiz, votre kit et votre journal. Le vendredi, un bilan et un badge.
Copie, simulation, action. À partir de la semaine 3, vos scripts toucheront des fichiers : uniquement dans un dossier d'entraînement fourni par le livre, avec des fichiers inventés. Aucun script du livre ne supprime ni n'écrase quoi que ce soit, et chacun affiche d'abord ce qu'il va faire.
Règle d'or. Un code que vous ne comprenez pas, vous ne le lancez pas.
Ce que vous savez déjàVous écrivez un algorithme puis le programme, avec des variables, des types et des calculs, dont // et % (Boost, épisode 1). Vous lancez un script et vous lisez une erreur en 3 questions (Boost, épisode 2). Vous faites décider le programme avec if / elif / else (Boost, épisode 3). Vous construisez un programme pas à pas, testé sur des cas calculés à la main (Boost, épisode 4), et vous avez écrit votre calculateur d'échéances (Boost, épisode 5). Vous démarrez ici sans avoir suivi le Boost ? Le kit de rattrapage est en fin de livre.
ARC 1 · SEMAINE 1

Répéter sans effort

Cette semaine, vous faites répéter le travail par une boucle, vous nettoyez et mettez en forme du texte, et vous apprenez à chasser les bugs avec méthode. Vendredi, votre script calcule le nouveau nom de chaque facture, sans toucher à aucun fichier.

01
S1 · LUNDI · Comprendre

Répéter sans recopier : les boucles

Faire répéter des instructions un nombre connu de fois avec for, ou tant qu'une condition est vraie avec while.

⏱ 20 min de lecture · 5 min de quiz
[1]Scénario
Nadia · Lundi, 9 h

Fin de mois. Quarante factures attendent. Nadia ouvre son calculateur d'échéances du Boost, fière : il marche. Puis elle fait le compte : le relancer quarante fois, retaper quarante dates. « Mon calculateur sait faire une facture. Il faudrait qu'il sache en faire quarante. » Karim, de la comptabilité, lève les yeux de son écran : « Dans ton tableur, tu tires la formule vers le bas. En Python, ça s'appelle une boucle. »

[2]Concept

La boucle : écrire une fois, répéter autant de fois que nécessaire

Répéter pour chaque valeur : for. Une suite de valeurs s'écrit entre crochets, séparées par des virgules (on l'appelle une liste ; vous l'étudierez en semaine 2). La boucle for prend les valeurs une par une :

Code
total = 0
for montant in [1250.50, 89.90, 45.00, 320.00, 210.40, 1480.00]:
    total = total + montant
print("Total du mois :", round(total, 2), "euros")

Affiche Total du mois : 3395.8 euros. Lisez la deuxième ligne comme une phrase : « pour chaque montant dans cette liste ». À chaque tour (on dit aussi une itération), la variable montant prend la valeur suivante, et les lignes décalées de 4 espaces s'exécutent. Après le dernier tour, Python passe à la ligne qui n'est plus décalée.

L'accumulateur. total est créé avant la boucle, à zéro, puis augmenté à chaque tour : c'est un accumulateur, comme la cellule « Total » au bas d'une colonne. Un compteur fonctionne de la même façon, en ajoutant 1 :

Code
nombre = 0
for montant in [1250.50, 89.90, 45.00, 320.00, 210.40, 1480.00]:
    if montant > 1000:
        nombre = nombre + 1
        print("Grosse facture :", montant)
print(nombre, "factures au-dessus de 1000 euros")

Affiche Grosse facture : 1250.5, Grosse facture : 1480.0, puis 2 factures au-dessus de 1000 euros. Une condition peut se trouver dans une boucle : ce qui dépend du if est décalé une fois de plus.

Répéter un nombre de fois : range.

Code
for i in range(3):
    print("Tour", i)
for numero in range(412, 415):
    print("Facture", numero)

Affiche Tour 0, Tour 1, Tour 2, puis Facture 412, Facture 413, Facture 414. range(3) donne 3 valeurs en commençant à 0 : 0, 1, 2. range(412, 415) va de 412 à 414 : la valeur de fin n'est jamais atteinte.

Répéter tant qu'une condition est vraie : while. Votre calculateur du Boost s'arrêtait sur un délai impossible ; une boucle while (« tant que ») le redemande :

Code
delai = int(input("Délai de paiement en jours (1 à 28) : "))
while delai < 1 or delai > 28:
    print("Le délai doit être compris entre 1 et 28 jours.")
    delai = int(input("Délai de paiement en jours (1 à 28) : "))
print("Délai enregistré :", delai, "jours")

Essai réel :

Ce que vous voyez
Délai de paiement en jours (1 à 28) : 45 Le délai doit être compris entre 1 et 28 jours. Délai de paiement en jours (1 à 28) : 0 Le délai doit être compris entre 1 et 28 jours. Délai de paiement en jours (1 à 28) : 28 Délai enregistré : 28 jours

Tant que le délai est hors limites, on le redemande. Attention : si rien ne change dans la boucle, la condition reste vraie pour toujours. C'est une boucle infinie : arrêtez le programme avec le bouton Stop de votre environnement (ou Ctrl + C). Rien n'est cassé.

Votre besoinLa boucle
traiter chaque valeur d'une suite (des montants, des noms de fichiers)for valeur in [ … ]:
répéter un nombre de fois connufor i in range(n):
répéter tant qu'une condition est vraie (une saisie invalide)while condition:
Suivre les variables · le total et le nombre de factures
Ce qui s'affiche

[3]Recherche

Ce que disent les sources

  • Tutoriel officiel de Python — L'instruction for de Python itère sur les éléments d'une séquence (une liste ou une chaîne), dans l'ordre où ils apparaissent. Avec range, le dernier élément fourni en paramètre ne fait jamais partie de la liste générée : range(10) génère 10 valeurs, de 0 à 9 [1].
  • Tutoriel officiel de Python — La boucle while s'exécute tant que la condition reste vraie ; le corps de la boucle est indenté, et l'indentation est la façon dont Python regroupe les instructions [2].
  • Tutoriel officiel de Python — L'instruction break sort immédiatement de la boucle for ou while la plus proche [1]. Ce livre ne s'en sert pas : une condition de boucle claire suffit dans tous nos exemples.
[4]Mythes
« range(5) va de 1 à 5. »
il va de 0 à 4 : cinq valeurs, en commençant à 0. Pour aller de 1 à 5, on écrit range(1, 6).
« Une boucle while finit toujours par s'arrêter. »
seulement si quelque chose, dans la boucle, finit par rendre la condition fausse (ici, une nouvelle saisie).
« Les boucles, c'est pour les informaticiens. »
vous en faites déjà quand vous tirez une formule vers le bas dans un tableur. La boucle, c'est la poignée de recopie, en plus souple.
[5]Quiz

1. Qu'affiche for i in range(4): print(i * 10) ?

i vaut 0, 1, 2, 3 ; on affiche dix fois i (sortie vérifiée, une valeur par ligne).

2. Où créer l'accumulateur total = 0 ?

dans la boucle, il repartirait de zéro à chaque tour ; vous verrez ce bug jeudi.

3. Avec n = 100, combien de tours fait while n > 10: si chaque tour fait n = n // 2 ?

n vaut 50, 25, 12, puis 6 ; 6 n'est plus plus grand que 10 : quatre tours (sortie vérifiée : 6 4).
[6]Kit

🎒 Votre kit — Épisode 1

20 min

Exercice : prédire, puis répéter

  1. Prédisez sur papier ce qu'affichent ces trois programmes, puis lancez-les (Basthon ou Thonny) :
  • A : for i in range(1, 4): puis, décalé, print(i, i * i)
  • B : compte = 0, puis for montant in [50, 1200, 80, 3000]:, décalé if montant >= 100:, décalé deux fois compte = compte + 1, enfin print(compte)
  • C : total = 0, puis for jour in range(5):, décalé total = total + 2, enfin print(total)
  1. Écrivez une boucle qui additionne les montants de 6 factures inventées, et une autre qui compte celles au-dessus de 1 000 €.
  2. Recopiez la saisie contrôlée du délai et essayez 45, puis 0, puis 28.

Corrigé (sorties réelles) : A affiche 1 1, 2 4, 3 9 ; B affiche 2 ; C affiche 10.

Code à copier : la saisie contrôlée du délai (ci-dessus).

Checklist

  • Je sais lire for montant in [ … ]: comme une phrase.
  • Je crée mes accumulateurs et mes compteurs avant la boucle.
  • Je sais choisir entre for et while.
Vigilancesi un programme ne s'arrête plus, bouton Stop (ou Ctrl + C) ; votre ordinateur ne risque rien.
[7]Journal

✍️ Journal de bord

Quelle tâche faites-vous « pour chaque » élément d'une liste (chaque facture, chaque courriel, chaque ligne) ? Combien de fois par mois ?

[8]Sources
  1. Python Software Foundation, Tutoriel Python — 4. D'autres outils de contrôle de flux (for, range, break), consulté le 31/10/2026.
  2. Python Software Foundation, Tutoriel Python — 3.2. Premiers pas vers la programmation (while, indentation), consulté le 31/10/2026.
02
S1 · MARDI · Pratiquer

Traiter toute une série de factures

D'abord pour une, puis pour toutes : mettre un calcul vérifié dans une boucle.

⏱ 25 min de pratique · 5 min de quiz
[1]Scénario
Nadia · Mardi, 12 h 30

Nadia reprend son calculateur d'échéances. Elle voudrait le lancer une seule fois pour toutes les factures du jour, et obtenir à la fin le total des pénalités à réclamer. Premier essai : le total affiché est celui de la dernière facture seulement. Karim : « Ton total, tu le remets à zéro à chaque tour, non ? »

[2]Méthode

« D'abord pour une, puis pour toutes »

Rappels : la boucle et l'accumulateur (voir épisode 1), une étape, un test, un cas connu (voir Boost, épisode 4).

  1. Pour une : le calcul marche pour une facture, vérifié sur un cas connu (c'est votre calculateur du Boost).
  2. Dans la boucle : on le place sous for i in range(nb_factures):, tout décalé de 4 espaces.
  3. Ce qui ne doit pas repartir à zéro (le total du mois) est créé avant la boucle ; ce qui change à chaque facture est dans la boucle.
  4. On teste sur 2 factures, puis sur toutes ; on vérifie le nombre de résultats affichés et le total.

Le calculateur pour plusieurs factures (année fixée à 2026, une année non bissextile, pour alléger) :

Code
ANNEE = 2026
TAUX_ANNUEL = 12.40
nb_factures = int(input("Nombre de factures : "))
total_penalites = 0
for i in range(nb_factures):
    print("--- Facture", i + 1, "---")
    jour = int(input("Jour de la facture : "))
    mois = int(input("Mois de la facture (1 à 12) : "))
    delai = int(input("Délai de paiement en jours (1 à 28) : "))
    montant = float(input("Montant en euros (avec un point) : "))
    jours_retard = int(input("Jours de retard (0 si payée à temps) : "))
    if mois == 2:
        jours_du_mois = 28
    elif mois == 4 or mois == 6 or mois == 9 or mois == 11:
        jours_du_mois = 30
    else:
        jours_du_mois = 31
    jour_echeance = jour + delai
    mois_echeance = mois
    annee_echeance = ANNEE
    if jour_echeance > jours_du_mois:
        jour_echeance = jour_echeance - jours_du_mois
        mois_echeance = mois + 1
    if mois_echeance == 13:
        mois_echeance = 1
        annee_echeance = ANNEE + 1
    print("Échéance :", str(jour_echeance) + "/" + str(mois_echeance) + "/" + str(annee_echeance))
    if jours_retard > 0:
        penalites = round(montant * TAUX_ANNUEL / 100 * jours_retard / 365, 2)
        print("Pénalités :", penalites, "euros + 40 euros d'indemnité")
        total_penalites = total_penalites + penalites + 40
print("Total à réclamer ce mois-ci :", round(total_penalites, 2), "euros")

ANNEE et TAUX_ANNUEL sont écrits en majuscules : ce sont des réglages qui ne changent pas pendant le programme (on les appelle des constantes ; vous y reviendrez en semaine 2). Essai réel avec 3 factures (le taux est celui de l'exemple du Boost) :

Ce que vous voyez
Nombre de factures : 3 --- Facture 1 --- Jour de la facture : 5 Mois de la facture (1 à 12) : 3 Délai de paiement en jours (1 à 28) : 20 Montant en euros (avec un point) : 1850 Jours de retard (0 si payée à temps) : 0 Échéance : 25/3/2026 --- Facture 2 --- Jour de la facture : 15 Mois de la facture (1 à 12) : 4 Délai de paiement en jours (1 à 28) : 28 Montant en euros (avec un point) : 1200 Jours de retard (0 si payée à temps) : 30 Échéance : 13/5/2026 Pénalités : 12.23 euros + 40 euros d'indemnité --- Facture 3 --- Jour de la facture : 20 Mois de la facture (1 à 12) : 12 Délai de paiement en jours (1 à 28) : 28 Montant en euros (avec un point) : 1850 Jours de retard (0 si payée à temps) : 12 Échéance : 17/1/2027 Pénalités : 7.54 euros + 40 euros d'indemnité Total à réclamer ce mois-ci : 99.77 euros

Les trois échéances sont celles des cas de référence du Boost ; le total se vérifie à la main : 12,23 + 40 + 7,54 + 40 = 99,77 €.

Le bug du premier essai (sortie réelle, avec les montants 100, 250 et 50) :

Code
nb_factures = 3
for i in range(nb_factures):
    total = 0
    montant = float(input("Montant en euros : "))
    total = total + montant
print("Total :", total)

Affiche Total : 50.0 au lieu de 400 : total = 0 est dans la boucle, il repart de zéro à chaque facture. On le remonte d'une ligne, avant le for.

[3]Pourquoi ça marche

Un calcul vérifié une fois reste juste quand on le répète : la boucle ne change pas le calcul, elle le refait. Les erreurs viennent presque toujours de ce qui est placé au mauvais niveau : un accumulateur remis à zéro dans la boucle, ou un print final décalé dans la boucle (qui s'affiche alors à chaque tour). Vérifier le nombre de lignes affichées et un total calculé à la main suffit à les repérer.

À retenirce qui se cumule avant la boucle, ce qui change dans la boucle, le résultat final après la boucle.
[4]Entraînement

Bon réflexe ou piège ?

Je mets total = 0 dans la boucle, pour être sûre qu'il démarre à zéro.
il repart de zéro à chaque tour ; seul le dernier montant compte.
Je teste d'abord avec 2 factures dont je connais le résultat.
un cas connu et court révèle les erreurs de niveau.
Je lance directement sur les 40 factures du mois.
40 saisies pour découvrir une erreur qu'on aurait vue avec 2.
Je vérifie que le programme affiche autant d'échéances que de factures.
une ligne en trop ou en moins trahit un décalage.
J'oublie le retrait sous le for.
Python signale une IndentationError (« expected an indented block », un bloc décalé était attendu) ; elle se corrige en une seconde.
[5]Quiz

1. Dans le calculateur, pourquoi total_penalites = 0 est-il écrit avant le for ?

créé avant la boucle, il garde sa valeur d'un tour à l'autre (sortie vérifiée : 99.77).

2. Le print du total est décalé de 4 espaces, sous le for. Que se passe-t-il ?

tout ce qui est décalé sous le for s'exécute à chaque tour.

3. Avec nb_factures = 3, combien de fois la ligne print("--- Facture", i + 1, "---") s'exécute-t-elle, et avec quels numéros ?

i vaut 0, 1, 2 ; on affiche i + 1 pour numéroter à partir de 1.
[6]Kit

🎒 Votre kit — Épisode 2

25 min

Exercice : votre calculateur pour toute une série

  1. Recopiez le calculateur pour plusieurs factures.
  2. Testez-le avec les 3 factures de l'essai ci-dessus : vous devez obtenir les mêmes échéances et 99,77 €.
  3. Déplacez total_penalites = 0 dans la boucle, relancez, observez, puis remettez-le à sa place.
  4. Ajoutez un compteur des factures en retard, affiché à la fin (attendu avec l'essai : 2).

Code à copier : le calculateur pour plusieurs factures.

Checklist

  • J'obtiens les 3 échéances et le total de l'essai.
  • J'ai provoqué puis corrigé le bug de l'accumulateur.
  • Mon compteur de factures en retard affiche 2.
Vigilancefactures inventées ; le calculateur reste une aide, le contrat fait foi.
[7]Journal

✍️ Journal de bord

Dans votre calculateur, qu'est-ce qui devait rester en dehors de la boucle ? Comment l'avez-vous vu ?

[8]Sources
  1. Python Software Foundation, Tutoriel Python — 4. D'autres outils de contrôle de flux, consulté le 31/10/2026.
  2. Sorties du calculateur pour plusieurs factures et de sa version boguée : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
03
S1 · MERCREDI · Comprendre

Le texte et les f-strings

Découper, nettoyer et assembler du texte, puis composer des affichages lisibles avec les f-strings.

⏱ 20 min de lecture · 5 min de quiz
[1]Scénario
Nadia · Mercredi, 14 h

Le logiciel de facturation exporte les factures du mois avec des noms comme F00412 papeterie MARTIN 03-10-2026.pdf : le numéro, le fournisseur écrit n'importe comment, la date à la française. La règle de la PME demande 2026-10-03_Papeterie-Martin_F00412.pdf : la date à l'envers (pour que les fichiers se classent dans l'ordre), le fournisseur propre, le numéro. Nadia le fait à la main depuis six ans. « Si Python sait découper un texte, il sait faire ça. »

[2]Concept

Les chaînes de caractères et les f-strings

Une chaîne, c'est une suite de caractères numérotés. Le texte s'appelle une chaîne de caractères (str). Chaque caractère a un indice, en commençant à 0 ; les indices négatifs comptent depuis la fin.

Code
nom = "F00412 papeterie MARTIN 03-10-2026.pdf"
print(len(nom))
print(nom[0], nom[-1])
print(nom[:6])
print(nom[-4:])
print(nom[-14:-4])
print(nom[7:-15])

Affiche 38, puis F f, F00412, .pdf, 03-10-2026 et papeterie MARTIN. len(nom) donne le nombre de caractères. nom[:6] est une tranche : du début jusqu'à l'indice 6, non compris (les 6 premiers caractères). nom[-4:] : les 4 derniers. nom[-14:-4] : du 14e caractère avant la fin jusqu'au 4e avant la fin, non compris : la date.

Remettre la date à l'endroit. Avec des tranches et + (qui colle des chaînes, vu au Boost) :

Code
date = "03-10-2026"
date_iso = date[6:] + "-" + date[3:5] + "-" + date[:2]
print(date_iso)

Affiche 2026-10-03. Cette écriture année-mois-jour est celle de la norme internationale ISO 8601 ; triés par ordre alphabétique, les fichiers se rangent alors par date.

Les méthodes : des outils attachés au texte. On les appelle avec un point :

Code
brut = "  PAPETERIE martin "
print(brut.strip())
print(brut.strip().lower())
print(brut.strip().title())
print(brut.strip().title().replace(" ", "-"))
print(brut)

Affiche PAPETERIE martin, papeterie martin, Papeterie Martin, Papeterie-Martin, puis PAPETERIE martin (inchangé).

MéthodeCe qu'elle renvoieExemple
.strip()le texte sans espaces au début ni à la fin" a b " → "a b"
.lower() / .upper()tout en minuscules / en majuscules"Leroy" → "leroy"
.title()une majuscule au début de chaque mot"café du coin" → "Café Du Coin"
.replace(a, b)le texte où chaque a est remplacé par b"1250,50" → "1250.50"
.startswith(t) / .endswith(t)True si le texte commence / finit par t"x.PDF".endswith(".pdf") → False
.zfill(n)le texte complété par des zéros à gauche jusqu'à n caractères"412".zfill(6) → "000412"

On peut les enchaîner : elles s'appliquent de gauche à droite. Et in teste si un texte en contient un autre :

Code
nom = "F00417 transports rapides 25-10-2026.PDF"
print(nom.endswith(".pdf"), nom.lower().endswith(".pdf"))
print(nom.startswith("F"), "rapides" in nom, "Rapides" in nom)
print("412".zfill(6), "1250,50".replace(",", "."), float("1250,50".replace(",", ".")))

Affiche False True, puis True True False, puis 000412 1250.50 1250.5. Les comparaisons de texte tiennent compte des majuscules : on passe en minuscules avant de comparer. Et voici la solution à la ValueError du Boost (Boost, épisode 5) : remplacer la virgule par un point avant float.

Un texte ne se modifie pas. Une méthode renvoie un nouveau texte ; la variable d'origine ne change pas, sauf si on y range le résultat :

Code
nom = "papeterie martin"
nom.title()
print(nom)
nom = nom.title()
print(nom)

Affiche papeterie martin, puis Papeterie Martin.

Les f-strings : assembler du texte et des valeurs. Un f devant les guillemets, et les valeurs entre accolades :

Code
montant = 1250.5
fournisseur = "Papeterie-Martin"
print(f"{fournisseur} : {montant} euros")
print(f"{fournisseur} : {montant:.2f} euros")
print(f"[{fournisseur:<20}][{montant:>10.2f}]")
print(f"{montant=}")

Affiche Papeterie-Martin : 1250.5 euros, puis Papeterie-Martin : 1250.50 euros, puis [Papeterie-Martin ][ 1250.50], puis montant=1250.5. Après les deux-points : .2f = 2 chiffres après la virgule ; <20 = aligné à gauche sur 20 caractères ; >10 = aligné à droite sur 10. {montant=} affiche le nom et la valeur : très utile pour vérifier (jeudi). Un récapitulatif aligné :

Code
print(f"{'Fournisseur':<20}{'Montant':>12}")
for fournisseur in ["  PAPETERIE martin ", "imprimerie LEROY", " café du coin"]:
    print(f"{fournisseur.strip().title():<20}{'à saisir':>12}")
total = 1250.5 + 89.9 + 45
print(f"{'Total':<20}{total:>10.2f} €")

Sortie réelle :

Ce que vous voyez
Fournisseur Montant Papeterie Martin à saisir Imprimerie Leroy à saisir Café Du Coin à saisir Total 1385.40 €

Un nom de fichier n'est pas un texte comme les autres. Sous Windows, il ne peut pas contenir < > : " / \ | ? *, et certains noms sont réservés (CON, PRN, NUL…) ; Windows ne distingue pas Facture.pdf de facture.pdf. La règle de la PME (chiffres, lettres, tirets, _) les évite tous.

Atelier de nommage · cochez les méthodes dans l'ordre où vous voulez les appliquer
Étapes (les espaces sont montrés par ·)
La ligne de Python
[3]Recherche

Ce que disent les sources

  • Tutoriel officiel de Python — Les chaînes peuvent être indicées, le premier caractère ayant l'indice 0 ; les indices négatifs comptent depuis la droite. Le début d'une tranche est toujours inclus et la fin toujours exclue. Les chaînes de caractères sont immuables : on ne peut pas modifier un caractère, il faut créer une nouvelle chaîne [1].
  • Documentation officielle des méthodes de chaînes — str.title() renvoie une version du texte où chaque mot commence par une capitale ; la documentation prévient que l'algorithme définit un mot comme une suite de lettres, ce qui donne des résultats inattendus avec les apostrophes ("they're bill's friends" donne "They'Re Bill'S Friends") [2].
  • Documentation officielle — Les f-strings (chaînes littérales formatées) acceptent un spécificateur de format après les deux-points (.2f, <20, >10) ; depuis Python 3.8, {expression=} affiche l'expression et sa valeur [3][4].
  • Microsoft — Dans les noms de fichiers Windows, les caractères < > : " / \ | ? * sont réservés, ainsi que les noms CON, PRN, AUX, NUL, COM1 à COM9 et LPT1 à LPT9, même avec une extension ; il ne faut pas supposer que les noms distinguent les majuscules [5].
[4]Mythes
« .lower() modifie la variable. »
il renvoie un nouveau texte ; il faut écrire nom = nom.lower() pour garder le résultat.
« Arrondir avec round et formater avec :.2f, c'est pareil. »
round(1250.5, 2) reste le nombre 1250.5 ; f"{1250.5:.2f}" est le texte "1250.50", toujours avec deux chiffres. Pour afficher des euros, on formate.
« Un nom de fichier, c'est juste du texte. »
il a une extension, des caractères interdits sous Windows et des règles de majuscules qui dépendent du système. Une règle de nommage simple évite les surprises.
[5]Quiz

1. Que vaut "F00412 papeterie"[:6] ?

les 6 premiers caractères, indices 0 à 5 (sortie vérifiée).

2. Que vaut " Leroy ".strip().upper() ?

strip enlève les espaces, puis upper met en majuscules (sortie vérifiée).

3. Qu'affiche print(f"{89.9:.2f}") ?

.2f affiche toujours deux chiffres après la virgule (sortie vérifiée).
[6]Kit

🎒 Votre kit — Épisode 3

20 min

Exercice : nettoyer et mettre en forme

  1. Pour chacun de ces noms bruts, écrivez sur papier ce que donnera .strip().title().replace(" ", "-"), puis vérifiez dans la console : " PAPETERIE martin ", "imprimerie LEROY", " café du coin", "NETTOYAGE pro ", "transports RAPIDES", "papeterie Martin".
  2. À partir de "F00415 nettoyage pro 12-10-2026.pdf", extrayez avec des tranches le numéro, le fournisseur et la date, puis remettez la date à l'endroit.
  3. Recopiez le récapitulatif aligné et remplacez « à saisir » par trois montants inventés, affichés avec :.2f.

Corrigé (sorties réelles) : Papeterie-Martin, Imprimerie-Leroy, Café-Du-Coin, Nettoyage-Pro, Transports-Rapides, Papeterie-Martin. Pour la question 2 : F00415, nettoyage pro, 2026-10-12.

Code à copier : le récapitulatif aligné (ci-dessus).

Checklist

  • Je sais extraire une partie d'un texte avec une tranche.
  • Je range le résultat d'une méthode dans une variable.
  • J'affiche des montants avec :.2f.
Vigilance.title() met aussi une majuscule aux petits mots (Café Du Coin) ; si la règle de votre entreprise est différente, vous adapterez avec .replace.
[7]Journal

✍️ Journal de bord

Quels textes recopiez-vous ou corrigez-vous à la main dans votre travail (noms de fichiers, objets de courriels, références) ? Quelle méthode vous ferait gagner le plus de temps ?

[8]Sources
  1. Python Software Foundation, Tutoriel Python — 3.1.2. Chaînes de caractères, consulté le 31/10/2026.
  2. Python Software Foundation, Types natifs — Méthodes de chaînes de caractères, consulté le 31/10/2026.
  3. Python Software Foundation, Tutoriel Python — 7.1.1. Les chaînes de caractères formatées (f-strings) et Mini-langage de spécification de format (docs.python.org/fr/3/tutorial/inputoutput.html ; docs.python.org/fr/3/library/string.html), consulté le 31/10/2026.
  4. Python Software Foundation, Nouveautés de Python 3.8 — f-strings et spécificateur =, consulté le 30/10/2026.
  5. Microsoft, Naming Files, Paths, and Namespaces, consulté le 31/10/2026.
04
S1 · JEUDI · Pratiquer

Déboguer avec méthode

Reproduire, afficher, réduire, corriger, retester : trouver un bug même quand Python ne dit rien.

⏱ 25 min de pratique · 5 min de quiz
[1]Scénario
Nadia · Jeudi, 10 h

Le script de nettoyage de Nadia transforme " Imprimerie leroy " en --Imprimerie-Leroy-, ses dates deviennent 026-0--3-, et son total des factures ne compte que la dernière. Aucun message rouge : le programme tourne et se trompe en silence. Son premier réflexe : tout effacer et recommencer. Karim : « Tu ne sais même pas encore où est le problème. Cherche-le d'abord. »

[2]Méthode

« Reproduire, afficher, réduire, corriger, retester »

Rappel : lire un message d'erreur en 3 questions (voir Boost, épisode 2). Ici, il n'y a pas de message : c'est un bug silencieux (un « bug » est une erreur dans un programme).

  1. Reproduire avec un cas dont vous connaissez la réponse : " Imprimerie leroy " doit donner Imprimerie-Leroy.
  2. Afficher les valeurs intermédiaires avec un print de contrôle : print(f"CONTRÔLE {date=}") (le = de mercredi).
  3. Réduire : isoler la plus petite partie du code qui montre encore le bug (une seule ligne, une seule valeur).
  4. Suivre pas à pas avec le débogueur de Thonny (un débogueur exécute le programme ligne par ligne en montrant les variables) : Ctrl + F5 pour lancer en mode débogage, F6 pour avancer d'un grand pas, F7 d'un petit pas, menu View → Variables (Affichage → Variables si Thonny est en français) pour voir les valeurs. Basthon n'a pas de débogueur : on y utilise les print de contrôle.
  5. Corriger une seule chose, puis retester tous les cas connus.

Bug 1 : l'ordre des méthodes.

Code
brut = "  Imprimerie leroy "
propre = brut.replace(" ", "-").strip().title()
print(propre)

Affiche --Imprimerie-Leroy-. Réduit à une ligne et relu dans l'ordre : replace change tous les espaces en tirets, y compris ceux du début et de la fin ; ensuite, strip ne trouve plus d'espaces à enlever. Correction : brut.strip().title().replace(" ", "-") → Imprimerie-Leroy.

Bug 2 : la tranche décalée.

Code
nom = "F00412 papeterie MARTIN 03-10-2026.pdf"
date = nom[-13:-4]
print(f"{date=}")
date_iso = date[6:] + "-" + date[3:5] + "-" + date[:2]
print(date_iso)

Affiche date='3-10-2026', puis 026-0--3-. Le print de contrôle montre tout de suite le problème : il manque le premier chiffre, la tranche commence un caractère trop tard. Correction : nom[-14:-4] → date='03-10-2026', puis 2026-10-03.

Bug 3 : l'accumulateur qui oublie.

Code
total = 0
for montant in [1250.50, 89.90, 45.00, 320.00]:
    total = montant
print("Total :", total)

Affiche Total : 320.0 au lieu de 1705.4. Avec un print(f"CONTRÔLE {montant=} {total=}") dans la boucle, on voit à chaque tour que total vaut le montant du tour, et non la somme (sortie réelle) :

Ce que vous voyez
CONTRÔLE montant=1250.5 total=1250.5 CONTRÔLE montant=89.9 total=89.9 CONTRÔLE montant=45.0 total=45.0 CONTRÔLE montant=320.0 total=320.0 Total : 320.0

Correction : total = total + montant → Total : 1705.4.

Suivre les variables · le bug 3 : l'accumulateur qui oublie
Ce qui s'affiche

[3]Pourquoi ça marche

Un bug silencieux, c'est un écart entre ce que vous croyez que le programme fait et ce qu'il fait vraiment. Relire le code ne montre que ce que vous croyez ; afficher les valeurs, ou les suivre au débogueur, montre ce qui se passe. Réduire le problème évite de chercher dans cinquante lignes ce qui tient en une. Et corriger une seule chose à la fois permet de savoir laquelle a réglé le problème.

À retenirun cas connu, des valeurs affichées, le plus petit exemple, une correction, et on reteste tout.
[4]Entraînement

Bon réflexe ou piège ?

J'efface tout et je réécris le script.
vous risquez de refaire la même erreur, et vous perdez ce qui marchait.
J'affiche les valeurs intermédiaires avec f"{date=}".
vous voyez le nom et la valeur, sans vous tromper de variable.
Je corrige trois choses d'un coup, puis je relance.
si ça marche, vous ne savez pas quelle correction était la bonne.
Après la correction, je reteste tous mes cas connus, pas seulement celui qui posait problème.
une correction peut en casser une autre.
Une fois le bug trouvé, je laisse mes print de contrôle dans le script.
on les retire, ou on utilise le débogueur, qui ne touche pas au code.
[5]Quiz

1. Le total d'une boucle vaut le dernier montant. Que montre un print de contrôle de total dans la boucle ?

total = montant remplace au lieu d'ajouter (sortie vérifiée).

2. Pourquoi brut.replace(" ", "-").strip() laisse-t-il des tirets aux extrémités ?

les méthodes s'appliquent de gauche à droite ; strip n'enlève que les espaces (sortie vérifiée).

3. Dans Thonny, quelle touche lance un programme en mode débogage ?

Ctrl + F5 lance le débogage ; F6 et F7 font avancer d'un grand ou d'un petit pas.
[6]Kit

🎒 Votre kit — Épisode 4

25 min

Exercice : la chasse aux trois bugs

  1. Recopiez les trois programmes bogués.
  2. Pour chacun : écrivez le résultat attendu, reproduisez le bug, ajoutez un print de contrôle, trouvez la ligne fautive.
  3. Corrigez une chose à la fois, relancez, comparez à l'attendu (Imprimerie-Leroy, 2026-10-03, Total : 1705.4).
  4. Si vous utilisez Thonny, suivez le bug 3 au débogueur (Ctrl + F5, puis F7).

Code à copier : les trois programmes bogués (ci-dessus).

Checklist

  • J'ai reproduit chaque bug avec un cas connu.
  • J'ai trouvé chaque ligne fautive en affichant les valeurs.
  • Les trois programmes corrigés donnent le résultat attendu.
Vigilanceavant de modifier un script qui marche presque, gardez-en une copie (enregistrez-le sous un autre nom).
[7]Journal

✍️ Journal de bord

Quand un calcul est faux dans votre tableur, comment cherchez-vous l'erreur ? Qu'est-ce que la méthode du jour ajoute ?

[8]Sources
  1. A. Annamaa, Thonny, Python IDE for beginners (débogueur pas à pas : Ctrl + F5, F6, F7), consulté le 30/10/2026.
  2. Python Software Foundation, Nouveautés de Python 3.8 — f-strings et spécificateur =, consulté le 30/10/2026.
  3. Sorties des programmes bogués et corrigés : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
05
S1 · VENDREDI · Pratiquer · Bilan d'arc

Répéter sans effort : le bilan

Votre outil de synthèse, le récapitulatif de la semaine et votre badge.

⏱ 25 min · arbre de décision · carrousel · quiz final
[1]Scénario
Nadia · Vendredi, 17 h

Nadia lance son script devant Karim. Six lignes apparaissent : à gauche, le nom exporté par le logiciel ; à droite, le nom selon la règle de la PME. Aucun fichier n'a été touché : tout est affiché à l'écran. Karim vérifie trois noms à la main. « C'est exactement ça. Et lundi prochain ? » Nadia : « Lundi, j'apprends à ranger tout ça dans des listes et des fonctions. Les vrais fichiers, ce sera dans deux semaines. Sur une copie. »

[2]Outil

« for ou while ? »

Aide à la décision interactive
[3]Carrousel

🎨 Carrousel récapitulatif — Semaine 1

[4]Quiz final

1. Qu'affiche for i in range(2, 8, 2): print(i) ? (le troisième nombre est le pas)

on part de 2, on avance de 2, et 8 n'est jamais atteint (sortie vérifiée, une valeur par ligne).

2. Que vaut " FACT-412 ".strip().lower()[:4] ?

on enlève les espaces, on passe en minuscules, puis on garde les 4 premiers caractères (sortie vérifiée).

3. total = 0, puis for montant in [10, 20, 30]: avec total = total + montant décalé, puis print(total). Qu'affiche le programme ?

le print n'est pas décalé : il s'exécute une fois, après la boucle (sortie vérifiée).

4. Vous voulez redemander un montant tant qu'il est négatif. Quelle boucle ?

on ne sait pas d'avance combien de fois il faudra redemander.

5. Le résultat est faux, sans message d'erreur. Que faites-vous d'abord ?

reproduire et afficher avant de corriger ; des affichages ciblés valent mieux que cinquante lignes au hasard.
[5]Kit

🎒 Votre kit de fin d'arc

25 min

Le nouveau nom de chaque facture

Synthèse : une boucle, des tranches, des méthodes et une f-string calculent le nouveau nom de six factures, sans toucher à aucun fichier.

Code à copier
for ancien in ["F00412 papeterie MARTIN 03-10-2026.pdf",
               "F00413   Imprimerie leroy  05-10-2026.pdf",
               "F00414 CAFÉ DU COIN 07-10-2026.pdf",
               "F00415 nettoyage pro 12-10-2026.pdf",
               "F00416 Papeterie Martin 18-10-2026.pdf",
               "F00417 transports rapides 25-10-2026.PDF"]:
    numero = ancien[:6]
    fournisseur = ancien[7:-15].strip().title().replace(" ", "-")
    date = ancien[-14:-4]
    date_iso = date[6:] + "-" + date[3:5] + "-" + date[:2]
    nouveau = f"{date_iso}_{fournisseur}_{numero}.pdf"
    print(f"{ancien:<42} -> {nouveau}")

Une suite de valeurs entre crochets peut s'écrire sur plusieurs lignes : Python attend le crochet fermant. Sortie réelle :

Ce que vous voyez
F00412 papeterie MARTIN 03-10-2026.pdf -> 2026-10-03_Papeterie-Martin_F00412.pdf F00413 Imprimerie leroy 05-10-2026.pdf -> 2026-10-05_Imprimerie-Leroy_F00413.pdf F00414 CAFÉ DU COIN 07-10-2026.pdf -> 2026-10-07_Café-Du-Coin_F00414.pdf F00415 nettoyage pro 12-10-2026.pdf -> 2026-10-12_Nettoyage-Pro_F00415.pdf F00416 Papeterie Martin 18-10-2026.pdf -> 2026-10-18_Papeterie-Martin_F00416.pdf F00417 transports rapides 25-10-2026.PDF -> 2026-10-25_Transports-Rapides_F00417.pdf

Les espaces en trop (F00413) et l'extension en majuscules (.PDF) sont absorbés : la tranche [7:-15] garde les espaces, que strip enlève, et le nouveau nom écrit toujours .pdf. Ce script suppose que tous les noms suivent le même modèle (numéro sur 6 caractères au début, date à la fin) ; la semaine prochaine, vous traiterez aussi les noms qui ne le suivent pas.

Consigne : recopiez le script, ajoutez deux noms inventés au même modèle, vérifiez trois nouveaux noms à la main, puis exportez votre carnet de bord (bouton dans le menu).

Checklist

  • J'ai exporté mon carnet (bouton dans le menu).
  • Mon script affiche le nouveau nom de mes 8 factures.
  • J'ai vérifié trois nouveaux noms à la main.
[6]Journal

✍️ Journal de bord — bilan

Quelle tâche de cette semaine vous a semblé la plus « magique » ? Et laquelle vous paraît encore fragile ?

✦ PLUS JAMAIS À LA MAIN · SEMAINE 1 ✦

Bravo ! Vous avez terminé la semaine 1. Vous savez faire répéter un calcul, nettoyer et mettre en forme du texte, et trouver un bug avec méthode.

ARC 2 · SEMAINE 2

Ranger et réutiliser

Cette semaine, vous rangez vos séries dans des listes, vous écrivez vos propres fonctions et vous relisez votre code comme le ferait une collègue. Vendredi, une seule fonction calcule le nouveau nom de n'importe quelle facture, et met de côté celles qu'elle ne sait pas lire.

06
S2 · LUNDI · Comprendre

Ranger des séries dans des listes

Une liste range plusieurs valeurs sous un seul nom : on y accède par position, on la parcourt, on la trie, on la complète.

⏱ 20 min de lecture · 5 min de quiz
[1]Scénario
Nadia · Lundi, 9 h

Dans le script de Nadia, les montants du mois sont rangés dans douze variables, de facture1 à facture12. Une treizième facture arrive : il faut ajouter une variable, modifier le calcul du total, celui du maximum… Karim jette un œil : « Tu ne mettrais pas douze cellules nommées dans ton tableur. Tu ferais une colonne. »

[2]Concept

La liste : plusieurs valeurs, un seul nom

Créer et lire une liste. Vous avez déjà parcouru des suites de valeurs entre crochets (épisode 1) : ce sont des listes. On peut les ranger dans une variable et lire chaque élément par sa position, son indice, en commençant à 0, comme pour les caractères d'un texte (épisode 3) :

Code
montants = [1250.50, 89.90, 45.00, 320.00, 210.40, 1480.00]
print(montants[0], montants[-1])
print(montants[1:3], montants[-3:])
print(len(montants), round(sum(montants), 2), min(montants), max(montants))

Affiche 1250.5 1480.0, puis [89.9, 45.0] [320.0, 210.4, 1480.0], puis 6 3395.8 45.0 1480.0. Les tranches marchent comme pour le texte. len donne le nombre d'éléments ; sum, min et max font le total, le plus petit et le plus grand, comme SOMME, MIN et MAX dans un tableur.

Un indice qui n'existe pas. Avec trois éléments, les indices vont de 0 à 2 ; montants[3] provoque une erreur (sortie réelle) :

Message d'erreur
IndexError: list index out of range

« Erreur d'indice : indice de liste hors limites. » Lisez-la avec la méthode du Boost (Boost, épisode 2) : la ligne, le type, la cause.

Trier.

Code
montants = [1250.50, 89.90, 45.00, 320.00, 210.40, 1480.00]
print(sorted(montants))
print(sorted(montants, reverse=True)[:3])
print(montants)

Affiche [45.0, 89.9, 210.4, 320.0, 1250.5, 1480.0], puis [1480.0, 1250.5, 320.0] (les trois plus grosses factures), puis la liste d'origine, inchangée. sorted renvoie une nouvelle liste triée ; reverse=True trie du plus grand au plus petit.

Compléter et modifier. Contrairement au texte, une liste se modifie :

Code
montants = [1250.50, 89.90, 45.00]
montants.append(99.99)
montants[1] = 98.90
print(montants)
print(45.00 in montants, 50 in montants)

Affiche [1250.5, 98.9, 45.0, 99.99], puis True False. .append(valeur) ajoute à la fin ; liste[indice] = valeur remplace un élément ; in teste la présence.

Parcourir.

Code
noms = ["Papeterie-Martin", "Imprimerie-Leroy", "Café-Du-Coin"]
for nom in noms:
    print(nom)
for i in range(len(noms)):
    print(i + 1, noms[i])

La première boucle affiche les trois noms ; la seconde affiche 1 Papeterie-Martin, 2 Imprimerie-Leroy, 3 Café-Du-Coin. for nom in noms suffit le plus souvent ; range(len(noms)) sert quand on a besoin de la position (pour numéroter, ou pour lire au même rang dans deux listes).

Construire une liste dans une boucle. On part d'une liste vide [] et on ajoute ce qui nous intéresse :

Code
grosses = []
for montant in [1250.50, 89.90, 320.00, 45.00]:
    if montant > 200:
        grosses.append(montant)
print(grosses, len(grosses))

Affiche [1250.5, 320.0] 2.

Du texte à la liste, et retour. split découpe un texte à chaque séparateur et renvoie une liste ; join fait l'inverse :

Code
morceaux = "FACT-00418_Papeterie Martin_19-10-2026".split("_")
print(morceaux)
print(len(morceaux), morceaux[1])
print("-".join(["Papeterie", "Martin"]))
print("03-10-2026".split("-"))

Affiche ['FACT-00418', 'Papeterie Martin', '19-10-2026'], puis 3 Papeterie Martin, puis Papeterie-Martin, puis ['03', '10', '2026']. C'est l'outil qui manquait la semaine dernière : un nom de fichier dont les parties sont séparées par _ se découpe sans compter les caractères.

Suivre les variables · une liste construite dans une boucle
Ce qui s'affiche

[3]Recherche

Ce que disent les sources

  • Tutoriel officiel de Python — Les listes peuvent être indicées et découpées en tranches comme les chaînes ; mais, contrairement aux chaînes, qui sont immuables, les listes sont modifiables : on peut changer leur contenu, et ajouter des éléments à la fin avec la méthode append() [1].
  • Documentation officielle des fonctions natives — sorted() renvoie une nouvelle liste triée à partir des éléments de son argument ; le paramètre reverse=True trie en ordre décroissant ; sum(), min() et max() renvoient la somme, le plus petit et le plus grand élément [2].
  • Documentation officielle des méthodes de chaînes — str.split(sep) renvoie une liste des mots de la chaîne, en utilisant sep comme séparateur ; str.join(iterable) renvoie une chaîne qui est la concaténation des chaînes de l'itérable, séparées par la chaîne sur laquelle la méthode est appelée [3].
[4]Mythes
« Le premier élément est à l'indice 1. »
il est à l'indice 0, comme le premier caractère d'un texte ; le dernier est à l'indice len(liste) - 1, ou plus simplement -1.
« sorted(liste) trie la liste. »
il renvoie une nouvelle liste triée ; la liste d'origine ne bouge pas (sortie vérifiée).
« Une liste, c'est une colonne de tableur. »
presque : c'est une suite de valeurs dans l'ordre, mais sans en-tête, et elle peut contenir des nombres, des textes, et même d'autres listes.
[5]Quiz

1. Avec montants = [1250.50, 89.90, 45.00], que vaut montants[1] ?

l'indice 1 est le deuxième élément (les indices commencent à 0).

2. Que renvoie "FACT-00418_Papeterie Martin_19-10-2026".split("_") ?

split découpe à chaque _ et renvoie la liste des morceaux (sortie vérifiée : ['FACT-00418', 'Papeterie Martin', '19-10-2026']).

3. Après m = [3, 1, 2] et t = sorted(m), que vaut m ?

sorted renvoie une nouvelle liste triée, rangée ici dans t ; m est inchangée.
[6]Kit

🎒 Votre kit — Épisode 6

20 min

Exercice : prédire six expressions Avec m = [120.0, 45.5, 980.0, 15.0], écrivez sur papier ce que valent, puis vérifiez dans la console :

  1. m[1]
  2. m[-1]
  3. m[:2]
  4. len(m)
  5. max(m) - min(m) (l'étendue)
  6. sorted(m)[0]

Puis remplacez, dans votre script de la semaine 1, les montants écrits un par un par une liste montants, et affichez le total, la plus grosse facture et les trois plus petites.

Corrigé (sorties réelles) : 45.5 ; 15.0 ; [120.0, 45.5] ; 4 ; 965.0 ; 15.0.

Code à copier : la construction d'une liste dans une boucle (grosses, ci-dessus).

Checklist

  • Je lis un élément par son indice, en commençant à 0.
  • Je sais que sorted ne modifie pas la liste d'origine.
  • Je construis une liste avec [] et append dans une boucle.
VigilanceIndexError signifie que vous demandez une position qui n'existe pas ; vérifiez len(liste).
[7]Journal

✍️ Journal de bord

Quelles « colonnes » de votre travail pourraient devenir des listes (montants, noms de clients, dates de rendez-vous) ? Que voudriez-vous en calculer ?

[8]Sources
  1. Python Software Foundation, Tutoriel Python — 3.1.3. Listes et 5.1. Compléments sur les listes (docs.python.org/fr/3/tutorial/introduction.html ; datastructures.html), consulté le 31/10/2026.
  2. Python Software Foundation, Fonctions natives — sorted, sum, min, max, len, consulté le 31/10/2026.
  3. Python Software Foundation, Types natifs — Méthodes de chaînes de caractères — split, join, consulté le 31/10/2026.
07
S2 · MARDI · Pratiquer

Trier et filtrer une liste de factures

Une liste en entrée, une liste en sortie : filtrer, trier par date et repérer les doublons.

⏱ 25 min de pratique · 5 min de quiz
[1]Scénario
Nadia · Mardi, 12 h 30

Clôture dans trois jours. Le comptable demande la liste des factures de la Papeterie Martin, dans l'ordre chronologique, et signale qu'une facture a peut-être été reçue deux fois (une fois par courriel, une fois par le scanner). Nadia a les douze noms du mois sous les yeux, déjà au format de la PME. Elle s'apprête à les recopier dans un tableur pour les trier.

[2]Méthode

« Une liste en entrée, une liste en sortie »

Rappels : la boucle et la condition (voir épisode 1), la liste, append, sorted et in (voir épisode 6).

  1. Une liste vide pour le résultat : martin = [].
  2. Une boucle sur la liste d'entrée ; une condition ; append si elle est vraie.
  3. Trier avec sorted : comme les noms commencent par la date AAAA-MM-JJ, l'ordre alphabétique est l'ordre chronologique.
  4. Les doublons : une liste des noms déjà vus ; un nom déjà présent est un doublon.
  5. Vérifier avec des comptages : combien en entrée, combien en sortie, combien de différents.
Code
noms = ["2026-10-18_Papeterie-Martin_F00416.pdf", "2026-10-05_Imprimerie-Leroy_F00413.pdf",
        "2026-10-03_Papeterie-Martin_F00412.pdf", "2026-10-25_Transports-Rapides_F00417.pdf",
        "2026-10-12_Nettoyage-Pro_F00415.pdf", "2026-10-19_Papeterie-Martin_F00418.pdf",
        "2026-10-07_Café-Du-Coin_F00414.pdf", "2026-10-21_Nettoyage-Pro_F00419.pdf",
        "2026-10-03_Papeterie-Martin_F00412.pdf", "2026-10-28_Imprimerie-Leroy_F00420.pdf",
        "2026-10-30_Café-Du-Coin_F00421.pdf", "2026-10-31_Transports-Rapides_F00422.pdf"]
martin = []
for nom in noms:
    if "_Papeterie-Martin_" in nom:
        martin.append(nom)
print(len(martin), "factures Papeterie Martin")
for nom in sorted(martin):
    print(nom)
deja_vus = []
doublons = []
for nom in noms:
    if nom in deja_vus:
        doublons.append(nom)
    else:
        deja_vus.append(nom)
print("Doublons :", doublons)
print(len(noms), "noms,", len(deja_vus), "différents")

Sortie réelle :

Ce que vous voyez
4 factures Papeterie Martin 2026-10-03_Papeterie-Martin_F00412.pdf 2026-10-03_Papeterie-Martin_F00412.pdf 2026-10-18_Papeterie-Martin_F00416.pdf 2026-10-19_Papeterie-Martin_F00418.pdf Doublons : ['2026-10-03_Papeterie-Martin_F00412.pdf'] 12 noms, 11 différents

Le doublon apparaît deux fois dans la liste triée, et le comptage le confirme : 12 noms, 11 différents. On cherche "_Papeterie-Martin_" avec ses _ pour ne pas attraper, un jour, une « Papeterie-Martinez ».

Pourquoi la date à l'envers. Triées comme du texte, les dates à la française se mélangent ; à l'envers, elles se rangent (sortie réelle) :

Code
print(sorted(["03-10-2026", "25-09-2026", "12-10-2026"]))
print(sorted(["2026-10-03", "2026-09-25", "2026-10-12"]))

Affiche ['03-10-2026', '12-10-2026', '25-09-2026'] (le 25 septembre se retrouve après le 12 octobre), puis ['2026-09-25', '2026-10-03', '2026-10-12'].

Le piège : modifier une liste pendant qu'on la parcourt.

Code
montants = [50.0, 80.0, 1250.5, 30.0, 320.0]
for m in montants:
    if m < 100:
        montants.remove(m)
print(montants)

Affiche [80.0, 1250.5, 320.0] : le 80 est resté ! En retirant le 50, la liste s'est décalée sous la boucle, qui a sauté l'élément suivant. La solution est la méthode du jour : construire une nouvelle liste avec ce qu'on garde.

Code
montants = [50.0, 80.0, 1250.5, 30.0, 320.0]
gros = []
for m in montants:
    if m >= 100:
        gros.append(m)
print(gros)

Affiche [1250.5, 320.0].

[3]Pourquoi ça marche

Construire une nouvelle liste laisse la liste d'origine intacte : on peut toujours recompter, comparer, recommencer. C'est la même prudence que celle que vous appliquerez aux fichiers en semaine 3 : on ne touche pas aux données d'origine, on produit un résultat à côté. Et les comptages (entrée, sortie, différents) transforment une impression (« ça a l'air juste ») en vérification.

À retenirliste vide, boucle, condition, append ; on ne retire jamais d'éléments d'une liste qu'on parcourt.
[4]Entraînement

Bon réflexe ou piège ?

Je retire les petits montants de la liste pendant la boucle.
la liste se décale et la boucle saute des éléments (sortie vérifiée : le 80 est resté).
Je trie des dates écrites JJ-MM-AAAA avec sorted.
elles se trient comme du texte, jour d'abord ; il faut l'ordre année-mois-jour.
Je compare le nombre de noms et le nombre de noms différents.
un écart signale un doublon, avant même de le chercher.
Je cherche "Martin" tout court.
vous attraperez aussi un client « Martinez » ou un « Martin » dans un autre champ ; cherchez la partie complète, avec ses séparateurs.
Je supprime automatiquement les doublons trouvés.
on les signale ; c'est une personne qui décide (le même nom peut cacher deux factures différentes).
[5]Quiz

1. Pour filtrer une liste, que crée-t-on avant la boucle ?

on y ajoute avec append ce qui passe la condition.

2. Pourquoi sorted range-t-il les noms 2026-10-03_… dans l'ordre chronologique ?

on compare d'abord l'année, puis le mois, puis le jour, caractère par caractère (sortie vérifiée).

3. 12 noms, 11 différents : combien de doublons au moins ?

un nom en trop dans la liste, c'est au moins un doublon (sortie vérifiée : un doublon).
[6]Kit

🎒 Votre kit — Épisode 7

25 min

Exercice : préparer la clôture

  1. Recopiez le programme ci-dessus (la liste des 12 noms est dans le code à copier).
  2. Ajoutez la liste des factures de l'Imprimerie Leroy, triée (attendu : 2 factures, F00413 puis F00420).
  3. Ajoutez la liste des factures de la seconde quinzaine (date à partir du 16 : comparez nom[8:10] à "16"). Attendu : 7 noms.
  4. Provoquez le piège de remove, observez, puis corrigez avec une nouvelle liste.

Code à copier : le programme de filtrage, de tri et de doublons (ci-dessus).

Checklist

  • Je filtre en construisant une nouvelle liste.
  • Je trie des noms qui commencent par une date AAAA-MM-JJ.
  • Je vérifie avec des comptages.
Vigilanceun doublon se signale, il ne se supprime pas automatiquement.
[7]Journal

✍️ Journal de bord

Quelle liste triez-vous ou filtrez-vous à la main dans votre travail ? Quelle condition de tri ou de filtre utilisez-vous ?

[8]Sources
  1. Python Software Foundation, Tutoriel Python — 4.2. L'instruction for (modifier une collection pendant qu'on la parcourt : créer plutôt une nouvelle collection), consulté le 31/10/2026.
  2. ISO, ISO 8601 — International standard for date and time format (date au format AAAA-MM-JJ, créé pour lever la confusion entre les formats nationaux) (help.iso.org, article du 31/07/2025), consulté le 31/10/2026.
  3. Sorties du filtrage, du tri et du piège de remove : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
08
S2 · MERCREDI · Comprendre

Vos propres fonctions

Donner un nom à une tâche, lui passer des paramètres, récupérer une valeur renvoyée, et la vérifier une fois pour toutes.

⏱ 20 min de lecture · 5 min de quiz
[1]Scénario
Nadia · Mercredi, 14 h

Le nettoyage d'un nom de fournisseur (.strip().title().replace(" ", "-")) est recopié à trois endroits du script de Nadia. Elle décide que « Café Du Coin » doit devenir « Cafe-Du-Coin », sans accent, pour son ancien logiciel… et ne modifie que deux endroits sur trois. Karim : « Une règle, un seul endroit. Ça s'appelle une fonction. »

[2]Concept

La fonction : une tâche, un nom

Définir et appeler.

Code
def nettoyer(texte):
    """Renvoie le nom de fournisseur propre : sans espaces autour, majuscules aux mots, tirets."""
    return texte.strip().title().replace(" ", "-")


print(nettoyer("  PAPETERIE martin "))
print(nettoyer("imprimerie LEROY"))
propre = nettoyer(" café du coin")
print(propre, len(propre))

Affiche Papeterie-Martin, Imprimerie-Leroy, puis Café-Du-Coin 12.

  • def (de define, définir) crée la fonction ; nettoyer est son nom ; texte est son paramètre : une variable qui reçoit la valeur donnée à l'appel.
  • Le corps est décalé de 4 espaces. La première ligne entre triples guillemets est la docstring : une phrase qui dit ce que fait la fonction.
  • return (renvoyer) donne le résultat à celui qui a appelé la fonction.
  • À l'appel, nettoyer(" PAPETERIE martin "), la valeur entre parenthèses est l'argument. Le résultat peut être affiché, rangé dans une variable (propre), ou utilisé dans un calcul.
  • Python lit le def sans rien exécuter ; le corps ne s'exécute qu'à l'appel. Deux lignes vides séparent la fonction du reste du programme.

Afficher ou renvoyer : la différence qui compte.

Code
def nettoyer_affiche(texte):
    print(texte.strip().title())


def nettoyer_renvoie(texte):
    return texte.strip().title()


a = nettoyer_affiche("  leroy ")
b = nettoyer_renvoie("  leroy ")
print("a vaut", a)
print("b vaut", b)

Affiche Leroy, puis a vaut None, puis b vaut Leroy. La première fonction affiche, mais ne renvoie rien : une fonction sans return renvoie None (« rien »). On ne peut pas réutiliser son résultat. Règle : une fonction qui calcule renvoie ; c'est le programme qui décide d'afficher.

Plusieurs paramètres, une valeur par défaut, des fonctions qui s'appellent.

Code
def date_iso(date_fr):
    """Transforme une date JJ-MM-AAAA en AAAA-MM-JJ."""
    return date_fr[6:] + "-" + date_fr[3:5] + "-" + date_fr[:2]


def nettoyer(texte):
    """Renvoie le nom de fournisseur propre : sans espaces autour, majuscules aux mots, tirets."""
    return texte.strip().title().replace(" ", "-")


def nouveau_nom(date_fr, fournisseur, numero, extension=".pdf"):
    """Assemble le nom de fichier selon la règle de la PME."""
    return date_iso(date_fr) + "_" + nettoyer(fournisseur) + "_" + numero + extension


assert date_iso("03-10-2026") == "2026-10-03"
assert nettoyer("  PAPETERIE martin ") == "Papeterie-Martin"
assert nouveau_nom("03-10-2026", "papeterie MARTIN", "F00412") == "2026-10-03_Papeterie-Martin_F00412.pdf"
print(nouveau_nom("05-10-2026", " Imprimerie leroy ", "F00413"))
print(nouveau_nom("31-10-2026", "transports rapides", "F00422", ".PDF".lower()))
print("Les 3 vérifications passent.")

Affiche 2026-10-05_Imprimerie-Leroy_F00413.pdf, puis 2026-10-31_Transports-Rapides_F00422.pdf, puis Les 3 vérifications passent. Les arguments sont reçus dans l'ordre des paramètres. extension=".pdf" est une valeur par défaut : si on ne la donne pas, c'est .pdf. Une fonction peut en appeler une autre : nouveau_nom utilise date_iso et nettoyer.

Vérifier une fonction : assert. assert condition (« affirmer ») ne dit rien si la condition est vraie, et arrête le programme si elle est fausse. C'est une vérification sur un cas connu, écrite une fois et refaite à chaque lancement. Avec une erreur dans date_iso (date_fr[0:1] au lieu de date_fr[:2]), le programme s'arrête dès la vérification (sortie réelle) :

Ce que vous voyez
assert date_iso("03-10-2026") == "2026-10-03" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AssertionError

« Erreur d'affirmation » : la fonction ne donne pas ce qu'on attend, et on le sait avant d'avoir renommé quoi que ce soit.

Les variables d'une fonction restent dans la fonction.

Code
def total_du_mois(montants):
    total = 0
    for m in montants:
        total = total + m
    return total


print(total_du_mois([100, 250]))
print(total)

Affiche 350, puis s'arrête sur NameError: name 'total' is not defined. total est une variable locale : elle n'existe que pendant l'exécution de la fonction. Pour récupérer un résultat, on utilise return. C'est une protection : une fonction ne peut pas modifier par accident les variables du reste du programme.

Nommer une fonction par une action (un verbe ou ce qu'elle fabrique) : nettoyer, date_iso, nouveau_nom, et pas f1 ni traitement.

Machine à fonction · def nettoyer(texte): return texte.strip().title().replace(" ", "-")
Choisissez une entrée pour voir ce que la machine fait.

Valeurs calculées avec la fonction du livre, en Python, à la fabrication du livre.

[3]Recherche

Ce que disent les sources

  • Tutoriel officiel de Python — Le mot-clé def introduit une définition de fonction ; il est suivi du nom de la fonction et de la liste des paramètres entre parenthèses ; les instructions qui constituent le corps de la fonction commencent à la ligne suivante et doivent être indentées. La première instruction peut être une chaîne de documentation (docstring). Une fonction sans return renvoie None [1].
  • Tutoriel officiel de Python — On peut donner une valeur par défaut à un ou plusieurs paramètres ; la fonction peut alors être appelée avec moins d'arguments. Les variables affectées dans une fonction sont stockées dans la table des symboles locale [1].
  • PEP 257 — La docstring d'une ligne doit tenir sur une ligne, être une phrase qui finit par un point, et décrire l'effet de la fonction sous forme d'instruction (« Renvoie… ») [2].
  • Référence du langage — L'instruction assert est une façon d'insérer des vérifications dans un programme ; si l'expression est fausse, elle lève AssertionError [3].
[4]Mythes
« print et return, c'est pareil. »
print affiche pour vous ; return donne le résultat au programme. Une fonction qui affiche renvoie None (sortie vérifiée).
« Une variable créée dans une fonction existe partout. »
elle est locale ; en dehors, NameError (sortie vérifiée).
« Les fonctions, c'est pour les gros programmes. »
dès qu'une règle sert deux fois, une fonction évite de la corriger à deux endroits ; et une fonction vérifiée par assert est une pièce sur laquelle on peut compter.
[5]Quiz

1. Qu'affiche print(double(21), bonjour(), bonjour("Karim"), rien(3)), avec def double(x): return x * 2, def bonjour(nom="Nadia"): return "Bonjour " + nom et def rien(x): x * 2 ?

la valeur par défaut est utilisée quand on ne donne pas d'argument ; rien calcule sans renvoyer, donc renvoie None (sortie vérifiée).

2. Que se passe-t-il si assert nettoyer(" a ") == "A" est vraie ?

assert est silencieux quand la condition est vraie.

3. Une fonction crée total = 0 à l'intérieur. Peut-on lire total après l'appel ?

total est local à la fonction (sortie vérifiée : NameError).
[6]Kit

🎒 Votre kit — Épisode 8

20 min

Exercice : vos trois premières fonctions

  1. Recopiez date_iso, nettoyer et nouveau_nom, avec leurs vérifications.
  2. Ajoutez une vérification par fonction, sur un cas inventé (par exemple date_iso("25-12-2026")).
  3. Cassez volontairement date_iso (changez une tranche) et relancez : observez l'AssertionError, puis réparez.
  4. Écrivez une fonction tva(montant_ht, taux=20) qui renvoie le montant de TVA arrondi au centime, et vérifiez-la : tva(100) doit valoir 20.0 et tva(100, 5.5) 5.5.

Code à copier : les trois fonctions et leurs vérifications (ci-dessus).

Checklist

  • Mes fonctions renvoient leur résultat avec return.
  • Chacune a une docstring et au moins une vérification assert.
  • J'ai vu une AssertionError et je sais ce qu'elle signifie.
Vigilanceles taux de TVA de l'exercice servent d'exemple de calcul ; le taux applicable dépend du produit ou du service.
[7]Journal

✍️ Journal de bord

Quelle règle de votre travail appliquez-vous à plusieurs endroits (un calcul, une mise en forme) ? Quel nom donneriez-vous à la fonction ?

[8]Sources
  1. Python Software Foundation, Tutoriel Python — 4.8. Définir des fonctions et 4.9.1. Valeur par défaut des arguments, consulté le 31/10/2026.
  2. D. Goodger, G. van Rossum, PEP 257 – Docstring Conventions, consulté le 31/10/2026.
  3. Python Software Foundation, Référence du langage — 7.3. L'instruction assert, consulté le 31/10/2026.
  4. Sorties des fonctions, de l'AssertionError et de la NameError : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
09
S2 · JEUDI · Pratiquer

Un code qu'une collègue peut relire

Relire son code comme celui d'une autre : des noms qui parlent, des docstrings, des constantes et une présentation régulière.

⏱ 25 min de pratique · 5 min de quiz
[1]Scénario
Nadia · Jeudi, 10 h

Nadia part en congés dans deux semaines ; Karim lancera le script de fin de mois à sa place. Il ouvre une version ancienne et tombe sur l2, x3, tmp et un mystérieux !!. « Ça marche, je suppose. Mais si ça plante, je ne saurai pas quoi toucher. » Nadia relit son propre code… et ne se souvient plus pourquoi elle avait mis 100.

[2]Méthode

« Relire comme si c'était le code d'une autre »

Rappels : fonctions et docstrings (voir épisode 8), listes et range(len(...)) (voir épisode 6).

  1. Des noms qui disent ce qu'ils contiennent : fournisseurs, montants, total, pas l2, x3, t.
  2. Des fonctions nommées par une action, avec une docstring.
  3. Des commentaires qui expliquent pourquoi, pas ce que fait la ligne (# euros : au-dessus, la facture doit être validée par la direction). Un commentaire commence par # ; Python l'ignore.
  4. Des constantes en majuscules en haut du script pour les réglages : SEUIL_ALERTE = 100. On les modifie à un seul endroit.
  5. Une présentation régulière (règles de la PEP 8, le guide de style officiel) : 4 espaces de retrait, des lignes courtes, deux lignes vides entre les fonctions, des noms en minuscules avec _, des espaces autour de = et après les virgules.
  6. Même résultat avant et après : on relance et on compare la sortie.

Avant :

Code
l2 = ["  PAPETERIE martin ", "imprimerie LEROY", " café du coin"]
x3 = [1250.5, 89.9, 45.0]
t = 0
for i in range(len(l2)):
    tmp = l2[i].strip().title()
    if x3[i] > 100:
        print(tmp, "!!")
    else:
        print(tmp)
    t = t + x3[i]
print(t)

Après :

Code
"""Affiche les fournisseurs du mois, signale les factures au-dessus du seuil et calcule le total."""

SEUIL_ALERTE = 100  # euros : au-dessus, la facture doit être validée par la direction


def nettoyer(texte):
    """Renvoie le nom de fournisseur sans espaces autour, avec une majuscule à chaque mot."""
    return texte.strip().title()


fournisseurs = ["  PAPETERIE martin ", "imprimerie LEROY", " café du coin"]
montants = [1250.5, 89.9, 45.0]
total = 0
for i in range(len(fournisseurs)):
    nom = nettoyer(fournisseurs[i])
    if montants[i] > SEUIL_ALERTE:
        print(nom, "!!")
    else:
        print(nom)
    total = total + montants[i]
print(total)

Les deux versions affichent exactement la même chose (sortie réelle) :

Ce que vous voyez
Papeterie Martin !! Imprimerie Leroy Café Du Coin 1385.4

La docstring en tête de fichier dit à quoi sert le script ; le commentaire explique enfin le 100 ; range(len(fournisseurs)) sert ici à lire le montant au même rang que le fournisseur.

Auto-diagnostic · mon code est-il lisible ?
  • Mes noms disent ce qu'ils contiennent.
  • Mes fonctions sont nommées par une action et ont une docstring.
  • Mes commentaires expliquent pourquoi.
  • Mes réglages sont des constantes en majuscules en haut du script.
  • 4 espaces, lignes courtes, deux lignes vides entre les fonctions.
  • La sortie est la même qu'avant ma relecture.
[3]Pourquoi ça marche

Un script de fin de mois s'écrit une fois et se relit chaque mois, souvent dans l'urgence, parfois par quelqu'un d'autre. Un nom clair évite d'avoir à remonter le code pour comprendre une variable ; une constante évite de chercher un nombre perdu au milieu des lignes ; une présentation régulière laisse voir la structure d'un coup d'œil. Comparer la sortie avant et après garantit qu'en rendant le code lisible, on ne l'a pas cassé.

À retenirdes noms qui parlent, une docstring, le pourquoi en commentaire, les réglages en haut, et la même sortie qu'avant.
[4]Entraînement

Bon réflexe ou piège ?

Je commente chaque ligne : # ajoute le montant au total.
le code le dit déjà ; le commentaire doit dire pourquoi.
J'abrège tous les noms pour aller plus vite : fr, mt, t.
vous gagnez trois secondes à l'écriture et en perdez dix à chaque relecture.
Je regroupe les réglages (seuil, dossier, taux) en constantes en haut du script.
on les change à un seul endroit, sans toucher au reste.
Je relance le script avant et après ma relecture, et je compare.
c'est la preuve que la relecture n'a rien cassé.
Je laisse un vieux morceau de code en commentaire, au cas où.
il encombre et trompe le lecteur ; gardez plutôt une copie du fichier.
[5]Quiz

1. Quel commentaire est le plus utile ?

il explique pourquoi, ce que le code ne peut pas dire.

2. Où placer SEUIL_ALERTE = 100 ?

c'est un réglage : on le trouve et on le change à un seul endroit.

3. Comment vérifier qu'une relecture n'a rien cassé ?

seule l'exécution prouve que le résultat n'a pas changé (sorties vérifiées : identiques).
[6]Kit

🎒 Votre kit — Épisode 9

25 min

Exercice : la relecture de Karim

  1. Recopiez la version « avant », lancez-la et notez sa sortie.
  2. Sans regarder la version « après », renommez les variables, ajoutez une docstring au script et une constante pour le seuil, puis expliquez le seuil dans un commentaire.
  3. Relancez et comparez la sortie avec celle de l'étape 1 : elle doit être identique.
  4. Relisez votre script de la semaine 1 avec le widget « Mon code est-il lisible ? ».

Code à copier : la version « après ».

Checklist

  • Mes noms disent ce qu'ils contiennent.
  • Mes réglages sont en constantes, en haut.
  • La sortie est identique avant et après.
Vigilanceavant de retoucher un script qui marche, gardez-en une copie.
[7]Journal

✍️ Journal de bord

Si une collègue devait lancer votre script demain sans vous, que lui faudrait-il savoir ? Est-ce écrit dans le code ?

[8]Sources
  1. G. van Rossum, B. Warsaw, A. Coghlan, PEP 8 – Style Guide for Python Code, consulté le 31/10/2026.
  2. Python Software Foundation, Tutoriel Python — 4.10. Aparté : le style de codage, consulté le 31/10/2026.
  3. Sorties des versions « avant » et « après » : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
10
S2 · VENDREDI · Pratiquer · Bilan d'arc

Ranger et réutiliser : le bilan

Votre outil de synthèse, le récapitulatif de la semaine et votre badge.

⏱ 25 min · arbre de décision · carrousel · quiz final
[1]Scénario
Nadia · Vendredi, 17 h

Nadia montre à Karim sa nouvelle fonction nouveau_nom : elle reconnaît les deux modèles de noms (l'export du logiciel et les factures reçues par courriel), et met de côté ce qu'elle ne sait pas lire, comme Scan_0042.pdf. Karim lit le code sans aide. « Et si un troisième modèle arrive ? » Nadia : « Un elif de plus, une vérification de plus. Au même endroit. »

[2]Outil

« Faut-il en faire une fonction ? »

Aide à la décision interactive
[3]Carrousel

🎨 Carrousel récapitulatif — Semaine 2

[4]Quiz final

1. Après liste = ["b", "a", "c"] et liste.append("a"), qu'affiche print(sorted(liste), len(liste)) ?

append ajoute à la fin, sorted trie une copie et garde les doublons (sortie vérifiée).

2. Que vaut "2026-10-03_Papeterie-Martin_F00412.pdf".split("_")[1] ?

le deuxième morceau, à l'indice 1 (sortie vérifiée).

3. Avec def tva(montant_ht, taux=20): return round(montant_ht * taux / 100, 2), que valent tva(100) et tva(100, 5.5) ?

sans deuxième argument, le taux par défaut (20) s'applique (sortie vérifiée).

4. Votre fonction nouveau_nom ne reconnaît pas un nom. Que doit-elle faire ?

on ne devine pas ; on signale, et une personne traite le cas à la main.

5. Vous renommez t en total dans tout un script. Comment savoir que rien n'est cassé ?

seule l'exécution prouve que le résultat n'a pas changé.
[5]Kit

🎒 Votre kit de fin d'arc

25 min

Une fonction pour tous les noms

Synthèse : une fonction nouveau_nom(ancien) reconnaît deux modèles de noms, renvoie "" pour les autres, et un programme principal range les résultats dans deux listes : les noms calculés et les fichiers à traiter à la main. Aucun fichier n'est touché.

Code à copier
"""Calcule le nouveau nom des factures du mois selon la règle de la PME (aucun fichier n'est touché)."""


def date_iso(date_fr):
    """Transforme une date JJ-MM-AAAA en AAAA-MM-JJ."""
    return date_fr[6:] + "-" + date_fr[3:5] + "-" + date_fr[:2]


def nettoyer(texte):
    """Renvoie le nom de fournisseur propre : sans espaces autour, majuscules aux mots, tirets."""
    return texte.strip().title().replace(" ", "-")


def nouveau_nom(ancien):
    """Renvoie le nouveau nom d'une facture, ou "" si le nom ne suit aucun modèle connu."""
    if not ancien.lower().endswith(".pdf"):
        return ""
    base = ancien[:-4]
    morceaux = base.split("_")
    if base.startswith("FACT-") and len(morceaux) == 3:
        numero = "F" + morceaux[0][5:]
        fournisseur = morceaux[1]
        date = morceaux[2]
    elif base.startswith("F") and base[6:7] == " " and len(base) > 17:
        numero = base[:6]
        fournisseur = base[7:-11]
        date = base[-10:]
    else:
        return ""
    return date_iso(date) + "_" + nettoyer(fournisseur) + "_" + numero + ".pdf"


assert nouveau_nom("F00412 papeterie MARTIN 03-10-2026.pdf") == "2026-10-03_Papeterie-Martin_F00412.pdf"
assert nouveau_nom("FACT-00418_Papeterie Martin_19-10-2026.pdf") == "2026-10-19_Papeterie-Martin_F00418.pdf"
assert nouveau_nom("Scan_0042.pdf") == ""
assert nouveau_nom("releve_banque_octobre.csv") == ""

anciens = ["F00412 papeterie MARTIN 03-10-2026.pdf",
           "F00413   Imprimerie leroy  05-10-2026.pdf",
           "FACT-00418_Papeterie Martin_19-10-2026.pdf",
           "Scan_0042.pdf",
           "F00414 CAFÉ DU COIN 07-10-2026.pdf",
           "FACT-00419_NETTOYAGE PRO_21-10-2026.PDF",
           "releve_banque_octobre.csv",
           "F00415 nettoyage pro 12-10-2026.pdf",
           "FACT-00420_imprimerie leroy_28-10-2026.pdf",
           "F00417 transports rapides 25-10-2026.PDF"]
nouveaux = []
a_traiter = []
for ancien in anciens:
    nom = nouveau_nom(ancien)
    if nom == "":
        a_traiter.append(ancien)
    else:
        nouveaux.append(nom)
for nom in sorted(nouveaux):
    print(nom)
print(len(nouveaux), "noms calculés ;", len(a_traiter), "à traiter à la main :", a_traiter)

Sortie réelle :

Ce que vous voyez
2026-10-03_Papeterie-Martin_F00412.pdf 2026-10-05_Imprimerie-Leroy_F00413.pdf 2026-10-07_Café-Du-Coin_F00414.pdf 2026-10-12_Nettoyage-Pro_F00415.pdf 2026-10-19_Papeterie-Martin_F00418.pdf 2026-10-21_Nettoyage-Pro_F00419.pdf 2026-10-25_Transports-Rapides_F00417.pdf 2026-10-28_Imprimerie-Leroy_F00420.pdf 8 noms calculés ; 2 à traiter à la main : ['Scan_0042.pdf', 'releve_banque_octobre.csv']

Comment la fonction reconnaît les modèles : les factures reçues par courriel commencent par FACT- et ont trois morceaux séparés par _ ; les exports du logiciel commencent par F, ont un espace en 7e position et une date à la fin. base[6:7] (une tranche d'un caractère) ne provoque pas d'erreur même si le nom est très court, contrairement à base[6]. Le comptage final, 8 + 2 = 10, prouve qu'aucun nom ne s'est perdu.

Consigne : recopiez le script ; ajoutez deux noms inventés (un de chaque modèle) et un nom qui ne suit aucun modèle, avec une vérification assert pour chacun ; vérifiez que le comptage final reste juste ; exportez votre carnet de bord.

Checklist

  • J'ai exporté mon carnet (bouton dans le menu).
  • Mes 4 vérifications d'origine et mes 3 nouvelles passent.
  • Le comptage final correspond au nombre de noms en entrée.
[6]Journal

✍️ Journal de bord — bilan

Qu'est-ce qui a le plus changé cette semaine dans votre façon d'écrire un script ? Que diriez-vous à Karim pour qu'il puisse le lancer seul ?

✦ CODE BIEN RANGÉ · SEMAINE 2 ✦

Bravo ! Vous avez terminé la semaine 2. Vous savez ranger vos séries dans des listes, écrire et vérifier vos propres fonctions, et rendre votre code lisible par une collègue.

ARC 3 · SEMAINE 3

Vos fichiers

Cette semaine, vos scripts touchent enfin des fichiers : des dates bien calculées, des dossiers parcourus, des relevés lus et fusionnés, des factures renommées et rangées. Toujours dans le dossier d'entraînement, toujours en simulation d'abord. Vendredi, le rituel de fin de mois tourne tout seul, sur la copie.

11
S3 · LUNDI · Comprendre

Les modules : dates et chemins

Importer les outils fournis avec Python : datetime pour calculer sur les dates, pathlib pour parcourir un dossier.

⏱ 25 min de lecture · 5 min de quiz
[1]Scénario
Nadia · Lundi, 9 h

Un nouveau fournisseur facture à « 45 jours fin de mois » ; un autre à 60 jours. Le calculateur d'échéances du Boost s'arrête à 28 jours, et Nadia sait pourquoi : il ne passe qu'un mois (Boost, épisode 4). « Il me faudrait un calendrier dans Python. » Karim : « Il y en a un. Il est livré avec. Et il sait aussi lire le contenu d'un dossier. »

[2]Concept

Les modules de la bibliothèque standard

Un module, c'est une boîte à outils livrée avec Python. La bibliothèque standard contient des centaines de modules, déjà installés. On les charge avec import :

  • import datetime charge le module ; on écrit ensuite datetime.date(…) ;
  • from datetime import date, timedelta charge seulement deux outils, qu'on utilise directement : date(…), timedelta(…).

Les import se placent en haut du script.

Les dates : date et timedelta.

Code
from datetime import date, timedelta

facture = date(2026, 10, 3)
echeance = facture + timedelta(days=60)
print(facture, echeance)
print(echeance.day, echeance.month, echeance.year)
paiement = date(2026, 12, 14)
retard = paiement - echeance
print(retard.days, "jours de retard")

Affiche 2026-10-03 2026-12-02, puis 2 12 2026, puis 12 jours de retard. date(année, mois, jour) crée une date ; timedelta(days=60) est une durée ; date + durée = date ; date − date = durée, dont .days donne le nombre de jours. Le module connaît la longueur des mois et les années bissextiles.

La promesse du Boost tenue. Les quatre cas de référence du Boost, recalculés en une ligne chacun :

Code
from datetime import date, timedelta

print(date(2026, 3, 5) + timedelta(days=20))
print(date(2026, 4, 15) + timedelta(days=28))
print(date(2028, 2, 15) + timedelta(days=20))
print(date(2026, 12, 20) + timedelta(days=28))
print(date(2026, 1, 30) + timedelta(days=30))

Affiche 2026-03-25, 2026-05-13, 2028-03-06, 2027-01-17 : les mêmes échéances que votre calculateur. Et le cas qui piégeait la règle du Boost, le 30 janvier + 30 jours, donne 2026-03-01, et non un « 29 février » qui n'existe pas. Pourquoi avoir appris la règle d'abord ? Parce que c'est elle qui vous permet de vérifier ce que fait le module.

La fin du mois et les « 45 jours fin de mois ». Le dernier jour d'un mois, c'est la veille du 1er du mois suivant :

Code
from datetime import date, timedelta


def fin_de_mois(jour):
    """Renvoie le dernier jour du mois de la date donnée."""
    if jour.month == 12:
        premier_suivant = date(jour.year + 1, 1, 1)
    else:
        premier_suivant = date(jour.year, jour.month + 1, 1)
    return premier_suivant - timedelta(days=1)


facture = date(2026, 3, 1)
print("45 jours fin de mois :", fin_de_mois(facture + timedelta(days=45)))
print("fin de mois 45 jours :", fin_de_mois(facture) + timedelta(days=45))
print(fin_de_mois(date(2028, 2, 10)), fin_de_mois(date(2026, 12, 31)))

Affiche 45 jours fin de mois : 2026-04-30, puis fin de mois 45 jours : 2026-05-15, puis 2028-02-29 2026-12-31. Il existe deux façons de compter : ajouter 45 jours puis aller à la fin du mois, ou aller à la fin du mois puis ajouter 45 jours. Pour une facture du 1er mars, elles donnent le 30 avril et le 15 mai : ce sont les deux résultats de l'exemple de la DGCCRF. C'est le contrat qui dit laquelle s'applique.

Lire et écrire une date sous forme de texte.

Code
from datetime import datetime

facture = datetime.strptime("03-10-2026", "%d-%m-%Y").date()
print(facture, type(facture))
print(facture.strftime("%Y-%m-%d"), facture.strftime("%d/%m/%Y"), facture.isoformat())

Affiche 2026-10-03 <class 'datetime.date'>, puis 2026-10-03 03/10/2026 2026-10-03. strptime (string parse time, lire une date dans un texte) a besoin du format : %d le jour, %m le mois, %Y l'année sur 4 chiffres ; .date() garde la date sans l'heure. strftime (string format time) fait l'inverse. isoformat() donne directement le format AAAA-MM-JJ de la règle de la PME. (Depuis Python 3.14, on peut aussi écrire date.strptime(…) ; la forme ci-dessus marche dès Python 3.12.)

Les chemins : pathlib. Un chemin indique où se trouve un fichier ou un dossier. Path le représente, et l'opérateur / assemble les morceaux, quel que soit le système (Windows écrit \, macOS et Linux /) :

Code
from pathlib import Path

FACTURES = Path("entrainement") / "factures"
print(FACTURES, FACTURES.exists())
nombre = 0
for fichier in sorted(FACTURES.iterdir()):
    nombre = nombre + 1
    if nombre <= 3:
        print(fichier.name, "|", fichier.stem, "|", fichier.suffix)
print(nombre, "fichiers dans", FACTURES)

Sortie réelle (sous macOS ou Linux ; Windows affiche entrainement\factures) :

Ce que vous voyez
entrainement/factures True F00412 papeterie MARTIN 01-10-2026.pdf | F00412 papeterie MARTIN 01-10-2026 | .pdf F00414 Café du coin 15-10-2026.pdf | F00414 Café du coin 15-10-2026 | .pdf F00416 TRANSPORTS RAPIDES 29-10-2026.pdf | F00416 TRANSPORTS RAPIDES 29-10-2026 | .pdf 40 fichiers dans entrainement/factures

exists() dit si le chemin existe ; iterdir() donne le contenu du dossier, dans un ordre qui n'est pas garanti (d'où sorted) ; .name est le nom complet, .stem le nom sans extension, .suffix l'extension. Lire un dossier ne modifie rien.

Un piège selon le système : les majuscules. glob("*.pdf") trouve les fichiers dont le nom correspond au motif (* = n'importe quels caractères) :

Code
from pathlib import Path

FACTURES = Path("entrainement") / "factures"
minuscules = 0
for fichier in FACTURES.glob("*.pdf"):
    minuscules = minuscules + 1
pdf = 0
for fichier in FACTURES.iterdir():
    if fichier.suffix.lower() == ".pdf":
        pdf = pdf + 1
print(minuscules, "trouvés avec glob(\"*.pdf\") ;", pdf, "avec suffix.lower()")

Sous macOS et Linux, affiche 34 trouvés avec glob("*.pdf") ; 40 avec suffix.lower() : les 6 fichiers en .PDF échappent au motif, qui y respecte les majuscules ; sous Windows, les deux comptes donnent 40. Comparer fichier.suffix.lower() marche partout.

Ne donnez jamais à votre script le nom d'un module. Un fichier datetime.py dans votre dossier de travail cache le vrai module (sortie réelle sous Python 3.15) :

Message d'erreur
ImportError: cannot import name 'date' from 'datetime' (consider renaming 'datetime.py' since it has the same name as the standard library module named 'datetime' and prevents importing that standard library module)

« Impossible d'importer date depuis datetime (pensez à renommer datetime.py, qui porte le même nom qu'un module de la bibliothèque standard). » Python 3.12 affiche seulement ImportError: cannot import name 'date' from 'datetime' (datetime.py) : le nom du fichier, entre parenthèses, est l'indice.

Calculateur de dates · date + timedelta, fin de mois
Échéance
Les lignes de Python

Même calcul que le module datetime ; contrôlé contre Python à la fabrication du livre.

[3]Recherche

Ce que disent les sources

  • Documentation officielle de datetime — Un timedelta est une durée qui exprime la différence entre deux dates ; date2 - date1 donne un timedelta. date.isoformat() renvoie une chaîne au format ISO 8601, YYYY-MM-DD. date.strptime() a été ajoutée dans la version 3.14 ; avant, on utilise datetime.strptime(…).date() [1].
  • DGCCRF — Le délai de paiement convenu entre professionnels ne peut dépasser 60 jours à compter de la date d'émission de la facture, ou 45 jours fin de mois. Pour une facture du 1er mars, la limite est le 30 avril (« 45 jours fin de mois ») ou le 15 mai (« fin de mois 45 jours ») selon la méthode retenue. Sans délai convenu, c'est 30 jours après la réception des marchandises ou l'exécution de la prestation [2].
  • Documentation de pathlib — Par défaut, glob() applique les règles de majuscules du système : sensible à la casse sous les systèmes POSIX (macOS, Linux), insensible sous Windows ; le paramètre case_sensitive existe depuis Python 3.12 [3].
  • Tests de ce livre — Quand un script porte le nom d'un module de la bibliothèque standard et le masque, Python 3.15 suggère dans le message d'erreur de le renommer ; Python 3.12 n'indique que le nom du fichier fautif [4].
[4]Mythes
« Pour faire plus, il faut tout installer. »
la bibliothèque standard est déjà là : dates, chemins, fichiers CSV, statistiques… L'installation de paquets ne viendra qu'en semaine 4, pour lire un fichier Excel.
« Un calendrier, je sais faire. »
la règle du Boost s'arrête à 28 jours ; le module connaît toutes les longueurs de mois et les années bissextiles. Votre règle reste utile : elle sert à vérifier.
« Lister un dossier, c'est risqué. »
iterdir, glob, exists ne font que lire. Rien n'est modifié tant que vous n'appelez pas une méthode qui écrit, renomme ou crée (jeudi).
[5]Quiz

1. Qu'affiche print(date(2026, 10, 31) + timedelta(days=1)) ?

le module connaît la fin des mois (sortie vérifiée).

2. Que vaut (date(2026, 11, 10) - date(2026, 10, 31)).days ?

du 31 octobre au 10 novembre, il y a 10 jours (sortie vérifiée).

3. Vous comptez les factures avec glob("*.pdf") sur un Mac et en trouvez moins que prévu. Pourquoi ?

comparez plutôt fichier.suffix.lower() à ".pdf" (sorties vérifiées : 34 contre 40).
[6]Kit

🎒 Votre kit — Épisode 11

25 min

Exercice : le calendrier et le dossier

  1. Téléchargez le dossier d'entraînement (bouton ci-dessous) et décompressez-le dans un dossier de travail.
  2. Recalculez avec timedelta les 4 cas de référence du Boost : mêmes résultats ?
  3. Calculez l'échéance à 60 jours et à « 45 jours fin de mois » (les deux méthodes) d'une facture du 15 octobre 2026.
  4. Enregistrez le programme pathlib dans votre dossier de travail (à côté du dossier entrainement), lancez-le : vous devez lire 40 fichiers.
Fichier fourni · le dossier d'entraînement (données inventées)

40 factures factices (PDF d'une page « Facture fictive »), 3 relevés CSV et la liste des fournisseurs (Excel). Décompressez l'archive dans un dossier de travail ; pour repartir de zéro, supprimez le dossier entrainement et décompressez de nouveau.

Corrigé (sorties réelles) : à la question 3, 2026-12-14 à 60 jours ; 2026-11-30 en « 45 jours fin de mois » ; 2026-12-15 en « fin de mois 45 jours ».

Code à copier : la fonction fin_de_mois et ses essais (ci-dessus).

Checklist

  • J'ai décompressé le dossier d'entraînement dans un dossier de travail.
  • Je calcule une échéance avec date et timedelta.
  • Mon script lit les 40 fichiers du dossier d'entraînement.
Vigilancele dossier d'entraînement est votre terrain d'essai ; vos vrais dossiers n'interviennent pas avant la fin du livre, et seulement par une copie.
[7]Journal

✍️ Journal de bord

Quels délais ou quelles dates calculez-vous encore à la main ou sur un calendrier papier ? Lequel automatiseriez-vous en premier ?

[8]Sources
  1. Python Software Foundation, datetime — Types de base pour la date et l'heure, consulté le 31/10/2026.
  2. DGCCRF, Délais de paiement : les règles à connaître (brochure, mars 2022, diffusée par la DREETS Nouvelle-Aquitaine), consulté le 31/10/2026.
  3. Python Software Foundation, pathlib — Path.glob, paramètre case_sensitive (ajouté en 3.12) (texte de la documentation, python-checkins, mai 2023 ; docs.python.org/fr/3/library/pathlib.html), consulté le 31/10/2026.
  4. Sorties des extraits (dates, dossier, glob, module masqué) : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
12
S3 · MARDI · Pratiquer

Lire la documentation d'un module

Chercher, lire la fiche, essayer l'exemple, adapter : trouver seule ou seul le bon outil et son mode d'emploi.

⏱ 25 min de pratique · 5 min de quiz
[1]Scénario
Nadia · Mardi, 12 h 30

Les dates des factures arrivent sous trois formes : 03-10-2026 (le logiciel), 2026.10.05 (un fournisseur allemand), 05/10/26 (un scan relu à la main). Nadia tape sa question dans un moteur de recherche et tombe sur une réponse de forum de 2011, avec un code qui commence par print "…", sans parenthèses. Il ne marche pas. Karim : « Va voir la documentation officielle. Elle est en français, et elle est à jour. »

[2]Méthode

« Chercher, lire la fiche, essayer l'exemple, adapter »

Rappel : les modules et strptime (voir épisode 11).

  1. Chercher au bon endroit : la documentation officielle, docs.python.org, existe en français (menu des langues en haut de page) ; choisissez la version de Python que vous utilisez. Une page par module ; la recherche se fait par mot (« strptime »).
  2. Lire la fiche : la signature (le nom et les paramètres, par exemple datetime.strptime(date_string, format)), une phrase qui dit ce que fait l'outil, puis les détails et les cas limites. Repérez les mentions « Ajouté dans la version… » : un outil récent peut manquer à une version plus ancienne.
  3. Essayer l'exemple tel quel dans la console, avant de l'adapter.
  4. Adapter à votre cas, puis vérifier sur un cas connu.
  5. help() dans la console affiche la documentation intégrée : help(datetime.strptime) (touche q pour sortir si elle s'affiche page par page).

Ce que la documentation de datetime donne pour nos trois formats (tableau des codes de format) : %d jour sur deux chiffres, %m mois, %Y année sur quatre chiffres, %y année sur deux chiffres ; les autres caractères du format (-, ., /) doivent se retrouver tels quels dans le texte.

Code
from datetime import datetime

print(datetime.strptime("03-10-2026", "%d-%m-%Y").date())
print(datetime.strptime("2026.10.05", "%Y.%m.%d").date())
print(datetime.strptime("05/10/26", "%d/%m/%y").date())

Affiche 2026-10-03, 2026-10-05 et 2026-10-05. Un texte qui ne correspond pas au format s'arrête sur une erreur claire (dernière ligne de la sortie réelle) :

Message d'erreur
ValueError: time data '2026.10.05' does not match format '%d-%m-%Y'

« Les données de date '2026.10.05' ne correspondent pas au format '%d-%m-%Y'. » Le message est précédé de plusieurs lignes qui citent des fichiers internes de Python (_strptime.py) : appliquez la méthode du Boost, la dernière ligne d'abord.

Reconnaître une réponse périmée. print "…" sans parenthèses, c'est Python 2, dont le support a pris fin le 1er janvier 2020 : passez votre chemin. Une bonne réponse indique sa date et la version de Python ; et même bonne, elle se vérifie sur un cas connu.

Remettez les lignes dans l'ordre · cliquez les lignes une par une
Votre programme
Lignes à placer
[3]Pourquoi ça marche

La documentation officielle est écrite par ceux qui maintiennent Python, pour la version que vous utilisez. Une réponse trouvée ailleurs peut viser une autre version, un autre problème, ou contenir une erreur. Essayer l'exemple tel quel avant de l'adapter permet de savoir si un problème vient de l'outil ou de votre adaptation.

À retenirdocumentation officielle, bonne version, exemple essayé tel quel, puis adapté et vérifié.
[4]Entraînement

Bon réflexe ou piège ?

Je copie une réponse de forum sans regarder sa date.
elle peut viser Python 2, ou une version où l'outil n'existait pas encore.
Je choisis la version de la documentation qui correspond à mon Python.
les mentions « Ajouté dans la version… » vous évitent un outil absent.
J'essaie l'exemple de la documentation tel quel dans la console.
vous savez qu'il marche avant de l'adapter.
Je lis la fiche jusqu'aux cas limites et aux erreurs possibles.
c'est là qu'on apprend, par exemple, que %y interprète les années à deux chiffres.
Le message d'erreur fait dix lignes : je le recopie entier dans un moteur de recherche.
lisez d'abord la dernière ligne ; elle suffit souvent (ici : le texte ne correspond pas au format).
[5]Quiz

1. Quel format lit la date "2026.10.05" ?

l'année d'abord, puis le mois, puis le jour, séparés par des points (sortie vérifiée).

2. Une réponse de forum utilise print "Total". Qu'en concluez-vous ?

depuis Python 3, print est une fonction : print("Total").

3. Que fait help(datetime.strptime) dans la console ?

la documentation est livrée avec Python.
[6]Kit

🎒 Votre kit — Épisode 12

25 min

Exercice : trouver soi-même dans la documentation

  1. Ouvrez la page du module datetime en français, dans la version de votre Python, et trouvez le tableau des codes de format.
  2. Écrivez les trois strptime ci-dessus sans regarder le livre, puis vérifiez.
  3. Trouvez dans la documentation comment afficher une date sous la forme 03/10/2026 (indice : strftime), et le jour de la semaine (weekday() : quel nombre pour le lundi ?).
  4. Dans la console, tapez help(timedelta) et repérez le paramètre weeks.

Corrigé (d'après la documentation officielle) : facture.strftime("%d/%m/%Y") ; weekday() renvoie 0 pour le lundi et 6 pour le dimanche ; timedelta(weeks=2) vaut 14 jours.

Code à copier : les trois lectures de dates (ci-dessus).

Checklist

  • J'ai trouvé le tableau des codes de format.
  • Je sais reconnaître une réponse écrite pour Python 2.
  • J'ai utilisé help() dans la console.
Vigilanceune réponse de forum ou d'IA se vérifie toujours sur un cas connu, comme un code du livre.
[7]Journal

✍️ Journal de bord

Où cherchez-vous d'habitude quand vous êtes bloquée ou bloqué ? Qu'est-ce qui vous fait faire confiance à une réponse ?

[8]Sources
  1. Python Software Foundation, datetime — Codes de format de strftime() et strptime() ; date.weekday() ; timedelta, consulté le 31/10/2026.
  2. Python Software Foundation, Sunsetting Python 2 (fin du support le 1er janvier 2020), consulté le 31/10/2026.
  3. Sorties des trois lectures et de l'erreur de format : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
13
S3 · MERCREDI · Comprendre

Lire et écrire des fichiers texte et CSV

Ouvrir un fichier avec with open, choisir le bon encodage, lire un CSV de tableur, écrire un résultat dans un nouveau fichier.

⏱ 25 min de lecture · 5 min de quiz
[1]Scénario
Nadia · Mercredi, 14 h

Les relevés du logiciel de facturation arrivent en trois fichiers CSV par mois, un par décade. Nadia les ouvre dans le tableur, recopie les totaux dans un récapitulatif, et vérifie deux fois. « Python pourrait lire ces fichiers lui-même ? » Karim : « Lire, oui, sans rien abîmer. Écrire, oui aussi, mais dans un nouveau fichier. »

[2]Concept

Lire et écrire des fichiers

Ouvrir un fichier : with open.

Code
from pathlib import Path

releve = Path("entrainement") / "releves" / "releve-2026-10-01-au-10.csv"
with open(releve, encoding="utf-8-sig") as fichier:
    for ligne in fichier:
        print(ligne.strip())
        break

Affiche date;fournisseur;numero;montant : la première ligne du relevé. open ouvre le fichier en lecture ; with (« avec ») garantit qu'il sera refermé à la fin du bloc décalé, même en cas de problème. Un fichier texte se parcourt ligne par ligne avec for ; .strip() enlève le retour à la ligne. (break arrête la boucle après la première ligne : c'est l'instruction citée lundi dernier, utilisée ici une seule fois.)

L'encodage : préciser toujours. Un fichier texte est une suite d'octets ; l'encodage dit comment les transformer en lettres. Les accents sont la première victime d'un mauvais encodage. Deux règles :

  • précisez toujours encoding= : sans lui, Python 3.12 utilise l'encodage du système (souvent Windows-1252 sous Windows), et Python 3.15 utilise UTF-8 partout ; votre script se comporterait différemment selon l'ordinateur ;
  • les CSV « UTF-8 » enregistrés par un tableur commencent souvent par une marque invisible, le BOM (byte order mark). Lus en utf-8, elle se colle au premier mot ; lus en utf-8-sig, elle disparaît :
Code
import csv
from pathlib import Path

releve = Path("entrainement") / "releves" / "releve-2026-10-01-au-10.csv"
with open(releve, encoding="utf-8") as fichier:
    print(next(csv.reader(fichier, delimiter=";")))
with open(releve, encoding="utf-8-sig") as fichier:
    print(next(csv.reader(fichier, delimiter=";")))

Affiche ['date', 'fournisseur', 'numero', 'montant'], puis ['date', 'fournisseur', 'numero', 'montant']. Le , c'est le BOM. (next(…) lit seulement la première ligne.) Si des lettres accentuées s'affichent comme é, essayez l'autre encodage courant des fichiers Windows : encoding="cp1252".

Lire un CSV : le module csv. Un CSV (comma-separated values) est un tableau en texte, une ligne par rangée, avec un séparateur entre les colonnes. Avec un tableur réglé en français, c'est souvent le point-virgule, car la virgule sert aux décimales.

Code
import csv
from pathlib import Path

releve = Path("entrainement") / "releves" / "releve-2026-10-01-au-10.csv"
with open(releve, encoding="utf-8-sig", newline="") as fichier:
    lecteur = csv.reader(fichier, delimiter=";")
    for ligne in lecteur:
        print(ligne)

Sortie réelle (début) :

Ce que vous voyez
['date', 'fournisseur', 'numero', 'montant'] ['01/10/2026', 'Papeterie Martin', 'F00412', '1250,50'] ['01/10/2026', 'Imprimerie Leroy', 'F00443', '520,00'] ['02/10/2026', 'Nettoyage Pro', 'F00421', '980,00']

Chaque ligne devient une liste de textes. newline="" est recommandé par la documentation du module pour tous les fichiers CSV. Avec le mauvais séparateur (delimiter=","), toute la ligne reste en un seul morceau : ['date;fournisseur;numero;montant'].

DictReader : chaque ligne devient un dictionnaire. csv.DictReader utilise la première ligne comme noms de colonnes ; on lit ensuite ligne["montant"] au lieu de ligne[3]. (Les dictionnaires seront étudiés en semaine 4 ; ici, retenez seulement cette écriture.) Les montants sont des textes avec une virgule : .replace(",", ".") puis float (épisode 3).

Code
import csv
from pathlib import Path

RELEVES = Path("entrainement") / "releves"
for releve in sorted(RELEVES.glob("*.csv")):
    total = 0
    nombre = 0
    with open(releve, encoding="utf-8-sig", newline="") as fichier:
        for ligne in csv.DictReader(fichier, delimiter=";"):
            total = total + float(ligne["montant"].replace(",", "."))
            nombre = nombre + 1
    print(f"{releve.name:<30}{nombre:>4} lignes{total:>12.2f} €")

Sortie réelle :

Ce que vous voyez
releve-2026-10-01-au-10.csv 12 lignes 5644.65 € releve-2026-10-11-au-20.csv 11 lignes 3093.34 € releve-2026-10-21-au-31.csv 16 lignes 5737.99 €

Écrire un fichier : dans un nouveau fichier, jamais par-dessus. open(chemin, "w") ouvre en écriture : il crée le fichier, ou efface son contenu s'il existe déjà, sans prévenir. D'où la règle du livre : on vérifie d'abord avec exists().

Code
import csv
from pathlib import Path

RELEVES = Path("entrainement") / "releves"
SORTIES = Path("entrainement") / "sorties"
SORTIES.mkdir(exist_ok=True)
recap = SORTIES / "recapitulatif.txt"
if recap.exists():
    print("Le fichier", recap, "existe déjà : je ne l'écrase pas.")
else:
    with open(recap, "w", encoding="utf-8") as sortie:
        for releve in sorted(RELEVES.glob("*.csv")):
            total = 0
            with open(releve, encoding="utf-8-sig", newline="") as fichier:
                for ligne in csv.DictReader(fichier, delimiter=";"):
                    total = total + float(ligne["montant"].replace(",", "."))
            sortie.write(f"{releve.name} : {total:.2f} euros\n")
    print("Écrit :", recap)
    print(recap.read_text(encoding="utf-8"))

Premier lancement (sortie réelle) :

Ce que vous voyez
Écrit : entrainement/sorties/recapitulatif.txt releve-2026-10-01-au-10.csv : 5644.65 euros releve-2026-10-11-au-20.csv : 3093.34 euros releve-2026-10-21-au-31.csv : 5737.99 euros

Deuxième lancement : Le fichier entrainement/sorties/recapitulatif.txt existe déjà : je ne l'écrase pas. SORTIES.mkdir(exist_ok=True) crée le dossier sorties s'il n'existe pas, et ne fait rien s'il existe. sortie.write(…) écrit du texte ; "\n" est le retour à la ligne. read_text relit tout le fichier d'un coup, pour vérifier.

Lecteur de CSV simulé · les 4 premières lignes du relevé du 1er au 10 octobre
Séparateur :
Encodage :
Fonction :
Code
Ce que Python obtient, ligne par ligne

Sorties produites par le module csv de Python à la fabrication du livre, sur le vrai relevé du dossier d'entraînement.

[3]Recherche

Ce que disent les sources

  • Documentation officielle du module csv — Un fichier CSV doit être ouvert avec newline='' ; sans cela, les retours à la ligne à l'intérieur des champs entre guillemets sont mal interprétés. Le paramètre delimiter vaut ',' par défaut. DictReader utilise la première ligne comme noms de champs [1].
  • Python 3.15 et l'encodage par défaut (PEP 686) — Depuis Python 3.15, open() en mode texte sans encoding= utilise UTF-8 au lieu de l'encodage local du système ; Windows, dont l'encodage local est souvent cp1252, est le plus concerné. Le conseil : toujours préciser encoding= [2].
  • Microsoft — Dans Excel, le séparateur de liste des fichiers CSV dépend des paramètres régionaux de Windows ; avec une virgule comme séparateur décimal, Excel utilise le point-virgule [3].
[4]Mythes
« Un CSV, c'est toujours des virgules. »
le séparateur dépend du logiciel et des réglages régionaux ; en France, c'est souvent le point-virgule. Regardez la première ligne avant de choisir delimiter.
« open sans with, c'est pareil. »
sans with, il faut penser à fermer le fichier ; with le ferme toujours, même si le programme s'arrête en cours de route.
« Un CSV, c'est un fichier Excel. »
c'est du texte pur, lisible par n'importe quel logiciel ; un fichier Excel (.xlsx) est un format différent, que vous lirez en semaine 4.
[5]Quiz

1. Le premier nom de colonne s'affiche 'date'. Que faire ?

c'est la marque BOM ; utf-8-sig la retire (sortie vérifiée).

2. Que fait open(chemin, "w") si le fichier existe déjà ?

d'où la vérification exists() avant d'écrire.

3. Une ligne de CSV reste en un seul morceau : ['date;fournisseur;numero;montant']. Pourquoi ?

le fichier utilise ; ; il faut delimiter=";" (sortie vérifiée).
[6]Kit

🎒 Votre kit — Épisode 13

25 min

Exercice : lire les relevés, écrire un récapitulatif

  1. Dans votre dossier de travail, enregistrez et lancez le programme des totaux : vous devez obtenir les trois totaux ci-dessus.
  2. Additionnez-les à la main : total du mois attendu, 14 475,98 €.
  3. Lancez le programme d'écriture deux fois : le second lancement doit refuser d'écraser.
  4. Ouvrez recapitulatif.txt avec un éditeur de texte pour vérifier son contenu.

Code à copier : le programme d'écriture du récapitulatif (ci-dessus).

Checklist

  • Je précise toujours encoding= (et newline="" pour un CSV).
  • Je lis un CSV avec le bon séparateur.
  • Mon script refuse d'écraser un fichier existant.
Vigilanceon ne modifie jamais un relevé d'origine ; les résultats vont dans sorties/.
[7]Journal

✍️ Journal de bord

Quels fichiers recevez-vous ou exportez-vous au format CSV ? Quel séparateur et quel encodage utilisent-ils (ouvrez-en un avec un éditeur de texte pour le voir) ?

[8]Sources
  1. Python Software Foundation, csv — Lecture et écriture de fichiers CSV, consulté le 31/10/2026.
  2. pydevtools, Python 3.15 makes UTF-8 the default encoding (PEP 686), 19/07/2026, mis à jour le 20/07/2026, consulté le 31/10/2026.
  3. Microsoft, Importer ou exporter des fichiers texte (.txt ou .csv), consulté le 31/10/2026.
  4. Sorties de la lecture, du BOM, des totaux et de l'écriture : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
14
S3 · JEUDI · Pratiquer

Automatiser : renommer, ranger, fusionner

Copie, simulation, action, vérification : laisser un script agir sur des fichiers sans rien risquer.

⏱ 30 min de pratique · 5 min de quiz
[1]Scénario
Nadia · Jeudi, 10 h

Le moment que Nadia redoutait depuis le premier jour : laisser un script toucher des fichiers. Elle a lu qu'un script mal écrit peut écraser un document sans prévenir. Karim lui montre le dossier d'entraînement : « C'est une copie. Et ton script va d'abord te dire ce qu'il compte faire, sans rien toucher. Tu relis. Ensuite seulement, tu le laisses faire. »

[2]Méthode

« Copie, simulation, action, vérification »

Rappels : nouveau_nom (voir épisode 10), pathlib (voir épisode 11), fichiers CSV (voir épisode 13).

  1. Copie : on travaille dans le dossier d'entraînement, jamais dans un vrai dossier.
  2. Simulation : une constante SIMULATION = True en haut du script ; le script affiche « je rangerais A -> B » et ne touche à rien.
  3. Relecture de la liste affichée : les noms sont-ils justes ? les conflits signalés ?
  4. Action : SIMULATION = False, on relance.
  5. Vérification : le nombre de fichiers avant et après est le même ; rien ne s'est perdu.

Et trois règles qui ne se discutent pas : jamais d'écrasement (si la destination existe, le fichier est mis de côté) ; jamais de suppression (aucun script du livre n'efface) ; ce qui n'est pas reconnu reste à sa place et part dans la liste « à traiter à la main ».

Pourquoi exists() avant chaque renommage. rename déplace et renomme un fichier. Si la destination existe déjà, son comportement dépend du système : sous macOS et Linux, l'ancien fichier est remplacé sans prévenir ; sous Windows, Python s'arrête sur une FileExistsError. Démonstration dans un dossier d'essai, sous Linux (sortie réelle) : après a.txt.rename(b.txt), il ne reste qu'un fichier, b.txt : contenu de A. Le contenu de B a disparu. D'où le test systématique.

Le script de rangement :

Code
"""Renomme et range les factures du dossier d'entraînement (copie, simulation, action, vérification)."""
from pathlib import Path

SIMULATION = True  # True : le script affiche ce qu'il ferait, sans rien toucher
DOSSIER = Path("entrainement")
FACTURES = DOSSIER / "factures"
RANGEES = DOSSIER / "rangees"


def date_iso(date_fr):
    """Transforme une date JJ-MM-AAAA en AAAA-MM-JJ."""
    return date_fr[6:] + "-" + date_fr[3:5] + "-" + date_fr[:2]


def nettoyer(texte):
    """Renvoie le nom de fournisseur propre : sans espaces autour, majuscules aux mots, tirets."""
    return texte.strip().title().replace(" ", "-")


def nouveau_nom(ancien):
    """Renvoie le nouveau nom d'une facture, ou "" si le nom ne suit aucun modèle connu."""
    if not ancien.lower().endswith(".pdf"):
        return ""
    base = ancien[:-4]
    morceaux = base.split("_")
    if base.startswith("FACT-") and len(morceaux) == 3:
        numero = "F" + morceaux[0][5:]
        fournisseur = morceaux[1]
        date = morceaux[2]
    elif base.startswith("F") and base[6:7] == " " and len(base) > 17:
        numero = base[:6]
        fournisseur = base[7:-11]
        date = base[-10:]
    else:
        return ""
    return date_iso(date) + "_" + nettoyer(fournisseur) + "_" + numero + ".pdf"


def compter_fichiers(dossier):
    """Renvoie le nombre de fichiers dans le dossier et tous ses sous-dossiers."""
    nombre = 0
    for chemin in dossier.rglob("*"):
        if chemin.is_file():
            nombre = nombre + 1
    return nombre


if not FACTURES.exists():
    print("Dossier introuvable :", FACTURES, "- rien n'est fait.")
else:
    avant = compter_fichiers(DOSSIER)
    print("Mode simulation :", SIMULATION, "-", avant, "fichiers avant")
    a_traiter = []
    prevus = []
    for fichier in sorted(FACTURES.iterdir()):
        nom = nouveau_nom(fichier.name)
        if nom == "":
            a_traiter.append(fichier.name)
        else:
            destination = RANGEES / nom.split("_")[1] / nom
            if destination.exists() or destination in prevus:
                a_traiter.append(fichier.name + " (conflit : " + nom + " existe déjà)")
            elif SIMULATION:
                print("je rangerais", fichier.name, "->", destination)
                prevus.append(destination)
            else:
                destination.parent.mkdir(parents=True, exist_ok=True)
                fichier.rename(destination)
                prevus.append(destination)
    if SIMULATION:
        print(len(prevus), "factures à ranger ;", len(a_traiter), "à traiter à la main :")
    else:
        print(len(prevus), "factures rangées ;", len(a_traiter), "à traiter à la main :")
    for nom in a_traiter:
        print("  ", nom)
    apres = compter_fichiers(DOSSIER)
    if apres != avant:
        print(apres, "fichiers après : ATTENTION, écart !")
    elif SIMULATION:
        print(apres, "fichiers après : rien n'a bougé")
    else:
        print(apres, "fichiers après : aucun fichier perdu")

Les nouveautés, en une ligne chacune : RANGEES / nom.split("_")[1] / nom construit le chemin rangees/Fournisseur/nouveau-nom.pdf ; destination.parent est le dossier qui contiendra le fichier, et mkdir(parents=True, exist_ok=True) le crée s'il manque (avec les dossiers intermédiaires) ; fichier.rename(destination) déplace et renomme ; dossier.rglob("*") parcourt le dossier et tous ses sous-dossiers, et is_file() ne garde que les fichiers ; la liste prevus évite que deux fichiers visent la même destination pendant la simulation.

Simulation (sortie réelle, début et fin ; 34 lignes « je rangerais » au total) :

Ce que vous voyez
Mode simulation : True - 44 fichiers avant je rangerais F00412 papeterie MARTIN 01-10-2026.pdf -> entrainement/rangees/Papeterie-Martin/2026-10-01_Papeterie-Martin_F00412.pdf je rangerais F00414 Café du coin 15-10-2026.pdf -> entrainement/rangees/Café-Du-Coin/2026-10-15_Café-Du-Coin_F00414.pdf je rangerais F00416 TRANSPORTS RAPIDES 29-10-2026.pdf -> entrainement/rangees/Transports-Rapides/2026-10-29_Transports-Rapides_F00416.pdf […] 34 factures à ranger ; 6 à traiter à la main : FACT-00412_Papeterie Martin_01-10-2026.pdf (conflit : 2026-10-01_Papeterie-Martin_F00412.pdf existe déjà) FACT-00450_Bureau Express.pdf Scan_0042.pdf Scan_0043.pdf Scan_0044.pdf Scan_0045.pdf 44 fichiers après : rien n'a bougé

44 fichiers : les 40 factures, les 3 relevés et le fichier Excel des fournisseurs (il servira en semaine 4). La facture F00412 est arrivée deux fois (par l'export et par courriel) : la seconde copie est mise de côté, pas écrasée.

Action (SIMULATION = False, sortie réelle) : 34 factures rangées ; 6 à traiter à la main : (même liste), puis 44 fichiers après : aucun fichier perdu. Relancé une seconde fois, le script ne range plus rien (0 factures rangées) : les factures sont déjà rangées, les 6 autres sont toujours de côté. Contenu du dossier rangees après l'action (sortie réelle d'un petit programme de comptage) :

Ce que vous voyez
Bureau-Express 5 factures Café-Du-Coin 6 factures Imprimerie-Leroy 6 factures Nettoyage-Pro 6 factures Papeterie-Martin 6 factures Transports-Rapides 5 factures

Fusionner les trois relevés en un seul fichier, avec l'en-tête une seule fois :

Code
"""Fusionne les relevés du mois en un seul fichier CSV (l'en-tête une seule fois)."""
import csv
from pathlib import Path

RELEVES = Path("entrainement") / "releves"
SORTIES = Path("entrainement") / "sorties"
FUSION = SORTIES / "releves-octobre.csv"

SORTIES.mkdir(exist_ok=True)
if FUSION.exists():
    print(FUSION, "existe déjà : je ne l'écrase pas.")
else:
    lignes = 0
    with open(FUSION, "w", encoding="utf-8-sig", newline="") as sortie:
        ecrivain = csv.writer(sortie, delimiter=";")
        entete_ecrit = False
        for releve in sorted(RELEVES.glob("*.csv")):
            with open(releve, encoding="utf-8-sig", newline="") as fichier:
                lecteur = csv.reader(fichier, delimiter=";")
                entete = next(lecteur)
                if not entete_ecrit:
                    ecrivain.writerow(entete)
                    entete_ecrit = True
                for ligne in lecteur:
                    ecrivain.writerow(ligne)
                    lignes = lignes + 1
    print(lignes, "lignes écrites dans", FUSION)

Affiche 39 lignes écrites dans entrainement/sorties/releves-octobre.csv : 12 + 11 + 16. csv.writer écrit des lignes au format CSV ; next(lecteur) lit l'en-tête de chaque relevé, qu'on n'écrit que la première fois ; utf-8-sig à l'écriture ajoute le BOM, pour que le tableur reconnaisse les accents.

Lancer le script chaque mois, depuis le terminal. Un terminal (Invite de commandes ou PowerShell sous Windows, Terminal sous macOS) permet de lancer un script sans éditeur : on se place dans le dossier de travail avec cd, puis on tape python ranger.py (sous Windows, py ranger.py marche aussi). Thonny ou un double-clic font la même chose ; le terminal a l'avantage de garder l'affichage à l'écran.

Simulateur de rangement · un petit dossier de 8 factures
Dossier entrainement
Ce que le script affiche
[3]Pourquoi ça marche

La simulation transforme une action irréversible en liste à relire : vous voyez chaque renommage avant qu'il ait lieu. La copie rend toute erreur sans conséquence : on supprime le dossier d'entraînement et on le décompresse de nouveau. Le test exists() empêche le seul accident vraiment grave, l'écrasement. Et le comptage avant / après prouve, par un nombre, qu'aucun fichier ne s'est perdu.

À retenircopie, simulation, relecture, action, comptage ; jamais d'écrasement, jamais de suppression.
[4]Entraînement

Bon réflexe ou piège ?

Je teste mon script directement sur le vrai dossier du mois, il a l'air juste.
le premier essai se fait toujours sur la copie, en simulation.
Je vérifie que le nombre de fichiers est le même avant et après.
c'est la preuve qu'aucun fichier ne s'est perdu.
Si la destination existe, je renomme quand même : ce sera la même facture.
sous macOS et Linux, le fichier existant serait remplacé sans prévenir ; on met de côté et on regarde.
J'ajoute une ligne qui supprime automatiquement les doublons.
deux noms identiques peuvent cacher deux documents différents ; c'est une personne qui décide.
Je relis la liste de la simulation avant de passer SIMULATION = False.
c'est tout l'intérêt de la simulation.
[5]Quiz

1. Avec SIMULATION = True, que fait le script de rangement ?

seule la branche else (simulation désactivée) appelle rename (sortie vérifiée : « rien n'a bougé »).

2. Pourquoi la seconde facture F00412 n'est-elle pas rangée ?

le script n'écrase jamais (sortie vérifiée).

3. 44 fichiers avant, 44 après : qu'est-ce que cela prouve ?

les fichiers ont été déplacés, pas perdus ; les 6 mis de côté sont toujours là.
[6]Kit

🎒 Votre kit — Épisode 14

30 min

Exercice : le rituel de fin de mois, sur la copie

  1. Enregistrez ranger.py dans votre dossier de travail, à côté du dossier entrainement.
  2. Lancez-le en simulation (depuis Thonny ou le terminal) ; relisez la liste ; vérifiez 3 nouveaux noms à la main.
  3. Passez SIMULATION = False et relancez ; vérifiez les comptes (44 avant, 44 après) et ouvrez le dossier rangees dans votre explorateur de fichiers.
  4. Lancez la fusion des relevés ; ouvrez releves-octobre.csv dans votre tableur.
  5. Supprimez vous-même le dossier entrainement, décompressez de nouveau l'archive, et recommencez : tout doit se dérouler pareil.

Code à copier : ranger.py et la fusion des relevés (ci-dessus).

Checklist

  • J'ai lancé le rangement en simulation et relu la liste.
  • Après l'action, j'ai 44 fichiers avant et 44 après.
  • La fusion a écrit 39 lignes, que mon tableur ouvre correctement.
Vigilancen'utilisez ce script sur un vrai dossier qu'après la fin du livre, sur une copie de ce dossier, en simulation d'abord.
[7]Journal

✍️ Journal de bord

Qu'avez-vous ressenti au moment de passer SIMULATION = False ? Qu'est-ce qui vous a rassurée ou rassuré ?

[8]Sources
  1. Python Software Foundation, documentation de Path.rename (« sous Unix, si la cible existe et est un fichier, elle est remplacée silencieusement ») et précision pour Windows (FileExistsError), discussion de la documentation (bugs.python.org, message 388570), consulté le 31/10/2026.
  2. Python Software Foundation, Utiliser Python sous Windows (lanceur py, Python install manager, lancer un script en ligne de commande), consulté le 31/10/2026.
  3. Python Software Foundation, csv — csv.writer, consulté le 31/10/2026.
  4. Sorties du rangement (simulation, action, second lancement), du comptage, de la fusion et de la démonstration d'écrasement : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
15
S3 · VENDREDI · Pratiquer · Bilan d'arc

Vos fichiers : le bilan

Votre outil de synthèse, le récapitulatif de la semaine et votre badge.

⏱ 25 min · arbre de décision · carrousel · quiz final
[1]Scénario
Nadia · Vendredi, 17 h

Nadia supprime son dossier d'entraînement, le décompresse de nouveau, et rejoue tout le rituel devant Karim : simulation, relecture, action, fusion des relevés. Quatre minutes, relecture comprise. « Et les 6 fichiers mis de côté ? » demande-t-il. « Quatre scans, une facture sans date, un doublon. Ceux-là, je les regarde moi-même. C'est le but. »

[2]Outil

« Avant de lancer un script sur des fichiers »

Aide à la décision interactive
[3]Carrousel

🎨 Carrousel récapitulatif — Semaine 3

[4]Quiz final

1. Qu'affiche print(date(2026, 10, 3) + timedelta(days=45)) ?

28 jours restent en octobre après le 3, puis 17 en novembre (sortie vérifiée).

2. Pour chemin = Path("entrainement") / "factures" / "Scan_0042.pdf", que valent chemin.name, chemin.stem et chemin.suffix ?

nom complet, nom sans extension, extension avec son point (sortie vérifiée).

3. Un script lit un CSV et trouve une seule colonne par ligne. Que vérifiez-vous d'abord ?

avec le mauvais séparateur, la ligne entière reste en un morceau.

4. Où se fait le premier lancement d'un script qui renomme des fichiers ?

la simulation permet de relire chaque action avant qu'elle ait lieu.

5. Sous macOS, rename vers une destination qui existe…

sous Windows, Python s'arrête sur FileExistsError ; d'où le test exists() partout.
[5]Kit

🎒 Votre kit de fin d'arc

30 min

Le rituel de fin de mois, sur la copie

Synthèse : ranger.py et la fusion des relevés, lancés en simulation puis pour de vrai sur le dossier d'entraînement, avec le comptage avant / après.

Consigne :

  1. Repartez d'un dossier d'entraînement neuf (supprimez l'ancien, décompressez l'archive).
  2. Lancez ranger.py en simulation, relisez, puis en action ; notez les comptes.
  3. Lancez la fusion, puis le programme d'écriture du récapitulatif (épisode 13) en le faisant lire sorties/releves-octobre.csv : un seul total, 14 475,98 €.
  4. Rassemblez vos trois scripts de la semaine dans votre dossier de travail et exportez votre carnet de bord.

Code à copier : ranger.py (épisode 14).

Checklist

  • J'ai exporté mon carnet (bouton dans le menu).
  • Le rangement donne 34 factures rangées, 6 à traiter, 44 fichiers avant et après.
  • Le total des relevés fusionnés vaut 14 475,98 €.
[6]Journal

✍️ Journal de bord — bilan

Quelle tâche de votre vrai travail ressemble à ce rituel ? Quelles précautions de cette semaine y appliqueriez-vous ?

✦ MAÎTRESSE · MAÎTRE DE SES FICHIERS · SEMAINE 3 ✦

Bravo ! Vous avez terminé la semaine 3. Vous savez calculer sur les dates, parcourir un dossier, lire et écrire des fichiers CSV, et laisser un script ranger des fichiers sans rien risquer.

ARC 4 · SEMAINE 4

Aller plus loin, en sécurité

Cette semaine, vous rangez vos données par nom avec les dictionnaires, vous installez votre première bibliothèque pour lire le fichier Excel du comptable, et vous apprenez à vous faire aider par une IA sans lui confier ce qui ne doit pas sortir de l'entreprise. Vendredi, votre script de fin de mois est complet.

16
S4 · LUNDI · Comprendre

Dictionnaires, tuples et ensembles

Retrouver une valeur par son nom, totaliser par fournisseur, grouper des valeurs qui vont ensemble, garder les valeurs distinctes.

⏱ 25 min de lecture · 5 min de quiz
[1]Scénario
Nadia · Lundi, 9 h

Le même fournisseur apparaît sous quatre écritures : PAPETERIE MARTIN, Papeterie Martin SARL, pap. martin… Le comptable veut un seul total par fournisseur, et le dossier officiel de chacun. Nadia a fait une liste par fournisseur, puis une autre, puis s'est perdue. Karim : « Tu cherches une valeur à partir d'un nom ? Dans ton tableur, tu ferais une RECHERCHEV. En Python, c'est un dictionnaire. »

[2]Concept

Le dictionnaire : une valeur, retrouvée par son nom

Créer, lire, ajouter. Un dictionnaire associe des clés à des valeurs, entre accolades, comme un vrai dictionnaire associe un mot à sa définition :

Code
dossiers = {"Papeterie Martin": "Papeterie-Martin", "Imprimerie Leroy": "Imprimerie-Leroy"}
print(dossiers["Papeterie Martin"])
dossiers["Café du Coin"] = "Cafe-du-Coin"
print(len(dossiers), "Café du Coin" in dossiers, "Nettoyage Pro" in dossiers)
print(dossiers.get("Nettoyage Pro", "inconnu"))
for nom, dossier in dossiers.items():
    print(nom, "->", dossier)

Affiche Papeterie-Martin, puis 3 True False, puis inconnu, puis les trois paires Papeterie Martin -> Papeterie-Martin, Imprimerie Leroy -> Imprimerie-Leroy, Café du Coin -> Cafe-du-Coin.

  • dossiers[cle] donne la valeur ; dossiers[cle] = valeur ajoute une paire (ou remplace la valeur si la clé existe déjà : une clé est unique).
  • in teste si une clé existe ; not in teste l'inverse.
  • .get(cle, defaut) renvoie la valeur, ou defaut si la clé n'existe pas. Avec les crochets, une clé absente arrête le programme : KeyError: 'Nettoyage Pro' (« erreur de clé »).
  • .items() donne les paires, qu'on parcourt avec deux variables ; les paires restent dans l'ordre où on les a ajoutées.

Une table de correspondance. Toutes les écritures d'un même fournisseur, ramenées au nom officiel (les clés sont écrites en minuscules, et on passe le texte brut en minuscules avant de chercher) :

Code
CORRESPONDANCE = {
    "papeterie martin": "Papeterie Martin",
    "papeterie martin sarl": "Papeterie Martin",
    "pap. martin": "Papeterie Martin",
    "imprimerie leroy": "Imprimerie Leroy",
    "leroy impressions": "Imprimerie Leroy",
    "café du coin": "Café du Coin",
    "cafe du coin": "Café du Coin",
    "nettoyage pro": "Nettoyage Pro",
}
for brut in ["PAPETERIE MARTIN", "Papeterie Martin SARL", " pap. martin", "Leroy Impressions", "CAFE DU COIN", "Bureau Express"]:
    officiel = CORRESPONDANCE.get(brut.strip().lower(), "")
    if officiel == "":
        print(f"{brut:<24} -> inconnu : à ajouter à la table")
    else:
        print(f"{brut:<24} -> {officiel}")

Sortie réelle :

Ce que vous voyez
PAPETERIE MARTIN -> Papeterie Martin Papeterie Martin SARL -> Papeterie Martin pap. martin -> Papeterie Martin Leroy Impressions -> Imprimerie Leroy CAFE DU COIN -> Café du Coin Bureau Express -> inconnu : à ajouter à la table

Un dictionnaire peut s'écrire sur plusieurs lignes, une paire par ligne : c'est plus lisible.

Totaliser par clé. La ligne à retenir de la semaine : totaux[f] = totaux.get(f, 0) + montant. Si le fournisseur n'a pas encore de total, get renvoie 0 ; sinon, son total actuel.

Code
factures = [("Papeterie Martin", 1250.50), ("Imprimerie Leroy", 89.90), ("Papeterie Martin", 240.00),
            ("Café du Coin", 655.00), ("Imprimerie Leroy", 520.00), ("Papeterie Martin", 64.80)]
totaux = {}
nombres = {}
for fournisseur, montant in factures:
    totaux[fournisseur] = totaux.get(fournisseur, 0) + montant
    nombres[fournisseur] = nombres.get(fournisseur, 0) + 1
for fournisseur, total in totaux.items():
    print(f"{fournisseur:<18}{nombres[fournisseur]:>2} factures{total:>10.2f} €")
print(round(sum(totaux.values()), 2))

Sortie réelle :

Ce que vous voyez
Papeterie Martin 3 factures 1555.30 € Imprimerie Leroy 2 factures 609.90 € Café du Coin 1 factures 655.00 € 2820.2

.values() donne les valeurs seules : leur somme doit égaler le total de toutes les factures, c'est le contrôle.

Le tuple : des valeurs qui vont ensemble. Chaque facture ci-dessus est écrite entre parenthèses : ("Papeterie Martin", 1250.50). C'est un tuple : une suite de valeurs groupées, qu'on ne modifie pas. On le « déballe » dans plusieurs variables, et une fonction peut en renvoyer un pour donner deux résultats :

Code
facture = ("F00412", "Papeterie Martin", 1250.50)
numero, fournisseur, montant = facture
print(numero, fournisseur, montant, len(facture))


def extremes(montants):
    """Renvoie la plus petite et la plus grosse facture."""
    return min(montants), max(montants)


petite, grosse = extremes([1250.50, 89.90, 240.00, 655.00])
print(petite, grosse)

Affiche F00412 Papeterie Martin 1250.5 3, puis 89.9 1250.5. Essayer de modifier un tuple (facture[2] = 1300.00) arrête le programme : TypeError: 'tuple' object does not support item assignment (« un tuple ne permet pas de modifier un élément »). C'est voulu : une facture enregistrée ne change pas.

L'ensemble : des valeurs distinctes. Un ensemble (set) ne garde chaque valeur qu'une fois, sans ordre :

Code
fournisseurs = ["Papeterie Martin", "Imprimerie Leroy", "Papeterie Martin", "Café du Coin", "Imprimerie Leroy"]
distincts = set(fournisseurs)
print(len(fournisseurs), len(distincts))
print(sorted(distincts))
print("Café du Coin" in distincts)
deja_vus = set()
for nom in ["F00412", "F00413", "F00412"]:
    if nom in deja_vus:
        print("Doublon :", nom)
    deja_vus.add(nom)

Affiche 5 3, puis ['Café du Coin', 'Imprimerie Leroy', 'Papeterie Martin'], puis True, puis Doublon : F00412. set() crée un ensemble vide ; .add ajoute ; in y est très rapide. C'est la liste des « déjà vus » de l'épisode 7, en plus simple. Pour l'afficher dans l'ordre, on le trie.

Votre besoinLa structure
une suite de valeurs, dans l'ordre, qu'on complète (les montants du mois)une liste [ ]
retrouver une valeur par un nom (le dossier d'un fournisseur, un total)un dictionnaire {cle: valeur}
quelques valeurs qui vont ensemble et ne changent pas (une facture)un tuple ( )
des valeurs distinctes, sans doublon (les fournisseurs du mois)un ensemble set()
Suivre les variables · un dictionnaire de totaux
Ce qui s'affiche

[3]Recherche

Ce que disent les sources

  • Tutoriel officiel de Python — Un dictionnaire est un ensemble de paires clé-valeur, où les clés sont uniques ; les principales opérations consistent à stocker une valeur avec une clé et à retrouver la valeur à partir de la clé. Demander une clé absente lève une erreur ; in teste si une clé est présente. list(d) renvoie les clés dans l'ordre d'insertion [1].
  • Documentation officielle des types natifs — dict.get(key, default) renvoie la valeur de key si elle existe, sinon default ; depuis Python 3.7, l'ordre d'insertion des dictionnaires est garanti [2].
  • Tutoriel officiel de Python — Les tuples sont immuables et contiennent souvent des valeurs de types différents, auxquelles on accède par déballage ou par indice ; un ensemble est une collection non ordonnée sans doublon, qui sert notamment à tester l'appartenance et à éliminer les doublons [1].
[4]Mythes
« Un dictionnaire est trié par clé. »
il garde l'ordre d'ajout ; pour un affichage trié, on parcourt sorted(dictionnaire).
« Un ensemble garde l'ordre. »
il n'a pas d'ordre ; on le trie pour l'afficher (sortie vérifiée).
« Un dictionnaire, c'est compliqué. »
c'est une RECHERCHEV : un nom en entrée, une valeur en sortie. Et get évite le message « valeur non disponible ».
[5]Quiz

1. Après d = {"a": 1, "b": 2}, puis d["c"] = 3 et d["a"] = 10, que vaut d ?

une clé est unique ; d["a"] = 10 remplace sa valeur (sortie vérifiée).

2. Que renvoie d.get("z", 0) si "z" n'est pas une clé ?

get renvoie la valeur par défaut donnée (sortie vérifiée).

3. Que vaut len({3, 1, 3, 2, 1}) ?

un ensemble ne garde que les valeurs distinctes : 1, 2, 3 (sortie vérifiée).
[6]Kit

🎒 Votre kit — Épisode 16

25 min

Exercice : la table et les totaux

  1. Recopiez la table de correspondance et ajoutez deux écritures pour « Bureau Express » ; relancez : plus aucun « inconnu ».
  2. Recopiez le programme des totaux et ajoutez deux factures inventées ; vérifiez que la somme des totaux égale la somme de toutes les factures.
  3. Avec un ensemble, affichez les fournisseurs distincts du mois, triés.
  4. Écrivez une fonction extremes qui renvoie aussi la moyenne : min, max et sum(...) / len(...) dans un tuple de trois valeurs.

Code à copier : la table de correspondance et le programme des totaux (ci-dessus).

Checklist

  • Je retrouve une valeur par sa clé, avec get quand la clé peut manquer.
  • Je totalise par clé avec totaux.get(cle, 0) + montant.
  • Je sais choisir entre liste, dictionnaire, tuple et ensemble.
Vigilanceune écriture inconnue de la table est signalée, jamais devinée ; c'est vous qui l'ajoutez.
[7]Journal

✍️ Journal de bord

Quelles tables de correspondance tenez-vous dans votre tête ou dans un tableur (codes clients, abréviations, services) ? Laquelle deviendrait votre premier dictionnaire ?

[8]Sources
  1. Python Software Foundation, Tutoriel Python — 5.3. Tuples et séquences, 5.4. Ensembles, 5.5. Dictionnaires, consulté le 31/10/2026.
  2. Python Software Foundation, Types natifs — Les types de correspondances : dict, consulté le 31/10/2026.
  3. Sorties des extraits (dictionnaires, KeyError, tuples, TypeError, ensembles) : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
17
S4 · MARDI · Pratiquer

Totaliser par fournisseur

Lire, regrouper, écrire, vérifier : un récapitulatif fiable que le tableur ouvre sans retouche.

⏱ 25 min de pratique · 5 min de quiz
[1]Scénario
Nadia · Mardi, 12 h 30

Le comptable veut, avant le 5 du mois, le total des factures par fournisseur, dans un fichier qu'il ouvre dans son tableur. Jusqu'ici, Nadia faisait un tableau croisé à partir des trois relevés recopiés à la main. « Et si les totaux ne tombent pas juste ? » demande-t-elle. Karim : « Ton script fera la preuve lui-même. »

[2]Méthode

« Lire, regrouper, écrire, vérifier »

Rappels : lire un CSV et écrire sans écraser (voir épisode 13), totaliser par clé (voir épisode 16).

  1. Lire les trois relevés avec DictReader (épisode 13).
  2. Regrouper avec un dictionnaire : totaux[fournisseur] = totaux.get(fournisseur, 0) + montant.
  3. Écrire un CSV pour le tableur : séparateur ;, virgule décimale (on formate avec :.2f puis .replace(".", ",")), utf-8-sig pour les accents, jamais par-dessus un fichier existant.
  4. Vérifier : la somme des totaux doit égaler le total de toutes les lignes des relevés.
Code
"""Écrit le total des factures par fournisseur, à partir des relevés du mois, dans un CSV pour le tableur."""
import csv
from pathlib import Path

RELEVES = Path("entrainement") / "releves"
SORTIES = Path("entrainement") / "sorties"
TOTAUX = SORTIES / "totaux-fournisseurs.csv"

totaux = {}
total_releves = 0
for releve in sorted(RELEVES.glob("*.csv")):
    with open(releve, encoding="utf-8-sig", newline="") as fichier:
        for ligne in csv.DictReader(fichier, delimiter=";"):
            montant = float(ligne["montant"].replace(",", "."))
            fournisseur = ligne["fournisseur"]
            totaux[fournisseur] = totaux.get(fournisseur, 0) + montant
            total_releves = total_releves + montant

SORTIES.mkdir(exist_ok=True)
if TOTAUX.exists():
    print(TOTAUX, "existe déjà : je ne l'écrase pas.")
else:
    with open(TOTAUX, "w", encoding="utf-8-sig", newline="") as sortie:
        ecrivain = csv.writer(sortie, delimiter=";")
        ecrivain.writerow(["fournisseur", "total"])
        for fournisseur in sorted(totaux):
            ecrivain.writerow([fournisseur, f"{totaux[fournisseur]:.2f}".replace(".", ",")])
    print("Écrit :", TOTAUX)
    print(TOTAUX.read_text(encoding="utf-8-sig"))
somme = round(sum(totaux.values()), 2)
print("Contrôle :", somme, "=", round(total_releves, 2), "?", somme == round(total_releves, 2))

Premier lancement, sortie réelle :

Ce que vous voyez
Écrit : entrainement/sorties/totaux-fournisseurs.csv fournisseur;total Bureau Express;2012,30 Café du Coin;2112,40 Imprimerie Leroy;3165,50 Nettoyage Pro;1904,68 Papeterie Martin;2097,10 Transports Rapides;3184,00 Contrôle : 14475.98 = 14475.98 ? True

Deuxième lancement : entrainement/sorties/totaux-fournisseurs.csv existe déjà : je ne l'écrase pas., puis le même contrôle. Le total du mois est celui que vous aviez calculé en semaine 3 : 14 475,98 €. sorted(totaux) parcourt les clés dans l'ordre alphabétique. Les montants sont arrondis à l'affichage seulement ; le contrôle compare des totaux arrondis au centime.

[3]Pourquoi ça marche

Le dictionnaire fait en une ligne ce que le tableau croisé faisait en dix clics, et il le refait à l'identique chaque mois. Le contrôle final transforme un récapitulatif « qui a l'air juste » en récapitulatif prouvé : si une ligne était oubliée ou comptée deux fois, les deux nombres différeraient. Et le format (;, virgule, utf-8-sig) est choisi pour le tableur du destinataire, pas pour Python.

À retenirlire, regrouper avec get, écrire pour le tableur, vérifier par une somme.
[4]Entraînement

Bon réflexe ou piège ?

Je compare la somme des totaux au total des relevés.
c'est la preuve qu'aucune ligne ne s'est perdue.
J'écris les montants avec un point décimal pour un tableur réglé en français.
le tableur risque de les lire comme du texte ou des dates ; on remplace le point par une virgule.
J'utilise totaux[fournisseur] += montant sans get.
la première facture d'un fournisseur provoquerait une KeyError ; get(fournisseur, 0) règle le cas.
Un fournisseur inconnu de ma table apparaît dans le relevé : je l'ignore.
son montant manquerait au total ; le contrôle le révélerait.
J'ouvre le fichier produit dans le tableur avant de l'envoyer.
la dernière vérification se fait avec l'outil du destinataire.
[5]Quiz

1. Pourquoi totaux.get(fournisseur, 0) plutôt que totaux[fournisseur] ?

la clé n'existe qu'après le premier ajout.

2. Quel contrôle prouve qu'aucune ligne n'a été oubliée ?

sortie vérifiée : 14475.98 des deux côtés.

3. Pourquoi .replace(".", ",") sur les montants écrits ?

le séparateur de colonnes est ;, et la virgule sert aux décimales.
[6]Kit

🎒 Votre kit — Épisode 17

25 min

Exercice : le récapitulatif du comptable

  1. Dans votre dossier de travail (dossier d'entraînement neuf ou non : ce script ne touche pas aux factures), lancez le programme des totaux.
  2. Ouvrez totaux-fournisseurs.csv dans votre tableur : les montants doivent être des nombres (alignés à droite), avec les accents de « Café ».
  3. Ajoutez une colonne « nombre de factures » (un second dictionnaire, comme à l'épisode 16).
  4. Relancez : le script doit refuser d'écraser. Supprimez vous-même l'ancien fichier, relancez.

Code à copier : le programme des totaux (ci-dessus).

Checklist

  • Mon fichier s'ouvre correctement dans mon tableur.
  • Le contrôle affiche True.
  • J'ai ajouté le nombre de factures par fournisseur.
Vigilancele récapitulatif aide le comptable ; c'est la comptabilité qui fait foi.
[7]Journal

✍️ Journal de bord

Quel récapitulatif envoyez-vous régulièrement à quelqu'un ? Quel contrôle prouverait qu'il est juste ?

[8]Sources
  1. Python Software Foundation, csv — csv.writer et Types natifs — dict.get (docs.python.org/fr/3/library/csv.html ; stdtypes.html), consulté le 31/10/2026.
  2. Sorties du programme des totaux (premier et second lancements) : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre), le 31/10/2026.
18
S4 · MERCREDI · Comprendre

Installer une bibliothèque pour lire un fichier Excel

Un environnement virtuel pour le projet, pip pour installer openpyxl depuis PyPI, et un fichier Excel lu sans être modifié.

⏱ 25 min de lecture · 5 min de quiz
[1]Scénario
Nadia · Mercredi, 14 h

La liste officielle des fournisseurs (le nom tel qu'il apparaît dans les factures, le dossier où les ranger, le code comptable) est dans fournisseurs.xlsx, tenu par le comptable. Nadia la recopie dans son script à chaque changement. « Python sait lire un fichier Excel ? » Karim : « Pas tout seul. Il faut lui installer une bibliothèque. Avec l'accord du service informatique, sur ton poste de travail. »

[2]Concept

Paquets, environnements virtuels et pip

Bibliothèque standard ou paquet à installer. La bibliothèque standard (semaine 3) ne lit pas les fichiers Excel. D'autres développeurs ont écrit des bibliothèques pour cela et les publient sur PyPI (Python Package Index, pypi.org), le catalogue officiel des paquets Python. On les installe avec pip, l'installateur livré avec Python.

Un projet, un environnement. Un environnement virtuel est un dossier (souvent nommé .venv) qui contient une copie de travail de Python et les paquets d'un seul projet. Installer dedans ne touche ni au Python de l'ordinateur ni aux autres projets ; en cas de problème, on supprime le dossier et on le recrée.

Dans un terminal, depuis votre dossier de travail :

ÉtapeWindows (PowerShell ou Invite de commandes)macOS
créer l'environnementpy -m venv .venvpython3 -m venv .venv
l'activer.venv\Scripts\activatesource .venv/bin/activate
installer openpyxlpython -m pip install openpyxlpython -m pip install openpyxl
voir les paquets installéspython -m pip listpython -m pip list

Une fois l'environnement activé, son nom apparaît au début de la ligne de commande : (.venv). Session réelle (terminal Linux, Python 3.15.2, le 31/10/2026 ; à la première installation, pip affiche Downloading au lieu de Using cached) :

Dans le terminal
$ python -m venv .venv $ source .venv/bin/activate (.venv) $ python --version Python 3.15.2 (.venv) $ python -m pip install openpyxl Collecting openpyxl Using cached openpyxl-3.1.5-py2.py3-none-any.whl.metadata (2.5 kB) Collecting et-xmlfile (from openpyxl) Using cached et_xmlfile-2.0.0-py3-none-any.whl.metadata (2.7 kB) Using cached openpyxl-3.1.5-py2.py3-none-any.whl (250 kB) Using cached et_xmlfile-2.0.0-py3-none-any.whl (18 kB) Installing collected packages: et-xmlfile, openpyxl Successfully installed et-xmlfile-2.0.0 openpyxl-3.1.5 (.venv) $ python -m pip list Package Version ---------- ------- et_xmlfile 2.0.0 openpyxl 3.1.5 pip 26.2.2

pip a aussi installé et-xmlfile, dont openpyxl a besoin : c'est une dépendance. Avec Thonny, pas de terminal : menu Outils → Gérer les paquets, recherche « openpyxl », bouton « Installer » (Thonny utilise son propre environnement). Sans l'environnement activé (ou sans installation), import openpyxl s'arrête sur ModuleNotFoundError: No module named 'openpyxl' (« aucun module nommé openpyxl ») : c'est le signe qu'on n'est pas dans le bon environnement.

Vérifier le nom exact avant d'installer. Des paquets malveillants sont régulièrement publiés sur PyPI sous des noms proches de paquets connus (openpyxI avec un i majuscule, open-pyxl…) : c'est le typosquatting. Avant d'installer, vérifiez le nom sur la page officielle du projet (pypi.org/project/openpyxl, lien depuis la documentation d'openpyxl), et tapez-le sans faute.

Lire le fichier Excel, sans le modifier.

Code
from pathlib import Path

import openpyxl

FOURNISSEURS = Path("entrainement") / "fournisseurs.xlsx"
classeur = openpyxl.load_workbook(FOURNISSEURS, read_only=True, data_only=True)
print(classeur.sheetnames)
feuille = classeur["Fournisseurs"]
dossiers = {}
for nom, dossier, code in feuille.iter_rows(min_row=2, values_only=True):
    dossiers[nom] = dossier
    print(f"{nom:<20}{dossier:<20}{code}")
classeur.close()
print(len(dossiers), "fournisseurs lus")

Sortie réelle :

Ce que vous voyez
['Fournisseurs'] Papeterie-Martin Papeterie Martin 401PAP Imprimerie-Leroy Imprimerie Leroy 401IMP Café-Du-Coin Cafe du Coin 401CAF Nettoyage-Pro Nettoyage Pro 401NET Transports-Rapides Transports Rapides 401TRA Bureau-Express Bureau Express 401BUR 6 fournisseurs lus

load_workbook ouvre le classeur ; read_only=True l'ouvre en lecture seule (rien ne peut y être écrit), data_only=True donne les valeurs calculées plutôt que les formules. sheetnames liste les feuilles ; classeur["Fournisseurs"] en choisit une ; iter_rows(min_row=2, values_only=True) parcourt les lignes à partir de la deuxième (la première contient les titres), chacune sous forme de tuple déballé en trois variables. classeur.close() referme le fichier, ce que la documentation demande en mode lecture seule. La première colonne contient le nom tel que le calcule nouveau_nom : c'est la clé du dictionnaire qui servira vendredi.

Sans installation : on peut aussi enregistrer la feuille en CSV depuis le tableur et la lire avec le module csv (épisode 13). openpyxl évite cette manipulation chaque mois.

Auto-diagnostic · mon installation est-elle saine ?
  • J'ai l'accord du service informatique (poste professionnel) ou j'installe sur mon ordinateur personnel.
  • Mon projet a son propre environnement virtuel.
  • Je l'active avant d'installer.
  • J'installe avec python -m pip, jamais avec les droits d'administrateur.
  • J'ai vérifié le nom exact du paquet sur sa page officielle.
  • python -m pip list montre ce que j'attends, sans paquet inconnu.
[3]Recherche

Ce que disent les sources

  • Documentation officielle de venv — Un environnement virtuel est créé au-dessus d'une installation de Python existante ; il est isolé, contient ses propres paquets, et peut être supprimé puis recréé ; il ne doit pas être partagé ni versionné. Les commandes d'activation diffèrent selon le système [1].
  • openpyxl — openpyxl est une bibliothèque Python pour lire et écrire des fichiers Excel 2010 (xlsx/xlsm), sous licence MIT ; la version 3.1.5 est celle que pip installe le 31/10/2026, avec sa dépendance et-xmlfile 2.0.0 [2]. En mode read_only, le classeur doit être fermé explicitement avec close() ; data_only=True donne les valeurs des formules plutôt que les formules elles-mêmes [3].
  • Typosquatting — En mars 2024, PyPI a suspendu pendant une dizaine d'heures les inscriptions et la création de projets, après la publication de plus de 500 paquets malveillants aux noms imitant des paquets connus [4].
  • Thonny — Thonny propose un gestionnaire de paquets graphique (« Manage packages »), qui installe dans l'environnement utilisé par Thonny [5].
[4]Mythes
« Installer un paquet, c'est installer un virus. »
un paquet connu, installé depuis PyPI sous son nom exact, dans un environnement virtuel, est un geste courant et maîtrisé. Le risque vient des noms approximatifs et des sources non officielles.
« N'importe quel paquet au bon nom fait l'affaire. »
un caractère d'écart peut mener à un paquet malveillant ; vérifiez le nom sur la page officielle.
« Il faut Excel pour lire un fichier Excel. »
un fichier .xlsx est un format documenté, que openpyxl lit sans Excel ; votre script peut tourner sur un ordinateur qui n'a pas de tableur.
[5]Quiz

1. Pourquoi installer openpyxl dans un environnement virtuel ?

en cas de problème, on supprime le dossier .venv et on recommence.

2. import openpyxl donne ModuleNotFoundError. Que vérifiez-vous d'abord ?

le paquet est installé dans un environnement précis ; il faut lancer le script depuis celui-ci.

3. À quoi sert read_only=True dans load_workbook ?

le fichier du comptable n'est jamais modifié par le script.
[6]Kit

🎒 Votre kit — Épisode 18

25 min

Exercice : votre première installation

  1. Sur un poste professionnel, demandez d'abord l'accord du service informatique ; sinon, utilisez votre ordinateur personnel.
  2. Dans votre dossier de travail, créez et activez l'environnement .venv, installez openpyxl, puis python -m pip list (ou utilisez le gestionnaire de paquets de Thonny).
  3. Lancez le programme de lecture : 6 fournisseurs lus.
  4. Ajoutez au programme le dictionnaire codes (nom → code comptable), et affichez le code de « Café-Du-Coin » (attendu : 401CAF).

Code à copier : le programme de lecture du fichier Excel (ci-dessus).

Checklist

  • Mon environnement virtuel est créé et activé.
  • openpyxl est installé sous son nom exact.
  • Mon script lit les 6 fournisseurs sans modifier le fichier.
Vigilancele fichier Excel du comptable n'est jamais modifié par un script ; travaillez sur une copie, ouverte en lecture seule.
[7]Journal

✍️ Journal de bord

Quels fichiers Excel consultez-vous chaque semaine ou chaque mois ? Lequel aimeriez-vous que votre script lise ?

[8]Sources
  1. Python Software Foundation, venv — Création d'environnements virtuels, consulté le 30/10/2026.
  2. Installation réelle d'openpyxl avec pip le 31/10/2026 sous Python 3.12.13, 3.15.0 et 3.15.2 (version 3.1.5, licence MIT, dépendance et-xmlfile 2.0.0 ; métadonnées du paquet affichées par pip show) (journaux de test de ce livre).
  3. openpyxl, Optimised Modes (« The workbook must be explicitly closed with the close() method » en lecture seule) et Changes (option data_only : extraire les valeurs des formules), consulté le 31/10/2026.
  4. TechTarget, Typosquatting campaign, malicious packages slam PyPI (mars 2024), consulté le 30/10/2026.
  5. It's FOSS, Thonny: An Ideal IDE for Teaching Python Programming in Schools, « Manage Pip from GUI », consulté le 30/10/2026.
19
S4 · JEUDI · Pratiquer

Se faire aider par une IA, et vérifier

Demander, relire, tester sur la copie, garder ou refuser : utiliser un assistant d'IA sans renoncer à comprendre, et sans lui confier ce qui ne doit pas sortir de l'entreprise.

⏱ 25 min de pratique · 5 min de quiz
[1]Scénario
Nadia · Jeudi, 10 h

Nadia demande à un assistant d'IA d'ajouter à son script « un contrôle des doublons ». En dix secondes, elle reçoit vingt lignes, avec un commentaire enthousiaste. Elle les lance sur le dossier d'entraînement : Doublon supprimé, quatre fois. Le compteur affiche 44 fichiers avant, 40 après. Parmi les fichiers effacés, trois scans qui n'étaient pas des doublons du tout. « Heureusement que c'était la copie. »

[2]Méthode

« Demander, relire, tester, garder ou refuser »

Rappels : la règle d'or (Boost), copie et simulation (voir épisode 14).

1. Avant : le cadre.

  • Votre employeur autorise-t-il l'IA pour ce travail, et avec quel outil ? Beaucoup d'entreprises fournissent un outil (offre professionnelle) ou en interdisent d'autres : suivez la règle de votre entreprise.
  • Avec un compte personnel, vos conversations peuvent servir à entraîner les modèles, sauf si vous le refusez dans les réglages : « Améliorer le modèle pour tous » (ChatGPT, Paramètres → Gestion des données) ; « Aider à améliorer nos modèles d'IA » (Claude, Paramètres → Confidentialité). Les offres professionnelles de ces services ne s'en servent pas par défaut ; vérifiez celle que vous utilisez.

2. Demander : précis, et sans rien de confidentiel.

  • Le contexte, le but, les contraintes, un exemple inventé : « Mon script Python range des factures (exemple de nom : FACT-00412_Papeterie Martin_01-10-2026.pdf). Ajoute un contrôle des doublons. Contraintes : ne supprime rien, n'écrase rien, signale seulement ; reste compatible avec ma constante SIMULATION. »
  • Jamais de vrais noms de clients, montants, fichiers de l'entreprise ou données personnelles.

3. Relire : chaque ligne comprise. Cherchez en priorité ce qui agit sur les fichiers ou ailleurs : unlink ou remove (suppression), rename sans exists(), open(…, "w") sur un fichier existant, une adresse internet, un envoi. Si une ligne vous échappe, demandez une explication ligne par ligne.

4. Tester : sur la copie, en simulation, avec le comptage.

5. Garder, corriger ou refuser. Vous restez responsable du script.

La réponse de l'assistant (inventée pour le livre, à ne pas garder) :

Code
"""Version proposée par l'assistant d'IA (à ne pas garder) : elle SUPPRIME les doublons."""
from pathlib import Path

DOSSIER = Path("entrainement")
FACTURES = DOSSIER / "factures"


def compter_fichiers(dossier):
    """Renvoie le nombre de fichiers dans le dossier et tous ses sous-dossiers."""
    nombre = 0
    for chemin in dossier.rglob("*"):
        if chemin.is_file():
            nombre = nombre + 1
    return nombre


avant = compter_fichiers(DOSSIER)
vus = []
for fichier in sorted(FACTURES.iterdir()):
    numero = fichier.name[:6]
    if fichier.name.startswith("FACT-"):
        numero = "F" + fichier.name[5:10]
    if numero in vus:
        print("Doublon supprimé :", fichier.name)
        fichier.unlink()
    else:
        vus.append(numero)
print(avant, "fichiers avant,", compter_fichiers(DOSSIER), "après")

Lancée sur le dossier d'entraînement (sortie réelle) :

Ce que vous voyez
Doublon supprimé : FACT-00412_Papeterie Martin_01-10-2026.pdf Doublon supprimé : Scan_0043.pdf Doublon supprimé : Scan_0044.pdf Doublon supprimé : Scan_0045.pdf 44 fichiers avant, 40 après

Deux défauts, que la relecture aurait dû trouver avant le lancement : fichier.unlink() supprime un fichier (sans corbeille), ce que Nadia n'avait pas demandé ; et le « numéro » des scans vaut Scan_0 pour tous (fichier.name[:6]), si bien que trois scans différents passent pour des doublons. La version corrigée signale au lieu de supprimer :

Code
"""Version corrigée par Nadia : les doublons sont signalés et laissés à leur place, en simulation comme en action."""
from pathlib import Path

DOSSIER = Path("entrainement")
FACTURES = DOSSIER / "factures"


def compter_fichiers(dossier):
    """Renvoie le nombre de fichiers dans le dossier et tous ses sous-dossiers."""
    nombre = 0
    for chemin in dossier.rglob("*"):
        if chemin.is_file():
            nombre = nombre + 1
    return nombre


avant = compter_fichiers(DOSSIER)
vus = []
doublons = []
for fichier in sorted(FACTURES.iterdir()):
    numero = fichier.name[:6]
    if fichier.name.startswith("FACT-"):
        numero = "F" + fichier.name[5:10]
    if numero in vus:
        doublons.append(fichier.name)
    else:
        vus.append(numero)
print("Doublons probables, à vérifier à la main :", doublons)
print(avant, "fichiers avant,", compter_fichiers(DOSSIER), "après")

Sortie réelle :

Ce que vous voyez
Doublons probables, à vérifier à la main : ['FACT-00412_Papeterie Martin_01-10-2026.pdf', 'Scan_0043.pdf', 'Scan_0044.pdf', 'Scan_0045.pdf'] 44 fichiers avant, 44 après

Les trois scans restent signalés à tort, mais rien n'est perdu : c'est Nadia qui regarde et décide. Son script de rangement, lui, traitait déjà les doublons sans rien supprimer (épisode 14) : la meilleure décision était peut-être de ne pas ajouter ce code du tout.

Auto-diagnostic · avant de garder un code proposé par une IA
  • Mon employeur autorise cet usage et cet outil.
  • Ma demande ne contenait ni donnée de l'entreprise ni donnée personnelle.
  • Je comprends chaque ligne.
  • Le code ne supprime, n'écrase, n'envoie rien que je n'ai pas demandé.
  • Je l'ai testé sur la copie, en simulation, avec le comptage avant / après.
  • Je sais expliquer à une collègue ce qu'il fait.
[3]Pourquoi ça marche

Un assistant d'IA écrit un code plausible, pas un code vérifié : il peut ajouter ce que vous n'avez pas demandé, ou se tromper avec assurance. La règle d'or reste vraie : un code que vous ne comprenez pas, vous ne le lancez pas. La relecture ciblée (ce qui supprime, écrase ou envoie) attrape l'essentiel ; la copie, la simulation et le comptage rattrapent le reste. Et ne rien confier de confidentiel protège votre entreprise, quel que soit l'outil.

À retenirle cadre d'abord ; une demande précise et sans donnée réelle ; chaque ligne comprise ; testé sur la copie ; gardé, corrigé ou refusé.
[4]Entraînement

Bon réflexe ou piège ?

Je colle le vrai fichier des fournisseurs pour que l'IA comprenne mieux.
ce sont des données de l'entreprise ; un exemple inventé suffit.
Je précise dans ma demande : ne supprime rien, n'écrase rien.
l'assistant suit souvent les contraintes écrites… et vous les vérifiez quand même.
Le code marche du premier coup, je le garde sans le relire.
« marcher » ne veut pas dire « faire seulement ce que je voulais » (sortie vérifiée : 4 fichiers supprimés).
Je demande une explication ligne par ligne de ce que je ne comprends pas.
c'est un excellent usage de l'IA, pour apprendre.
J'utilise l'outil d'IA fourni par mon entreprise plutôt que mon compte personnel.
c'est souvent la règle, et les offres professionnelles n'entraînent pas les modèles sur vos conversations par défaut.
[5]Quiz

1. Dans un code proposé par une IA, quelle ligne doit vous arrêter immédiatement ?

unlink supprime un fichier, sans passer par la corbeille.

2. Que faire avant d'utiliser votre compte personnel d'IA pour le travail ?

la règle de l'entreprise prime ; le réglage limite l'usage de vos conversations.

3. Le code de l'IA a supprimé 4 fichiers dans le dossier d'entraînement. Qu'est-ce qui a évité le pire ?

la relecture aurait dû l'empêcher ; la copie a rendu l'erreur sans conséquence (sortie vérifiée : 44 avant, 40 après).
[6]Kit

🎒 Votre kit — Épisode 19

25 min

Exercice : relire avant de lancer

  1. Lisez la réponse de l'assistant sans la lancer et entourez les lignes qui agissent sur des fichiers.
  2. Expliquez pourquoi les scans passent pour des doublons (indice : que vaut "Scan_0043.pdf"[:6] ?).
  3. Si votre employeur l'autorise et avec un outil adapté, posez la demande de l'étape 2 de la méthode à un assistant d'IA ; sinon, utilisez la réponse fournie. Relisez, testez sur un dossier d'entraînement neuf, avec le comptage.
  4. Remplissez le widget « Avant de garder un code proposé par une IA ».

Code à copier : la version corrigée (ci-dessus).

Checklist

  • J'ai repéré unlink avant de lancer quoi que ce soit.
  • J'ai testé sur la copie, avec le comptage avant / après.
  • Ma demande à l'IA ne contenait aucune donnée réelle.
Vigilanceaucune donnée de l'employeur, de clients ou de collègues dans une IA en ligne non autorisée par votre entreprise.
[7]Journal

✍️ Journal de bord

Pour quelle tâche une IA vous aiderait-elle vraiment ? Qu'est-ce que vous ne lui confierez jamais ?

[8]Sources
  1. OpenAI, Data Controls FAQ / Contrôle des données dans ChatGPT (réglage « Améliorer le modèle pour tous » ; contenus des espaces Business, Enterprise et Edu non utilisés pour l'entraînement) (help.openai.com, article 7730893), consulté le 31/10/2026.
  2. Anthropic, Comment modifier mes paramètres de confidentialité pour l'amélioration du modèle ? (réglage « Aider à améliorer nos modèles d'IA », offres Free, Pro et Max), consulté le 31/10/2026.
  3. CNIL, Comment déployer une IA générative ? La CNIL apporte de premières précisions (18/07/2024 : encadrer les usages, ne pas transmettre de données personnelles, vérifier la réutilisation des données par le fournisseur, former les utilisateurs), consulté le 31/10/2026.
  4. Réponse d'assistant inventée pour le livre, exécutée le 31/10/2026 sur un dossier d'entraînement neuf, sous Python 3.12.13, 3.15.0 et 3.15.2 (journaux de test de ce livre).
20
S4 · VENDREDI · Pratiquer · Final

Votre script de fin de mois, votre boîte à outils et votre certificat

Le script complet, lancé en simulation puis pour de vrai, la boîte à outils de vos quatre semaines et votre certificat.

⏱ 30 min · boîte à outils · carrousel · certificat
[1]Scénario
Nadia · Vendredi, 16 h

Dernier jour du mois. Nadia décompresse un dossier d'entraînement neuf, lance fin_de_mois.py en simulation, relit : 34 factures à ranger, 6 à traiter à la main, les totaux par fournisseur, le contrôle à True. Elle passe SIMULATION = False, relance : 44 fichiers avant, 45 après, le fichier des totaux en plus. Puis elle copie son vrai dossier du mois et recommence, sur la copie, en simulation d'abord. Ce qui lui prenait une demi-journée prend dix minutes, relecture comprise. Karim, qui partira en congés le mois prochain, demande le mode d'emploi. « C'est mon prochain chantier », sourit-elle.

[2]Outil

Ma boîte à outils

Consigne : assemblez votre script de fin de mois à partir des pièces des quatre semaines, lancez-le en simulation, relisez, puis en action sur le dossier d'entraînement ; vérifiez les comptages et le contrôle ; enfin, rangez vos outils dans votre boîte à outils et exportez-la.

Le script complet (code à copier ; dans l'environnement virtuel où openpyxl est installé) :

Code
"""Script de fin de mois : renomme et range les factures, puis totalise les relevés par fournisseur.

À lancer à côté du dossier « entrainement » (ou d'une COPIE de votre vrai dossier), en simulation d'abord.
"""
import csv
from pathlib import Path

import openpyxl

SIMULATION = True  # True : le script affiche ce qu'il ferait, sans rien toucher
DOSSIER = Path("entrainement")
FACTURES = DOSSIER / "factures"
RANGEES = DOSSIER / "rangees"
RELEVES = DOSSIER / "releves"
SORTIES = DOSSIER / "sorties"
FOURNISSEURS = DOSSIER / "fournisseurs.xlsx"


def date_iso(date_fr):
    """Transforme une date JJ-MM-AAAA en AAAA-MM-JJ."""
    return date_fr[6:] + "-" + date_fr[3:5] + "-" + date_fr[:2]


def nettoyer(texte):
    """Renvoie le nom de fournisseur propre : sans espaces autour, majuscules aux mots, tirets."""
    return texte.strip().title().replace(" ", "-")


def nouveau_nom(ancien):
    """Renvoie le nouveau nom d'une facture, ou "" si le nom ne suit aucun modèle connu."""
    if not ancien.lower().endswith(".pdf"):
        return ""
    base = ancien[:-4]
    morceaux = base.split("_")
    if base.startswith("FACT-") and len(morceaux) == 3:
        numero = "F" + morceaux[0][5:]
        fournisseur = morceaux[1]
        date = morceaux[2]
    elif base.startswith("F") and base[6:7] == " " and len(base) > 17:
        numero = base[:6]
        fournisseur = base[7:-11]
        date = base[-10:]
    else:
        return ""
    return date_iso(date) + "_" + nettoyer(fournisseur) + "_" + numero + ".pdf"


def compter_fichiers(dossier):
    """Renvoie le nombre de fichiers dans le dossier et tous ses sous-dossiers."""
    nombre = 0
    for chemin in dossier.rglob("*"):
        if chemin.is_file():
            nombre = nombre + 1
    return nombre


def lire_dossiers(chemin):
    """Lit le fichier Excel des fournisseurs et renvoie le dictionnaire « nom dans les factures » -> dossier."""
    classeur = openpyxl.load_workbook(chemin, read_only=True, data_only=True)
    dossiers = {}
    for nom, dossier, code in classeur["Fournisseurs"].iter_rows(min_row=2, values_only=True):
        dossiers[nom] = dossier
    classeur.close()
    return dossiers


def ranger(dossiers):
    """Renomme et range les factures reconnues ; renvoie (nombre rangé, liste à traiter à la main)."""
    a_traiter = []
    prevus = []
    for fichier in sorted(FACTURES.iterdir()):
        nom = nouveau_nom(fichier.name)
        if nom == "":
            a_traiter.append(fichier.name)
        elif nom.split("_")[1] not in dossiers:
            a_traiter.append(fichier.name + " (fournisseur absent du fichier Excel)")
        else:
            destination = RANGEES / dossiers[nom.split("_")[1]] / nom
            if destination.exists() or destination in prevus:
                a_traiter.append(fichier.name + " (conflit : " + nom + " existe déjà)")
            elif SIMULATION:
                prevus.append(destination)
            else:
                destination.parent.mkdir(parents=True, exist_ok=True)
                fichier.rename(destination)
                prevus.append(destination)
    return len(prevus), a_traiter


def totaliser():
    """Renvoie le dictionnaire des totaux par fournisseur et le total des relevés."""
    totaux = {}
    total = 0
    for releve in sorted(RELEVES.glob("*.csv")):
        with open(releve, encoding="utf-8-sig", newline="") as fichier:
            for ligne in csv.DictReader(fichier, delimiter=";"):
                montant = float(ligne["montant"].replace(",", "."))
                totaux[ligne["fournisseur"]] = totaux.get(ligne["fournisseur"], 0) + montant
                total = total + montant
    return totaux, round(total, 2)


def ecrire_totaux(totaux, chemin):
    """Écrit les totaux dans un CSV pour le tableur, sans jamais écraser un fichier existant."""
    if chemin.exists():
        print(chemin, "existe déjà : je ne l'écrase pas.")
    elif SIMULATION:
        print("j'écrirais", chemin, "avec", len(totaux), "fournisseurs")
    else:
        SORTIES.mkdir(exist_ok=True)
        with open(chemin, "w", encoding="utf-8-sig", newline="") as sortie:
            ecrivain = csv.writer(sortie, delimiter=";")
            ecrivain.writerow(["fournisseur", "total"])
            for fournisseur in sorted(totaux):
                ecrivain.writerow([fournisseur, f"{totaux[fournisseur]:.2f}".replace(".", ",")])
        print("Écrit :", chemin)


if not FACTURES.exists() or not FOURNISSEURS.exists():
    print("Dossier ou fichier des fournisseurs introuvable dans", DOSSIER, "- rien n'est fait.")
else:
    avant = compter_fichiers(DOSSIER)
    print("=== Fin de mois - simulation :", SIMULATION, "-", avant, "fichiers avant ===")
    dossiers = lire_dossiers(FOURNISSEURS)
    nombre, a_traiter = ranger(dossiers)
    if SIMULATION:
        print(nombre, "factures à ranger dans", len(dossiers), "dossiers fournisseurs")
    else:
        print(nombre, "factures rangées dans", len(dossiers), "dossiers fournisseurs")
    print(len(a_traiter), "à traiter à la main :")
    for nom in a_traiter:
        print("  ", nom)
    totaux, total = totaliser()
    for fournisseur in sorted(totaux):
        print(f"  {fournisseur:<20}{totaux[fournisseur]:>10.2f} €")
    print(f"  {'Total des relevés':<20}{total:>10.2f} €  (contrôle : {round(sum(totaux.values()), 2) == total})")
    ecrire_totaux(totaux, SORTIES / "totaux-fournisseurs.csv")
    apres = compter_fichiers(DOSSIER)
    if apres == avant:
        print(apres, "fichiers après : aucun fichier perdu")
    elif apres == avant + 1:
        print(apres, "fichiers après : le fichier des totaux en plus, aucun fichier perdu")
    else:
        print(apres, "fichiers après : ATTENTION, écart !")

Simulation (sortie réelle) :

Ce que vous voyez
=== Fin de mois - simulation : True - 44 fichiers avant === 34 factures à ranger dans 6 dossiers fournisseurs 6 à traiter à la main : FACT-00412_Papeterie Martin_01-10-2026.pdf (conflit : 2026-10-01_Papeterie-Martin_F00412.pdf existe déjà) FACT-00450_Bureau Express.pdf Scan_0042.pdf Scan_0043.pdf Scan_0044.pdf Scan_0045.pdf Bureau Express 2012.30 € Café du Coin 2112.40 € Imprimerie Leroy 3165.50 € Nettoyage Pro 1904.68 € Papeterie Martin 2097.10 € Transports Rapides 3184.00 € Total des relevés 14475.98 € (contrôle : True) j'écrirais entrainement/sorties/totaux-fournisseurs.csv avec 6 fournisseurs 44 fichiers après : aucun fichier perdu

Action (SIMULATION = False, sortie réelle, fin) : 34 factures rangées dans 6 dossiers fournisseurs, la même liste et les mêmes totaux, Écrit : entrainement/sorties/totaux-fournisseurs.csv, puis 45 fichiers après : le fichier des totaux en plus, aucun fichier perdu. Relancé une seconde fois : 0 factures rangées, le fichier des totaux n'est pas écrasé, 45 fichiers après : aucun fichier perdu.

Comment il est construit. Chaque tâche est une fonction (semaine 2) ; le programme principal, en bas, se lit comme une liste d'étapes. Le dossier de rangement vient du fichier Excel (semaine 4) : un fournisseur absent du fichier est mis de côté. La fonction ranger renvoie deux valeurs dans un tuple. Les réglages sont des constantes en haut. Et tout ce qui agit (renommer, écrire) passe par SIMULATION, exists() et le comptage (semaine 3).

Ce que le script ne fait pas (encore). Si un fichier est illisible ou si le fichier Excel est ouvert par quelqu'un d'autre, il s'arrête sur un message d'erreur au lieu de continuer : faire réagir le programme aux erreurs, c'est le Master. Il n'a pas de mode d'emploi pour Karim, ni de vérifications automatiques : c'est aussi le Master.

Boîte à outils interactive · Enregistrée dans ce navigateur
[3]Carrousel final

🎨 Carrousel final — Pro

[4]Journal

✍️ Journal de bord — mot de la fin

Qu'est-ce que votre script fait aujourd'hui que vous faisiez à la main il y a quatre semaines ? Combien de temps vous fera-t-il gagner chaque mois ?

✦ AUTOMATISATION SEREINE · SEMAINE 4 ✦

Bravo ! Vous avez terminé le Pro. Vous savez automatiser une tâche répétitive de bout en bout : lire des fichiers, les ranger sans rien risquer, produire un récapitulatif vérifié, et vous faire aider sans perdre le contrôle.

[5]Certificat

🎓 Votre certificat

Le certificat s'affiche lorsque les 20 épisodes sont marqués comme terminés.

COLLECTION « CHALLENGES » · APPRENDRE À CODER EN PYTHON

Certificat de réussite

Ce certificat atteste que

—

a terminé les épisodes du Pro — Automatiser mon quotidien et atteint le niveau Automatisation sereine.

Ce que ce parcours couvre : boucles for et while, texte et f-strings, méthode de débogage, listes, fonctions et vérifications, code lisible, modules datetime et pathlib, fichiers texte et CSV, renommage et rangement de fichiers (copie, simulation, action, vérification), dictionnaires, tuples et ensembles, installation d'une bibliothèque (openpyxl) dans un environnement virtuel, aide d'une IA vérifiée.

[6]Sources
  1. Sorties du script de fin de mois (simulation, action, second lancement) : exécutions réelles sous Python 3.12.13, 3.15.0 et 3.15.2 avec openpyxl 3.1.5, chacune sur un dossier d'entraînement neuf (journaux de test de ce livre), le 31/10/2026.
  2. Python Software Foundation, Tutoriel Python — 8. Erreurs et exceptions (pour la suite : faire réagir un programme aux erreurs), consulté le 31/10/2026.
Annexe · Kit de rattrapage et glossaire
ARattrapage

Vous démarrez le Pro sans avoir suivi le Boost ? Voici, en une ligne chacune, les notions supposées acquises.

  • Algorithme et programme — une procédure suivie à la main devient une suite d'étapes, puis quelques lignes de Python exécutées de haut en bas (Boost, épisode 1).
  • Variables, types et calculs — = range une valeur ; int, float, str, bool ; + - * / // % ** et leurs priorités ; round pour les centimes (Boost, épisode 1).
  • Conversions — input renvoie toujours du texte ; float(input(...)) ou int(input(...)) pour un nombre ; Python attend un point décimal (Boost, épisodes 1 et 2 — Boost, épisode 2).
  • Lancer un script — un environnement (Basthon dans le navigateur, Thonny sur l'ordinateur), la console pour essayer, le script pour enregistrer ; print et input (Boost, épisode 2).
  • Lire une erreur en 3 questions — quelle ligne ? quel type (dernière ligne) ? qu'est-ce qui ne va pas à cet endroit ? (Boost, épisode 2).
  • Décider — if / elif / else testés dans l'ordre, le cas le plus précis en premier ; and, or, not ; le retrait de 4 espaces (Boost, épisode 3).
  • Construire pas à pas — une étape, un test, un cas calculé à la main (Boost, épisode 4).
  • Tester comme un collègue — saisies hors limites signalées par une condition ; une virgule au lieu du point provoque une ValueError qu'on sait lire (Boost, épisode 5).
BGlossaire
Boucle
instruction qui répète un bloc de code, pour chaque valeur d'une suite (for) ou tant qu'une condition est vraie (while).
Itération
un tour de boucle.
Accumulateur
variable créée avant une boucle et mise à jour à chaque tour (un total, un compteur).
Boucle infinie
boucle dont la condition reste toujours vraie ; on l'arrête avec le bouton Stop ou Ctrl + C.
Chaîne de caractères (str)
un texte, suite de caractères numérotés à partir de 0, qu'on ne peut pas modifier sur place.
Méthode
outil attaché à une valeur, appelé avec un point (texte.strip(), liste.append(…)).
Tranche
morceau d'une chaîne ou d'une liste, noté [début:fin], la fin étant exclue.
f-string
texte précédé de f, où ce qui est entre accolades est remplacé par sa valeur, éventuellement mise en forme (:.2f, :<20).
Bug silencieux
erreur qui ne provoque aucun message mais donne un résultat faux.
Débogueur
outil qui exécute un programme pas à pas et montre la valeur de ses variables.
Liste
suite ordonnée de valeurs, entre crochets, qu'on peut modifier (append, modification par indice).
Indice
numéro d'un élément dans une liste ou d'un caractère dans une chaîne ; le premier est 0, le dernier -1.
Fonction
morceau de code nommé, défini avec def, qui reçoit des valeurs et peut en renvoyer une.
Paramètre
nom, dans la définition d'une fonction, qui reçoit une valeur à l'appel.
Argument
valeur donnée à une fonction au moment de l'appel.
Valeur renvoyée
résultat transmis par return ; None s'il n'y a pas de return.
Portée
partie du programme où une variable existe ; une variable créée dans une fonction est locale.
Docstring
texte entre triples guillemets, sous le def ou en tête de fichier, qui dit ce que fait le code.
Constante
variable de réglage, écrite en majuscules en haut du script, qu'on ne modifie pas pendant le programme (SIMULATION).
Module
fichier de code Python qu'on charge avec import.
Bibliothèque standard
l'ensemble des modules fournis avec Python (datetime, pathlib, csv…).
Chemin
adresse d'un fichier ou d'un dossier, manipulée avec pathlib.Path.
Dossier
emplacement qui contient des fichiers et d'autres dossiers (on dit aussi répertoire).
Extension
fin du nom d'un fichier, après le dernier point (.pdf, .csv), qui indique son format.
Encodage
façon de traduire les caractères en octets dans un fichier ; on précise toujours encoding= (UTF-8, utf-8-sig pour les CSV de tableur).
CSV
fichier texte de tableau, une ligne par rangée, des valeurs séparées par un séparateur.
Séparateur
caractère qui sépare les colonnes d'un CSV (; dans les tableurs réglés en français, , ailleurs).
Simulation
mode d'un script qui affiche ce qu'il ferait, sans rien modifier.
Dictionnaire
ensemble de paires clé-valeur, entre accolades, pour retrouver une valeur par son nom.
Clé
nom qui donne accès à une valeur dans un dictionnaire ; elle est unique.
Tuple
suite ordonnée de valeurs, entre parenthèses, qu'on ne modifie pas.
Ensemble (set)
collection de valeurs distinctes, sans ordre ni doublon.
Paquet
bibliothèque qu'on installe depuis PyPI avec pip (openpyxl).
PyPI
Python Package Index, le catalogue officiel des paquets Python (pypi.org).
Environnement virtuel
dossier (souvent .venv) qui contient un Python et les paquets propres à un projet.
Typosquatting
publication d'un paquet malveillant sous un nom proche d'un paquet connu, pour piéger une faute de frappe.
Assistant d'IA
service qui génère du texte ou du code à partir d'une demande ; ses réponses sont plausibles, pas vérifiées.