Tutoriels gratuits et ouverts à toutes et à tous · conçus au Lycée Chevrollier — AngersÀ propos

Série « Parcours Python » · partie 8 / 8

Python niveau 7 — Projet final et ouvertures

Mener un projet complet, découvrir les bibliothèques et préparer la suite : niveau 7 du parcours Python, avec exercices corrigés et QCM.

Développement✎ B. Jousseaume📅 17 octobre 2026⏱ 8 à 10 h📶 Avancé

1. Les modules de la bibliothèque standard

Python est livré avec des centaines de modules prêts à l’emploi. Quelques-uns très utiles :

ModuleUtilitéExemple
mathFonctions mathématiquesmath.sqrt(16), math.pi
randomHasardrandom.shuffle(liste)
datetimeDates et heuresdatetime.now()
timePauses, chronomètretime.sleep(1)
os, pathlibFichiers et dossiersPath("data").mkdir()
turtleDessin (idéal pour s’amuser)turtle.forward(100)
hashlib, secretsSécurité : empreintes, aléatoire sûrsecrets.token_hex(8)
from math import sqrt, pi          # importer seulement certaines fonctions
import datetime as dt              # donner un nom court au module

print(sqrt(2))
print(f"Aire d'un disque de rayon 3 : {pi * 3 ** 2:.2f}")
print(dt.date.today())

Dessiner avec turtle

import turtle

t = turtle.Turtle()
t.speed(0)
for i in range(36):
    for cote in range(4):          # un carré
        t.forward(100)
        t.left(90)
    t.left(10)                     # on tourne un peu avant le suivant
turtle.done()

2. Les bibliothèques externes et pip

Au-delà de la bibliothèque standard, des centaines de milliers de bibliothèques sont publiées sur PyPI (pypi.org). On les installe avec pip. Dans Thonny, le plus simple est le menu Outils → Gérer les paquets. En ligne de commande :

pip install requests       # requêtes web
pip install matplotlib     # graphiques

3. Les empreintes (hash) : stocker un mot de passe correctement

Une fonction de hachage transforme un texte en une empreinte de longueur fixe. Il est impossible de retrouver le texte d’origine à partir de l’empreinte, mais le même texte donne toujours la même empreinte. On peut donc vérifier un mot de passe sans le stocker.

import hashlib, secrets

def hacher(mdp, sel=None):
    """Renvoie (sel, empreinte). Le sel rend chaque empreinte unique."""
    if sel is None:
        sel = secrets.token_hex(16)
    empreinte = hashlib.pbkdf2_hmac("sha256", mdp.encode(), sel.encode(), 200_000)
    return sel, empreinte.hex()

def verifier(mdp, sel, empreinte_attendue):
    _, empreinte = hacher(mdp, sel)
    return secrets.compare_digest(empreinte, empreinte_attendue)

sel, h = hacher("MaPhrase-De-Passe-2026!")
print(verifier("MaPhrase-De-Passe-2026!", sel, h))   # True
print(verifier("mauvais", sel, h))                   # False

Note aussi secrets au lieu de random : random est prévisible et convient aux jeux, secrets est conçu pour générer des codes et jetons sûrs.

4. Découvrir la programmation orientée objet

Jusqu’ici, les données (dictionnaires) et les traitements (fonctions) étaient séparés. Une classe regroupe les deux : c’est un « plan de fabrication » d’objets qui ont des attributs (données) et des méthodes (fonctions qui leur sont propres).

class CompteBancaire:
    def __init__(self, titulaire, solde=0):     # constructeur
        self.titulaire = titulaire              # attributs
        self.solde = solde

    def deposer(self, montant):                 # méthode
        if montant <= 0:
            raise ValueError("Montant invalide")
        self.solde += montant

    def retirer(self, montant):
        if montant > self.solde:
            return False
        self.solde -= montant
        return True

    def __str__(self):                          # affichage avec print
        return f"{self.titulaire} : {self.solde:.2f} €"

compte = CompteBancaire("Léa", 100)             # création d'un objet
compte.deposer(50)
compte.retirer(30)
print(compte)                                   # Léa : 120.00 €

self désigne l’objet lui-même. raise lève une exception volontairement, pour signaler une utilisation incorrecte. La POO est un vaste sujet : cet aperçu te permettra de reconnaître des classes dans le code des autres et de t’y former ensuite.

5. Méthode de projet

Un programme de plus de quelques dizaines de lignes ne s’écrit pas d’un bloc. Voici la démarche des professionnels, à appliquer à ton projet final :

  1. Cahier des charges : écris en français ce que le programme doit faire, et ce qu’il ne fera pas.
  2. Données : quelles informations faut-il mémoriser ? Sous quelle forme (liste, dictionnaire, fichier) ?
  3. Découpage : liste les fonctions nécessaires, chacune avec une seule responsabilité.
  4. Développement incrémental : programme une fonction, teste-la, puis passe à la suivante. Le programme doit fonctionner à chaque étape.
  5. Tests : des assert pour les fonctions de calcul, des essais « méchants » pour les saisies (texte vide, lettres, nombres négatifs…).
  6. Relecture : noms clairs, commentaires utiles, pas de code dupliqué.

6. Projet final : le jeu du pendu

Découpage proposé

FonctionRôle
charger_mots(chemin)Renvoie la liste des mots du fichier (liste par défaut si fichier absent)
sans_accent(texte)Remplace é, è, ê, à, ç… par la lettre simple
masquer(mot, lettres_trouvees)Renvoie le mot avec des _ pour les lettres non trouvées
demander_lettre(deja_proposees)Saisie contrôlée d’une lettre nouvelle
jouer_partie()Déroule une partie, renvoie le mot et le nombre d’erreurs
charger_scores() / enregistrer_score()Lecture et écriture du fichier JSON
afficher_top5()Trie les scores et affiche les 5 meilleurs
main()Menu : jouer, voir les scores, quitter

7. Et après ?

Tu as maintenant les bases solides de la programmation. Selon tes envies :

DirectionPour faire quoiÀ explorer
JeuxJeux 2D, animationspygame
WebSites et applications webFlask, puis HTML/CSS
DonnéesTableaux, statistiques, graphiquespandas, matplotlib
AutomatisationRenommer des fichiers, traiter des Excel, des PDFpathlib, openpyxl
CybersécuritéComprendre les attaques pour s’en défendrePlateformes légales type Root-Me, TryHackMe
ÉlectroniqueCapteurs, robots, domotiqueRaspberry Pi, micro:bit, MicroPython

8. Bilan final

Exercices corrigés

Pour le projet final, ta version peut être très différente de celle-ci : c’est normal et même souhaitable. Compare surtout le découpage et la robustesse, puis pioche dans le corrigé les idées qui t’intéressent.

Exercice 7.1 — Créations

Voir le corrigé
import turtle
from datetime import datetime

def polygone(t, nb_cotes, longueur):
    for i in range(nb_cotes):
        t.forward(longueur)
        t.left(360 / nb_cotes)

t = turtle.Turtle()
for n in (3, 6, 8):
    polygone(t, n, 80)
turtle.done()

saisie = input("Date de naissance (jj/mm/aaaa) : ")
naissance = datetime.strptime(saisie, "%d/%m/%Y")
print(f"Tu as vécu {(datetime.now() - naissance).days} jours.")

Explication

L’angle extérieur d’un polygone régulier vaut 360 / nb_cotes : c’est de combien la tortue doit tourner à chaque sommet. strptime transforme un texte en date selon un format ; la soustraction de deux dates donne une durée dont .days est le nombre de jours.

Exercice 7.2 — Ma première classe

Voir le corrigé
class Livre:
    def __init__(self, titre, auteur):
        self.titre = titre
        self.auteur = auteur
        self.emprunte = False

    def emprunter(self):
        if self.emprunte:
            return False
        self.emprunte = True
        return True

    def rendre(self):
        self.emprunte = False

biblio = [Livre("Germinal", "Zola"), Livre("Candide", "Voltaire"), Livre("Nana", "Zola")]
biblio[0].emprunter()
for livre in biblio:
    if not livre.emprunte:
        print(f"Disponible : {livre.titre} ({livre.auteur})")

Explication

__init__ initialise chaque nouvel objet ; self permet à chaque livre d’avoir ses propres valeurs. emprunter() renvoie un booléen pour que le programme sache si l’opération a réussi.

Pour aller plus loin

    def __str__(self):
        etat = "emprunté" if self.emprunte else "disponible"
        return f"{self.titre} ({self.auteur}) - {etat}"

Avec cette méthode ajoutée à la classe, print(livre) affiche directement une description lisible.

Projet final — Le pendu

Voir le corrigé
import random, json
from datetime import datetime

VIES = 7
FICHIER_SCORES = "scores.json"
ACCENTS = {"é": "e", "è": "e", "ê": "e", "ë": "e", "à": "a", "â": "a",
           "î": "i", "ï": "i", "ô": "o", "ù": "u", "û": "u", "ç": "c"}

def sans_accent(texte):
    return "".join(ACCENTS.get(c, c) for c in texte.lower())

def charger_mots(chemin="mots.txt"):
    try:
        with open(chemin, encoding="utf-8") as f:
            mots = [l.strip().lower() for l in f if l.strip().isalpha()]
        if mots:
            return mots
    except FileNotFoundError:
        pass
    return ["python", "ordinateur", "variable", "fonction", "élève"]

def masquer(mot, trouvees):
    return " ".join(c if sans_accent(c) in trouvees else "_" for c in mot)

def demander_lettre(deja):
    while True:
        l = sans_accent(input("Lettre : ").strip())
        if len(l) != 1 or not l.isalpha():
            print("Une seule lettre, s'il te plaît.")
        elif l in deja:
            print("Déjà proposée.")
        else:
            return l
def jouer_partie():
    mot = random.choice(charger_mots())
    lettres_mot = set(sans_accent(mot))
    trouvees, ratees = set(), set()
    while len(ratees) < VIES and not lettres_mot <= trouvees:
        print(f"\n{masquer(mot, trouvees)}   vies : {VIES - len(ratees)}")
        l = demander_lettre(trouvees | ratees)
        if l in lettres_mot:
            trouvees.add(l)
        else:
            ratees.add(l)
    gagne = lettres_mot <= trouvees
    print(("Gagné ! " if gagne else "Perdu... ") + f"Le mot était : {mot}")
    return mot, len(ratees), gagne

def charger_scores():
    try:
        with open(FICHIER_SCORES, encoding="utf-8") as f:
            donnees = json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return []
    # on ne garde que les scores bien formés
    return [s for s in donnees if isinstance(s, dict)
            and isinstance(s.get("erreurs"), int) and isinstance(s.get("nom"), str)]

def enregistrer_score(nom, mot, erreurs):
    scores = charger_scores()
    scores.append({"nom": nom, "mot": mot, "erreurs": erreurs,
                   "date": datetime.now().strftime("%d/%m/%Y")})
    with open(FICHIER_SCORES, "w", encoding="utf-8") as f:
        json.dump(scores, f, indent=2, ensure_ascii=False)

def afficher_top5():
    scores = sorted(charger_scores(), key=lambda s: s["erreurs"])
    for i, s in enumerate(scores[:5], 1):
        print(f"{i}. {s['nom'][:20]:<20} {s['erreurs']} erreur(s)  {s.get('date', '')}")

def main():
    while True:
        choix = input("\n1 Jouer  2 Top 5  3 Quitter > ")
        if choix == "1":
            mot, erreurs, gagne = jouer_partie()
            if gagne:
                nom = input("Ton pseudo : ").strip()[:20] or "Anonyme"
                enregistrer_score(nom, mot, erreurs)
        elif choix == "2":
            afficher_top5()
        elif choix == "3":
            break

main()

Nouveautés utilisées : les ensembles (set, collection sans doublons, <= teste l’inclusion), les expressions génératrices dans join et lambda (petite fonction anonyme utilisée comme critère de tri). Chacune se découvre facilement dans la documentation : c’est ta première occasion d’apprendre en autonomie.

Explication

Les ensembles (set) simplifient beaucoup le jeu : lettres_mot <= trouvees vérifie que toutes les lettres du mot ont été trouvées, et trouvees | ratees réunit toutes les lettres déjà proposées. Chaque fonction est testable séparément, par exemple masquer("chat", {"a"}) doit donner _ _ a _.

Pour aller plus loin

# Tests unitaires à placer avant main()
assert sans_accent("Élève") == "eleve"
assert masquer("chat", {"a"}) == "_ _ a _"
assert masquer("été", {"e"}) == "é _ é"

🧠 Testez vos connaissances

5 questions. Répondez puis cliquez sur « Corriger ». Rien n’est enregistré : c’est de l’auto-évaluation.

1. Qu’est-ce que le typosquatting sur PyPI ?

💬 Installer un paquet, c’est exécuter le code d’un inconnu : vérifie toujours l’orthographe exacte et la popularité du paquet.

2. Pourquoi stocke-t-on l’empreinte (hash) d’un mot de passe plutôt que le mot de passe ?

💬 Si la base fuit, les mots de passe ne sont pas directement exposés.

3. À quoi sert le « sel » ajouté avant le hachage ?

💬 Cela empêche l’usage de tables d’empreintes pré-calculées.

4. Dans une classe, à quoi sert self ?

💬 Grâce à self, chaque objet a ses propres attributs.

5. Tester la sécurité d’un système sans autorisation, c’est…

💬 On s’entraîne uniquement sur ses propres machines ou sur des plateformes prévues pour cela.

© 2026 Bruno Jousseaume. Ce cours est publié sous licence CC BY-NC-SA 4.0 : vous pouvez le partager et l’adapter en citant l’auteur et un lien vers cette page, sans usage commercial, et en partageant vos adaptations sous la même licence.

Pour aller plus loin