29  TP récapitulatif — Gestion d’une bibliothèque

Ce Travail Pratique mobilise toutes les notions du niveau Opérationnel : fonctions, docstrings, f-strings, itération avancée, références/mutabilité, logique booléenne, modules, algorithmes (tri/recherche), extraction de données.

🎯 Objectifs pédagogiques
  • Construire un programme complet de plusieurs dizaines de lignes.
  • Organiser le code en fonctions réutilisables, documentées.
  • Manipuler des listes de dictionnaires (structure réaliste de données).
  • Appliquer les 4 opérations : filtrer, trier, transformer, agréger.
  • Gérer des cas d’erreur (livre absent, déjà emprunté, etc.).

Temps estimé : 3 h.

29.1 Contexte

Vous travaillez pour une petite bibliothèque municipale. Le catalogue est représenté sous la forme d’une liste de dictionnaires :

catalogue = [
    {"id": 1, "titre": "Le Petit Prince",       "auteur": "Saint-Exupéry", "annee": 1943, "genre": "Conte",   "disponible": True},
    {"id": 2, "titre": "1984",                  "auteur": "Orwell",        "annee": 1949, "genre": "Roman",   "disponible": False},
    {"id": 3, "titre": "L'Étranger",            "auteur": "Camus",         "annee": 1942, "genre": "Roman",   "disponible": True},
    {"id": 4, "titre": "Les Misérables",        "auteur": "Hugo",          "annee": 1862, "genre": "Roman",   "disponible": True},
    {"id": 5, "titre": "Fahrenheit 451",        "auteur": "Bradbury",      "annee": 1953, "genre": "SF",      "disponible": True},
    {"id": 6, "titre": "Le Nom de la Rose",     "auteur": "Eco",           "annee": 1980, "genre": "Roman",   "disponible": False},
    {"id": 7, "titre": "Dune",                  "auteur": "Herbert",       "annee": 1965, "genre": "SF",      "disponible": True},
    {"id": 8, "titre": "Voyage au bout",        "auteur": "Céline",        "annee": 1932, "genre": "Roman",   "disponible": True},
    {"id": 9, "titre": "Le Seigneur des anneaux","auteur": "Tolkien",      "annee": 1954, "genre": "Fantasy", "disponible": True},
    {"id": 10,"titre": "Le Meilleur des mondes","auteur": "Huxley",        "annee": 1932, "genre": "SF",      "disponible": False},
]

Vous allez écrire progressivement un programme qui gère ce catalogue.

Conseil méthodologique
  1. Créez un fichier .py pour travailler, avec le catalogue en tête.
  2. Écrivez une fonction par question — nommez-les clairement.
  3. Documentez chaque fonction avec une docstring.
  4. Testez au fur et à mesure : n’écrivez pas 50 lignes avant de vérifier.
  5. Réutilisez les fonctions précédentes dans les suivantes.

Partie 1 — Fonctions de base sur le catalogue

Questions

  1. afficher_livre(livre) — affiche un livre dans un format lisible :

    "[#5] Fahrenheit 451 — Bradbury (1953) [Disponible]"

  2. afficher_catalogue(catalogue) — affiche tous les livres du catalogue, un par ligne.

  3. chercher_par_id(catalogue, id_livre) — renvoie le livre correspondant, ou None si non trouvé.

  4. chercher_par_titre(catalogue, fragment) — renvoie la liste des livres dont le titre contient fragment (insensible à la casse).

catalogue = [
    {"id": 1, "titre": "Le Petit Prince",       "auteur": "Saint-Exupéry", "annee": 1943, "genre": "Conte",   "disponible": True},
    {"id": 2, "titre": "1984",                  "auteur": "Orwell",        "annee": 1949, "genre": "Roman",   "disponible": False},
    {"id": 3, "titre": "L'Étranger",            "auteur": "Camus",         "annee": 1942, "genre": "Roman",   "disponible": True},
    {"id": 5, "titre": "Fahrenheit 451",        "auteur": "Bradbury",      "annee": 1953, "genre": "SF",      "disponible": True},
]

def afficher_livre(livre):
    """Affiche un livre dans un format lisible."""
    statut = "Disponible" if livre["disponible"] else "Emprunté"
    print(f"[#{livre['id']}] {livre['titre']}{livre['auteur']} ({livre['annee']}) [{statut}]")


def afficher_catalogue(catalogue):
    """Affiche tous les livres du catalogue, un par ligne."""
    for livre in catalogue:
        afficher_livre(livre)


def chercher_par_id(catalogue, id_livre):
    """Renvoie le livre correspondant à l'id, ou None si absent."""
    for livre in catalogue:
        if livre["id"] == id_livre:
            return livre
    return None


def chercher_par_titre(catalogue, fragment):
    """Renvoie la liste des livres dont le titre contient le fragment."""
    fragment = fragment.lower()
    resultats = []
    for livre in catalogue:
        if fragment in livre["titre"].lower():
            resultats.append(livre)
    return resultats


# Tests
print("=== Catalogue ===")
afficher_catalogue(catalogue)

print("\n=== Livre #3 ===")
livre = chercher_par_id(catalogue, 3)
afficher_livre(livre)

print("\n=== Livre #99 ===")
print(chercher_par_id(catalogue, 99))

print('\n=== Titres contenant "le" ===')
for livre in chercher_par_titre(catalogue, "le"):
    afficher_livre(livre)
=== Catalogue ===
[#1] Le Petit Prince — Saint-Exupéry (1943) [Disponible]
[#2] 1984 — Orwell (1949) [Emprunté]
[#3] L'Étranger — Camus (1942) [Disponible]
[#5] Fahrenheit 451 — Bradbury (1953) [Disponible]

=== Livre #3 ===
[#3] L'Étranger — Camus (1942) [Disponible]

=== Livre #99 ===
None

=== Titres contenant "le" ===
[#1] Le Petit Prince — Saint-Exupéry (1943) [Disponible]

Points à retenir

  • Factorisation : afficher_catalogue appelle afficher_livre dans une boucle.
  • Retour None explicite quand un élément n’est pas trouvé — idiomatique.
  • Recherche insensible à la casse : on .lower() les deux côtés.
  • Pour les tests, on passe chercher_par_id(..., 99) pour vérifier le cas absent.

Partie 2 — Statistiques sur le catalogue

Questions

  1. nb_livres(catalogue) — nombre total de livres.

  2. nb_disponibles(catalogue) — nombre de livres disponibles.

  3. pourcentage_disponibilite(catalogue) — pourcentage de livres disponibles, arrondi à 1 décimale.

  4. livre_plus_ancien(catalogue) — le livre le plus ancien du catalogue.

  5. livre_plus_recent(catalogue) — le plus récent.

  6. annee_moyenne(catalogue) — année moyenne de publication (arrondie à l’entier).

catalogue = [
    {"id": 1, "titre": "Le Petit Prince",       "auteur": "Saint-Exupéry", "annee": 1943, "genre": "Conte",   "disponible": True},
    {"id": 2, "titre": "1984",                  "auteur": "Orwell",        "annee": 1949, "genre": "Roman",   "disponible": False},
    {"id": 3, "titre": "L'Étranger",            "auteur": "Camus",         "annee": 1942, "genre": "Roman",   "disponible": True},
    {"id": 4, "titre": "Les Misérables",        "auteur": "Hugo",          "annee": 1862, "genre": "Roman",   "disponible": True},
    {"id": 5, "titre": "Fahrenheit 451",        "auteur": "Bradbury",      "annee": 1953, "genre": "SF",      "disponible": True},
    {"id": 7, "titre": "Dune",                  "auteur": "Herbert",       "annee": 1965, "genre": "SF",      "disponible": True},
    {"id": 10,"titre": "Le Meilleur des mondes","auteur": "Huxley",        "annee": 1932, "genre": "SF",      "disponible": False},
]

def nb_livres(catalogue):
    """Nombre total de livres."""
    return len(catalogue)


def nb_disponibles(catalogue):
    """Nombre de livres actuellement disponibles."""
    return sum(livre["disponible"] for livre in catalogue)


def pourcentage_disponibilite(catalogue):
    """Pourcentage de livres disponibles, arrondi à 1 décimale."""
    if not catalogue:
        return 0.0
    return round(nb_disponibles(catalogue) / nb_livres(catalogue) * 100, 1)


def livre_plus_ancien(catalogue):
    """Le livre le plus ancien."""
    return min(catalogue, key=lambda l: l["annee"])


def livre_plus_recent(catalogue):
    """Le livre le plus récent."""
    return max(catalogue, key=lambda l: l["annee"])


def annee_moyenne(catalogue):
    """Année moyenne de publication (entier)."""
    return round(sum(livre["annee"] for livre in catalogue) / len(catalogue))


# Tests
print(f"Nombre de livres : {nb_livres(catalogue)}")
print(f"Disponibles : {nb_disponibles(catalogue)}")
print(f"Disponibilité : {pourcentage_disponibilite(catalogue)} %")

print(f"\nPlus ancien : {livre_plus_ancien(catalogue)['titre']} ({livre_plus_ancien(catalogue)['annee']})")
print(f"Plus récent : {livre_plus_recent(catalogue)['titre']} ({livre_plus_recent(catalogue)['annee']})")
print(f"Année moyenne : {annee_moyenne(catalogue)}")
Nombre de livres : 7
Disponibles : 5
Disponibilité : 71.4 %

Plus ancien : Les Misérables (1862)
Plus récent : Dune (1965)
Année moyenne : 1935

Points à retenir

  • Comptage de booléens : sum(livre["disponible"] for livre in catalogue) — astuce True = 1.
  • min/max avec key= : cherche l’élément (le livre), pas juste la valeur.
  • Le pourcentage gère le cas de liste vide pour éviter une division par zéro.

Partie 3 — Gestion des emprunts

Questions

  1. emprunter(catalogue, id_livre) — marque un livre comme emprunté (disponible = False). Gérer les cas :

    • Livre introuvable : afficher "Livre introuvable" et ne rien modifier.
    • Livre déjà emprunté : afficher "Livre déjà emprunté" et ne rien modifier.
    • Sinon : marquer comme emprunté et afficher "Emprunt enregistré".
  2. rendre(catalogue, id_livre) — inverse : rend le livre disponible, avec messages symétriques.

  3. liste_empruntes(catalogue) — renvoie la liste des livres non disponibles.

  4. liste_disponibles_par_genre(catalogue, genre) — renvoie la liste des livres disponibles d’un certain genre.

catalogue = [
    {"id": 1, "titre": "Le Petit Prince",       "auteur": "Saint-Exupéry", "annee": 1943, "genre": "Conte",   "disponible": True},
    {"id": 2, "titre": "1984",                  "auteur": "Orwell",        "annee": 1949, "genre": "Roman",   "disponible": False},
    {"id": 5, "titre": "Fahrenheit 451",        "auteur": "Bradbury",      "annee": 1953, "genre": "SF",      "disponible": True},
    {"id": 7, "titre": "Dune",                  "auteur": "Herbert",       "annee": 1965, "genre": "SF",      "disponible": True},
]

def chercher_par_id(catalogue, id_livre):
    for livre in catalogue:
        if livre["id"] == id_livre:
            return livre
    return None


def emprunter(catalogue, id_livre):
    """Marque le livre comme emprunté."""
    livre = chercher_par_id(catalogue, id_livre)
    if livre is None:
        print("Livre introuvable")
        return
    if not livre["disponible"]:
        print("Livre déjà emprunté")
        return
    livre["disponible"] = False       # on modifie le dict → impacte le catalogue
    print(f"Emprunt enregistré : {livre['titre']}")


def rendre(catalogue, id_livre):
    """Marque le livre comme rendu (à nouveau disponible)."""
    livre = chercher_par_id(catalogue, id_livre)
    if livre is None:
        print("Livre introuvable")
        return
    if livre["disponible"]:
        print("Livre déjà disponible")
        return
    livre["disponible"] = True
    print(f"Retour enregistré : {livre['titre']}")


def liste_empruntes(catalogue):
    """Liste des livres actuellement empruntés."""
    return [livre for livre in catalogue if not livre["disponible"]]


def liste_disponibles_par_genre(catalogue, genre):
    """Livres disponibles d'un genre donné."""
    return [livre for livre in catalogue if livre["disponible"] and livre["genre"] == genre]


# Tests
print("--- Avant ---")
for livre in liste_empruntes(catalogue):
    print(f"  Emprunté : {livre['titre']}")

print("\n--- Emprunts ---")
emprunter(catalogue, 5)       # Fahrenheit 451 : disponible → emprunté
emprunter(catalogue, 2)       # 1984 : déjà emprunté
emprunter(catalogue, 99)      # Inexistant

print("\n--- Après emprunts ---")
for livre in liste_empruntes(catalogue):
    print(f"  Emprunté : {livre['titre']}")

print("\n--- Retour ---")
rendre(catalogue, 2)           # 1984 : emprunté → disponible
rendre(catalogue, 5)           # Fahrenheit 451

print("\n--- SF disponibles ---")
for livre in liste_disponibles_par_genre(catalogue, "SF"):
    print(f"  - {livre['titre']}")
--- Avant ---
  Emprunté : 1984

--- Emprunts ---
Emprunt enregistré : Fahrenheit 451
Livre déjà emprunté
Livre introuvable

--- Après emprunts ---
  Emprunté : 1984
  Emprunté : Fahrenheit 451

--- Retour ---
Retour enregistré : 1984
Retour enregistré : Fahrenheit 451

--- SF disponibles ---
  - Fahrenheit 451
  - Dune

Points à retenir

  • Mutabilité : modifier livre["disponible"] change le dict dans le catalogue (alias). C’est exactement ce qu’on veut.
  • Séparation des cas d’erreur (livre introuvable, déjà emprunté) avec des messages clairs.
  • Retour implicite None après les cas d’erreur — évite les imbrications profondes de if/else.

Partie 4 — Tri et extraction

Questions

  1. trier_par_annee(catalogue, decroissant=False) — renvoie une nouvelle liste triée par année.

  2. trier_par_titre(catalogue) — triée par titre (alphabétique, insensible à la casse).

  3. top_n_recents(catalogue, n) — les n livres les plus récents.

  4. grouper_par_genre(catalogue) — renvoie un dict {genre: [liste_de_livres]}.

  5. stats_par_genre(catalogue) — pour chaque genre, renvoie {nb, disponibles, annee_moyenne}.

from collections import defaultdict

catalogue = [
    {"id": 1, "titre": "Le Petit Prince",       "auteur": "Saint-Exupéry", "annee": 1943, "genre": "Conte",   "disponible": True},
    {"id": 2, "titre": "1984",                  "auteur": "Orwell",        "annee": 1949, "genre": "Roman",   "disponible": False},
    {"id": 3, "titre": "L'Étranger",            "auteur": "Camus",         "annee": 1942, "genre": "Roman",   "disponible": True},
    {"id": 4, "titre": "Les Misérables",        "auteur": "Hugo",          "annee": 1862, "genre": "Roman",   "disponible": True},
    {"id": 5, "titre": "Fahrenheit 451",        "auteur": "Bradbury",      "annee": 1953, "genre": "SF",      "disponible": True},
    {"id": 7, "titre": "Dune",                  "auteur": "Herbert",       "annee": 1965, "genre": "SF",      "disponible": True},
    {"id": 9, "titre": "Le Seigneur des anneaux","auteur": "Tolkien",      "annee": 1954, "genre": "Fantasy", "disponible": True},
    {"id": 10,"titre": "Le Meilleur des mondes","auteur": "Huxley",        "annee": 1932, "genre": "SF",      "disponible": False},
]

def trier_par_annee(catalogue, decroissant=False):
    """Nouvelle liste triée par année."""
    return sorted(catalogue, key=lambda l: l["annee"], reverse=decroissant)


def trier_par_titre(catalogue):
    """Nouvelle liste triée par titre, insensible à la casse."""
    return sorted(catalogue, key=lambda l: l["titre"].lower())


def top_n_recents(catalogue, n):
    """Les n livres les plus récents."""
    return trier_par_annee(catalogue, decroissant=True)[:n]


def grouper_par_genre(catalogue):
    """Dict {genre: [livres]}."""
    groupes = defaultdict(list)
    for livre in catalogue:
        groupes[livre["genre"]].append(livre)
    return dict(groupes)


def stats_par_genre(catalogue):
    """Pour chaque genre : nb, disponibles, année moyenne."""
    groupes = grouper_par_genre(catalogue)
    stats = {}
    for genre, livres in groupes.items():
        stats[genre] = {
            "nb": len(livres),
            "disponibles": sum(l["disponible"] for l in livres),
            "annee_moyenne": round(sum(l["annee"] for l in livres) / len(livres)),
        }
    return stats


# Tests
print("=== Par année (ascendant) ===")
for livre in trier_par_annee(catalogue):
    print(f"  {livre['annee']} - {livre['titre']}")

print("\n=== Top 3 récents ===")
for livre in top_n_recents(catalogue, 3):
    print(f"  {livre['annee']} - {livre['titre']}")

print("\n=== Stats par genre ===")
for genre, s in sorted(stats_par_genre(catalogue).items()):
    print(f"  {genre} : {s['nb']} livres, {s['disponibles']} dispo, année moy. {s['annee_moyenne']}")
=== Par année (ascendant) ===
  1862 - Les Misérables
  1932 - Le Meilleur des mondes
  1942 - L'Étranger
  1943 - Le Petit Prince
  1949 - 1984
  1953 - Fahrenheit 451
  1954 - Le Seigneur des anneaux
  1965 - Dune

=== Top 3 récents ===
  1965 - Dune
  1954 - Le Seigneur des anneaux
  1953 - Fahrenheit 451

=== Stats par genre ===
  Conte : 1 livres, 1 dispo, année moy. 1943
  Fantasy : 1 livres, 1 dispo, année moy. 1954
  Roman : 3 livres, 2 dispo, année moy. 1918
  SF : 3 livres, 2 dispo, année moy. 1950

Points à retenir

  • sorted renvoie toujours une nouvelle liste — on ne touche pas le catalogue original.
  • Réutilisation : top_n_recents utilise trier_par_annee. DRY respecté.
  • defaultdict(list) simplifie énormément le groupement.
  • Dict de dict : stats[genre] = {"nb": ..., "disponibles": ..., ...} — structure typique de résultats.

Partie 5 — Interaction utilisateur

Questions

Construisez un petit menu interactif qui permet à un utilisateur de :

  1. Afficher le catalogue complet.
  2. Chercher un livre par titre.
  3. Emprunter un livre (par id).
  4. Rendre un livre (par id).
  5. Voir les statistiques.
  6. Quitter.

Le menu doit boucler jusqu’à ce que l’utilisateur quitte. Gérez les erreurs de saisie (id non entier, par exemple).

# Ce code fonctionne en dehors de Quarto (besoin d'input() interactif).
# Copiez-le dans un fichier .py et lancez-le.

from collections import defaultdict


# Catalogue (comme dans les parties précédentes)
catalogue = [
    {"id": 1, "titre": "Le Petit Prince", "auteur": "Saint-Exupéry", "annee": 1943, "genre": "Conte", "disponible": True},
    # ... autres livres
]


def afficher_menu():
    print("\n" + "=" * 40)
    print("  GESTION DE BIBLIOTHÈQUE")
    print("=" * 40)
    print("  1. Afficher le catalogue")
    print("  2. Chercher un livre par titre")
    print("  3. Emprunter un livre")
    print("  4. Rendre un livre")
    print("  5. Statistiques")
    print("  6. Quitter")
    print("=" * 40)


def menu_principal(catalogue):
    while True:
        afficher_menu()
        choix = input("\nVotre choix : ").strip()

        if choix == "1":
            afficher_catalogue(catalogue)

        elif choix == "2":
            fragment = input("Fragment de titre : ").strip()
            resultats = chercher_par_titre(catalogue, fragment)
            if resultats:
                for livre in resultats:
                    afficher_livre(livre)
            else:
                print("Aucun résultat.")

        elif choix == "3":
            saisie = input("ID du livre à emprunter : ").strip()
            try:
                emprunter(catalogue, int(saisie))
            except ValueError:
                print("ID invalide.")

        elif choix == "4":
            saisie = input("ID du livre à rendre : ").strip()
            try:
                rendre(catalogue, int(saisie))
            except ValueError:
                print("ID invalide.")

        elif choix == "5":
            for genre, s in sorted(stats_par_genre(catalogue).items()):
                print(f"  {genre} : {s['nb']} livres, {s['disponibles']} dispo")

        elif choix == "6":
            print("Au revoir !")
            break

        else:
            print("Choix invalide.")


# Lancement
if __name__ == "__main__":
    menu_principal(catalogue)

Démonstration simulée (pour Quarto)

Comme input() n’est pas interactif dans Quarto, voici une simulation des interactions :

# On simule une séquence d'interactions utilisateur
catalogue = [
    {"id": 1, "titre": "Le Petit Prince", "auteur": "Saint-Exupéry", "annee": 1943, "genre": "Conte", "disponible": True},
    {"id": 5, "titre": "Fahrenheit 451",  "auteur": "Bradbury",      "annee": 1953, "genre": "SF",    "disponible": True},
]

# Simulation : l'utilisateur veut emprunter le livre #5
print("> Emprunt du #5")
for livre in catalogue:
    if livre["id"] == 5:
        livre["disponible"] = False
        print(f"Emprunt enregistré : {livre['titre']}")

# Simulation : l'utilisateur veut rendre le livre #5
print("\n> Retour du #5")
for livre in catalogue:
    if livre["id"] == 5:
        livre["disponible"] = True
        print(f"Retour enregistré : {livre['titre']}")
> Emprunt du #5
Emprunt enregistré : Fahrenheit 451

> Retour du #5
Retour enregistré : Fahrenheit 451

Points à retenir

  • try/except ValueError pour gérer les saisies non entières (sera approfondi en Partie 3 sur les exceptions).
  • while True + break pour la boucle de menu — un idiome classique.
  • Fonction menu_principal qui reçoit le catalogue en paramètre → testable et réutilisable.
  • if __name__ == "__main__": pour que le menu ne se lance pas à l’import.

🏆 Bonus — Pour aller plus loin

Une fois les 5 parties maîtrisées, défis supplémentaires :

  1. Système de réservation : ajouter une clé "reserve_par" aux livres empruntés. Un utilisateur peut être mis en attente quand un livre est déjà emprunté.

  2. Historique : conserver la trace des emprunts et retours dans une liste séparée, avec date (datetime).

  3. Persistance JSON : sauvegarder le catalogue dans un fichier JSON et le recharger au démarrage (vu en Partie 3 avec le module json).

  4. Export CSV : produire un fichier rapport.csv avec les stats par genre.

  5. Recherche floue : chercher un titre même avec des fautes (utiliser difflib.get_close_matches).

catalogue = [
    {"id": 1, "titre": "Dune", "disponible": False, "emprunte_par": "Alice", "file_attente": []},
]

def reserver(catalogue, id_livre, utilisateur):
    """Met un utilisateur en file d'attente pour un livre."""
    for livre in catalogue:
        if livre["id"] == id_livre:
            if livre["disponible"]:
                print("Livre disponible, pas besoin de réserver.")
                return
            if utilisateur in livre.get("file_attente", []):
                print(f"{utilisateur} est déjà dans la file.")
                return
            livre.setdefault("file_attente", []).append(utilisateur)
            print(f"{utilisateur} est en position {len(livre['file_attente'])} dans la file.")
            return
    print("Livre introuvable.")


reserver(catalogue, 1, "Bob")
reserver(catalogue, 1, "Charlie")
reserver(catalogue, 1, "Bob")       # déjà en file
print(catalogue[0])
Bob est en position 1 dans la file.
Charlie est en position 2 dans la file.
Bob est déjà dans la file.
{'id': 1, 'titre': 'Dune', 'disponible': False, 'emprunte_par': 'Alice', 'file_attente': ['Bob', 'Charlie']}

Ce genre d’extension montre qu’un programme réel évolue : on enrichit les structures de données et les fonctions selon les besoins.


✅ Validation du niveau Opérationnel

Bilan du TP

Si vous avez réussi à résoudre les 5 parties (même partiellement avec l’aide des solutions), vous maîtrisez les compétences du niveau Opérationnel TOSA :

  • ✅ Définir des fonctions avec paramètres, valeurs par défaut, retours multiples.
  • Documenter avec docstrings et annotations de type.
  • ✅ Utiliser les f-strings pour des affichages professionnels.
  • ✅ Itérer finement sur des structures imbriquées (liste de dicts).
  • ✅ Comprendre la mutabilité (modification d’un dict impacte le catalogue).
  • Filtrer / trier / grouper / agréger des données.
  • ✅ Utiliser defaultdict et le module collections.
  • ✅ Organiser un programme complet avec menu interactif.

Score TOSA estimé : 551-725 (niveau Opérationnel — seuil minimum de certification atteint !).

Prêt(e) pour la Partie 3 — Niveau Avancé ? Au programme : compréhensions, lambdas, POO, exceptions, fichiers… le vrai Python idiomatique.

← Chapitre précédent : Extraction de donnéesPartie 3 — Niveau Avancé →