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},
]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.
29.1 Contexte
Vous travaillez pour une petite bibliothèque municipale. Le catalogue est représenté sous la forme d’une liste de dictionnaires :
Vous allez écrire progressivement un programme qui gère ce catalogue.
- Créez un fichier
.pypour travailler, avec le catalogue en tête. - Écrivez une fonction par question — nommez-les clairement.
- Documentez chaque fonction avec une docstring.
- Testez au fur et à mesure : n’écrivez pas 50 lignes avant de vérifier.
- Réutilisez les fonctions précédentes dans les suivantes.
Partie 1 — Fonctions de base sur le catalogue
Questions
afficher_livre(livre)— affiche un livre dans un format lisible :"[#5] Fahrenheit 451 — Bradbury (1953) [Disponible]"afficher_catalogue(catalogue)— affiche tous les livres du catalogue, un par ligne.chercher_par_id(catalogue, id_livre)— renvoie le livre correspondant, ouNonesi non trouvé.chercher_par_titre(catalogue, fragment)— renvoie la liste des livres dont le titre contientfragment(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_catalogueappelleafficher_livredans une boucle. - Retour
Noneexplicite 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
nb_livres(catalogue)— nombre total de livres.nb_disponibles(catalogue)— nombre de livres disponibles.pourcentage_disponibilite(catalogue)— pourcentage de livres disponibles, arrondi à 1 décimale.livre_plus_ancien(catalogue)— le livre le plus ancien du catalogue.livre_plus_recent(catalogue)— le plus récent.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)— astuceTrue= 1. min/maxaveckey=: 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
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é".
- Livre introuvable : afficher
rendre(catalogue, id_livre)— inverse : rend le livre disponible, avec messages symétriques.liste_empruntes(catalogue)— renvoie la liste des livres non disponibles.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
Noneaprès les cas d’erreur — évite les imbrications profondes deif/else.
Partie 4 — Tri et extraction
Questions
trier_par_annee(catalogue, decroissant=False)— renvoie une nouvelle liste triée par année.trier_par_titre(catalogue)— triée par titre (alphabétique, insensible à la casse).top_n_recents(catalogue, n)— lesnlivres les plus récents.grouper_par_genre(catalogue)— renvoie un dict{genre: [liste_de_livres]}.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
sortedrenvoie toujours une nouvelle liste — on ne touche pas le catalogue original.- Réutilisation :
top_n_recentsutilisetrier_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 :
- Afficher le catalogue complet.
- Chercher un livre par titre.
- Emprunter un livre (par id).
- Rendre un livre (par id).
- Voir les statistiques.
- 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 ValueErrorpour gérer les saisies non entières (sera approfondi en Partie 3 sur les exceptions).while True + breakpour la boucle de menu — un idiome classique.- Fonction
menu_principalqui 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 :
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é.Historique : conserver la trace des emprunts et retours dans une liste séparée, avec date (
datetime).Persistance JSON : sauvegarder le catalogue dans un fichier JSON et le recharger au démarrage (vu en Partie 3 avec le module
json).Export CSV : produire un fichier
rapport.csvavec les stats par genre.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
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
defaultdictet le modulecollections. - ✅ 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ées • Partie 3 — Niveau Avancé →