Retour au blog

Maîtriser la POO en Python : Exercices Progressifs

Développe ta logique objet avec notre série d'exercices Python, conçue pour te faire progresser de manière efficace et ludique.

Cet article a été rédigé à des fins pédagogiques. Les informations présentées peuvent évoluer. Nous t’invitons à vérifier auprès de sources officielles.

Salut à toi, futur expert en développement ! Aujourd'hui, on s'attaque à un pilier fondamental de la programmation moderne : la Programmation Orientée Objet (POO) en Python. Que tu sois en licence d'informatique ou simplement désireux d'améliorer tes compétences, cette série d'exercices progressifs est faite pour toi. Prépare-toi à manipuler des classes, des objets, de l'héritage et bien plus encore !

Compétences travaillées :

  • Création et manipulation de classes et d'objets.
  • Compréhension des attributs et des méthodes.
  • Application des principes d'encapsulation, d'héritage et de polymorphisme.
  • Utilisation des méthodes spéciales (dunder methods).
  • Développement d'une logique de conception orientée objet robuste.

Erreurs fréquentes :

  • Confondre une classe et une instance (objet). Une classe est le plan, un objet est la réalisation.
  • Oublier le paramètre self dans les méthodes d'instance. Il représente l'instance elle-même.
  • Mal gérer la visibilité des attributs (privés/publics).
  • Ne pas comprendre quand utiliser l'héritage versus la composition.
  • Ignorer les exceptions : un bon code POO doit être robuste face aux erreurs.

Exercices de Programmation Orientée Objet en Python

Exercice 1 : Création d'une Classe Basique

Tu es chargé de créer un système simple pour gérer les livres d'une bibliothèque.

a) Crée une classe nommée Livre. Cette classe doit avoir un constructeur __init__ qui prend en paramètres le titre, l'auteur et l'année_publication du livre. Ces paramètres devront être stockés comme attributs de l'objet.

b) Instancie deux objets de type Livre :

  • "Le Seigneur des Anneaux", J.R.R. Tolkien, 1954
  • "1984", George Orwell, 1949

c) Affiche le titre et l'auteur de chaque livre instancié.

Barème indicatif : 2 points

Correction :

a) La création de la classe Livre avec son constructeur est la première étape. Le constructeur (__init__) est une méthode spéciale appelée lors de la création d'un nouvel objet. Le paramètre self est toujours le premier argument et représente l'instance de l'objet.

class Livre:
 def __init__(self, titre, auteur, annee_publication):
 self.titre = titre
 self.auteur = auteur
 self.annee_publication = annee_publication

b) Pour instancier des objets, tu dois appeler le nom de la classe comme une fonction, en lui passant les arguments nécessaires au constructeur.

livre1 = Livre("Le Seigneur des Anneaux", "J.R.R. Tolkien", 1954)
livre2 = Livre("1984", "George Orwell", 1949)

c) Pour accéder aux attributs d'un objet, tu utilises la notation pointée (objet.attribut).

print(f"Livre 1 : {livre1.titre} de {livre1.auteur}")
print(f"Livre 2 : {livre2.titre} de {livre2.auteur}")

Résultat :
Livre 1 : Le Seigneur des Anneaux de J.R.R. Tolkien
Livre 2 : 1984 de George Orwell

Astuce : Le __init__ est l'équivalent du constructeur dans d'autres langages. Il est crucial pour initialiser l'état de ton objet.

Exercice 2 : Ajout de Méthodes

Reprends ta classe Livre. Ajoute une méthode nommée afficher_details qui affichera tous les détails du livre (titre, auteur, année de publication) dans une phrase formatée.

a) Modifie la classe Livre pour inclure cette méthode.

b) Appelle cette méthode pour les deux livres instanciés précédemment.

Barème indicatif : 2 points

Correction :

a) Une méthode est une fonction définie à l'intérieur d'une classe. Elle prend toujours self comme premier argument pour pouvoir accéder aux attributs de l'instance.

class Livre:
 def __init__(self, titre, auteur, annee_publication):
 self.titre = titre
 self.auteur = auteur
 self.annee_publication = annee_publication

 def afficher_details(self):
 print(f"'{self.titre}' par {self.auteur}, publié en {self.annee_publication}.")

b) Pour appeler une méthode, tu utilises la notation pointée sur l'objet (objet.methode()).

livre1 = Livre("Le Seigneur des Anneaux", "J.R.R. Tolkien", 1954)
livre2 = Livre("1984", "George Orwell", 1949)

livre1.afficher_details()
livre2.afficher_details()

Résultat :
'Le Seigneur des Anneaux' par J.R.R. Tolkien, publié en 1954.
'1984' par George Orwell, publié en 1949.

Point méthode : Les méthodes te permettent de définir des comportements spécifiques aux objets de ta classe.

Exercice 3 : Attributs Modifiables

Ajoute un attribut disponible à ta classe Livre, initialisé à True par défaut. Crée ensuite deux méthodes : emprunter() qui met disponible à False si le livre l'est, et rendre() qui le met à True.

a) Modifie le constructeur et ajoute les méthodes emprunter et rendre à la classe Livre.

b) Crée un livre, puis simule un emprunt et un retour, en affichant l'état de disponibilité à chaque étape.

Barème indicatif : 2 points

Correction :

a) L'attribut disponible est ajouté au constructeur avec une valeur par défaut. Les méthodes vérifient l'état avant de le modifier.

class Livre:
 def __init__(self, titre, auteur, annee_publication, disponible=True):
 self.titre = titre
 self.auteur = auteur
 self.annee_publication = annee_publication
 self.disponible = disponible # Nouvel attribut

 def afficher_details(self):
 etat = "disponible" if self.disponible else "emprunté"
 print(f"'{self.titre}' par {self.auteur}, publié en {self.annee_publication}. État : {etat}.")

 def emprunter(self):
 if self.disponible:
 self.disponible = False
 print(f"Le livre '{self.titre}' a été emprunté.")
 else:
 print(f"Le livre '{self.titre}' n'est pas disponible pour l'emprunt.")

 def rendre(self):
 if not self.disponible:
 self.disponible = True
 print(f"Le livre '{self.titre}' a été rendu.")
 else:
 print(f"Le livre '{self.titre}' était déjà disponible.")

b) On instancie un livre et on appelle les méthodes pour simuler les actions.

mon_livre = Livre("Le Guide du voyageur galactique", "Douglas Adams", 1979)

mon_livre.afficher_details()
mon_livre.emprunter()
mon_livre.afficher_details()
mon_livre.rendre()
mon_livre.afficher_details()
mon_livre.rendre() # Tentative de rendre un livre déjà disponible

Résultat :
'Le Guide du voyageur galactique' par Douglas Adams, publié en 1979. État : disponible.
Le livre 'Le Guide du voyageur galactique' a été emprunté.
'Le Guide du voyageur galactique' par Douglas Adams, publié en 1979. État : emprunté.
Le livre 'Le Guide du voyageur galactique' a été rendu.
'Le Guide du voyageur galactique' par Douglas Adams, publié en 1979. État : disponible.
Le livre 'Le Guide du voyageur galactique' était déjà disponible.

Astuce : Les attributs peuvent être modifiés directement, mais encapsuler leur modification via des méthodes (emprunter, rendre) est une bonne pratique POO pour ajouter de la logique et des vérifications.

Exercice 4 : Encapsulation (Propriétés)

Il est important que l'année de publication d'un livre ne soit pas antérieure à 0 et pas postérieure à l'année actuelle. Utilise des propriétés (@property et @annee_publication.setter) pour gérer l'accès et la modification de l'attribut annee_publication de ta classe Livre.

a) Modifie la classe Livre pour implémenter une propriété pour annee_publication avec les validations nécessaires.

b) Teste la création d'un livre avec une année invalide (ex: -100) et la modification d'un livre avec une année future (ex: 2050).

Barème indicatif : 3 points

Correction :

a) Les propriétés permettent de contrôler l'accès aux attributs comme s'ils étaient des attributs directs, tout en exécutant du code (validations, traitements) en arrière-plan. On utilise un attribut interne (souvent préfixé par _) pour stocker la valeur réelle.

import datetime

class Livre:
 def __init__(self, titre, auteur, annee_publication, disponible=True):
 self.titre = titre
 self.auteur = auteur
 self.disponible = disponible
 self.annee_publication = annee_publication # Utilise le setter ici

 @property
 def annee_publication(self):
 return self._annee_publication

 @annee_publication.setter
 def annee_publication(self, value):
 annee_actuelle = datetime.date.today().year
 if not isinstance(value, int) or value <= 0 or value > annee_actuelle:
 print(f"Attention : L'année de publication '{value}' est invalide. Elle doit être un entier positif et non future. La valeur ne sera pas modifiée.")
 # Optionnel : lever une exception ValueError("Année de publication invalide")
 # Pour cet exercice, nous allons juste afficher un message et ne pas changer la valeur
 if not hasattr(self, '_annee_publication'): # S'assure que l'attribut existe pour la première assignation
 self._annee_publication = annee_actuelle # Initialise avec une valeur par défaut valide
 else:
 self._annee_publication = value

 def afficher_details(self):
 etat = "disponible" if self.disponible else "emprunté"
 print(f"'{self.titre}' par {self.auteur}, publié en {self.annee_publication}. État : {etat}.")

b) Test de la propriété avec des valeurs invalides.

# Test de création avec une année invalide
livre_test1 = Livre("Titre Test 1", "Auteur Test", -100)
livre_test1.afficher_details() # Devrait montrer l'année par défaut ou un message d'erreur lors de la création

print("-" * 20)

# Test de modification avec une année future
livre_test2 = Livre("Titre Test 2", "Autre Auteur", 2000)
livre_test2.afficher_details()
livre_test2.annee_publication = 2050 # Essai d'assigner une année future
livre_test2.afficher_details() # Devrait montrer l'année inchangée et un message d'erreur
livre_test2.annee_publication = 2010 # Essai d'assigner une année valide
livre_test2.afficher_details()

Résultat :
Attention : L'année de publication '-100' est invalide. Elle doit être un entier positif et non future. La valeur ne sera pas modifiée.
'Titre Test 1' par Auteur Test, publié aujourd'hui. État : disponible.
--------------------
'Titre Test 2' par Autre Auteur, publié en 2000. État : disponible.
Attention : L'année de publication '2050' est invalide. Elle doit être un entier positif et non future. La valeur ne sera pas modifiée.
'Titre Test 2' par Autre Auteur, publié en 2000. État : disponible.
'Titre Test 2' par Autre Auteur, publié en 2010. État : disponible.

Point méthode : L'encapsulation via @property est une manière élégante de protéger tes attributs et d'ajouter de la logique de validation sans casser l'interface de ta classe.

Exercice 5 : Héritage Simple

Tu dois maintenant gérer des livres numériques (e-books) qui ont une taille de fichier en Mo. Crée une classe LivreNumerique qui hérite de Livre et ajoute un attribut taille_mo. Assure-toi que le constructeur gère les attributs de la classe parente et le nouvel attribut.

a) Crée la classe LivreNumerique héritant de Livre. Le constructeur doit appeler le constructeur de la classe parente et initialiser taille_mo.

b) Redéfinis la méthode afficher_details dans LivreNumerique pour inclure la taille du fichier.

c) Instancie un LivreNumerique et affiche ses détails.

Barème indicatif : 3 points

Correction :

a) L'héritage permet à une classe (enfant) de réutiliser les attributs et méthodes d'une autre classe (parente). super().__init__() est utilisé pour appeler le constructeur de la classe parente.

import datetime

class Livre:
 def __init__(self, titre, auteur, annee_publication, disponible=True):
 self.titre = titre
 self.auteur = auteur
 self.disponible = disponible
 self.annee_publication = annee_publication # Utilise le setter

 @property
 def annee_publication(self):
 return self._annee_publication

 @annee_publication.setter
 def annee_publication(self, value):
 annee_actuelle = datetime.date.today().year
 if not isinstance(value, int) or value <= 0 or value > annee_actuelle:
 if not hasattr(self, '_annee_publication'):
 self._annee_publication = annee_actuelle # Initialise avec une valeur par défaut valide
 print(f"Attention : L'année de publication '{value}' est invalide. Elle doit être un entier positif et non future. La valeur ne sera pas modifiée.")
 else:
 self._annee_publication = value

 def afficher_details(self):
 etat = "disponible" if self.disponible else "emprunté"
 print(f"'{self.titre}' par {self.auteur}, publié en {self.annee_publication}. État : {etat}.")

 def emprunter(self):
 if self.disponible:
 self.disponible = False
 print(f"Le livre '{self.titre}' a été emprunté.")
 else:
 print(f"Le livre '{self.titre}' n'est pas disponible pour l'emprunt.")

 def rendre(self):
 if not self.disponible:
 self.disponible = True
 print(f"Le livre '{self.titre}' a été rendu.")
 else:
 print(f"Le livre '{self.titre}' était déjà disponible.")

class LivreNumerique(Livre): # Hérite de Livre
 def __init__(self, titre, auteur, annee_publication, taille_mo, disponible=True):
 super().__init__(titre, auteur, annee_publication, disponible) # Appel du constructeur parent
 self.taille_mo = taille_mo # Nouvel attribut spécifique

b) La méthode afficher_details est redéfinie pour ajouter l'information sur la taille. On peut appeler la méthode parente avec super().afficher_details() puis ajouter les informations supplémentaires, ou reconstruire complètement l'affichage.

class LivreNumerique(Livre):
 def __init__(self, titre, auteur, annee_publication, taille_mo, disponible=True):
 super().__init__(titre, auteur, annee_publication, disponible)
 self.taille_mo = taille_mo

 def afficher_details(self):
 # On peut réutiliser la logique de la classe parente ou la redéfinir
 etat = "disponible" if self.disponible else "emprunté"
 print(f"'{self.titre}' (numérique) par {self.auteur}, publié en {self.annee_publication}. Taille : {self.taille_mo} Mo. État : {etat}.")

c) Instanciation et affichage.

e_book = LivreNumerique("Dune", "Frank Herbert", 1965, 25.5)
e_book.afficher_details()
e_book.emprunter() # La méthode emprunter de Livre est héritée
e_book.afficher_details()

Résultat :
'Dune' (numérique) par Frank Herbert, publié en 1965. Taille : 25.5 Mo. État : disponible.
Le livre 'Dune' a été emprunté.
'Dune' (numérique) par Frank Herbert, publié en 1965. Taille : 25.5 Mo. État : emprunté.

Astuce : L'héritage est un excellent moyen de réutiliser du code et d'établir des relations "est un type de" (un livre numérique est un type de livre).

Exercice 6 : Polymorphisme

Crée une fonction decrire_objet qui prend un objet en paramètre et appelle sa méthode afficher_details(). Teste cette fonction avec un objet Livre et un objet LivreNumerique.

a) Implémente la fonction decrire_objet.

b) Appelle decrire_objet avec une instance de Livre et une instance de LivreNumerique. Observe comment la méthode afficher_details correcte est appelée pour chaque type d'objet.

Barème indicatif : 3 points

Correction :

a) Le polymorphisme permet à des objets de classes différentes d'être traités de manière uniforme s'ils partagent une interface commune (ici, la méthode afficher_details).

def decrire_objet(objet):
 objet.afficher_details()

b) Test de la fonction avec différentes instances.

# Création des instances
livre_physique = Livre("Fondation", "Isaac Asimov", 1951)
livre_numerique = LivreNumerique("Neuromancien", "William Gibson", 1984, 12.8)

# Appel de la fonction polymorphique
print("--- Description des objets via la fonction decrire_objet ---")
decrire_objet(livre_physique)
decrire_objet(livre_numerique)

Résultat :
--- Description des objets via la fonction decrire_objet ---
'Fondation' par Isaac Asimov, publié en 1951. État : disponible.
'Neuromancien' (numérique) par William Gibson, publié en 1984. Taille : 12.8 Mo. État : disponible.

Point méthode : C'est la beauté du polymorphisme : tu peux écrire du code générique qui fonctionne avec n'importe quel objet, du moment qu'il implémente la méthode attendue. Python utilise le "duck typing" ("Si ça marche comme un canard et ça cancane comme un canard, c'est un canard").

Exercice 7 : Composition de Classes

Plutôt que d'hériter, tu peux composer des objets. Crée une classe Auteur (avec nom, prenom, date_naissance) et modifie la classe Livre pour qu'elle contienne un objet Auteur au lieu de l'attribut auteur en chaîne de caractères.

a) Crée la classe Auteur.

b) Modifie la classe Livre pour qu'elle accepte un objet Auteur dans son constructeur. Adapte la méthode afficher_details pour afficher le nom complet de l'auteur à partir de l'objet Auteur.

c) Instancie un objet Auteur, puis un objet Livre en lui passant l'objet Auteur. Affiche les détails du livre.

Barème indicatif : 4 points

Correction :

a) La classe Auteur est simple.

class Auteur:
 def __init__(self, nom, prenom, date_naissance):
 self.nom = nom
 self.prenom = prenom
 self.date_naissance = date_naissance

 def nom_complet(self):
 return f"{self.prenom} {self.nom}"

b) La classe Livre est modifiée. L'attribut self.auteur stocke maintenant un objet Auteur.

import datetime

class Livre:
 def __init__(self, titre, auteur_obj, annee_publication, disponible=True): # auteur_obj est un objet Auteur
 self.titre = titre
 self.auteur = auteur_obj # Stocke l'objet Auteur
 self.disponible = disponible
 self.annee_publication = annee_publication

 @property
 def annee_publication(self):
 return self._annee_publication

 @annee_publication.setter
 def annee_publication(self, value):
 annee_actuelle = datetime.date.today().year
 if not isinstance(value, int) or value <= 0 or value > annee_actuelle:
 if not hasattr(self, '_annee_publication'):
 self._annee_publication = annee_actuelle
 print(f"Attention : L'année de publication '{value}' est invalide. Elle doit être un entier positif et non future. La valeur ne sera pas modifiée.")
 else:
 self._annee_publication = value

 def afficher_details(self):
 etat = "disponible" if self.disponible else "emprunté"
 # Utilisation de la méthode nom_complet de l'objet Auteur
 print(f"'{self.titre}' par {self.auteur.nom_complet()}, publié en {self.annee_publication}. État : {etat}.")

 def emprunter(self):
 if self.disponible:
 self.disponible = False
 print(f"Le livre '{self.titre}' a été emprunté.")
 else:
 print(f"Le livre '{self.titre}' n'est pas disponible pour l'emprunt.")

 def rendre(self):
 if not self.disponible:
 self.disponible = True
 print(f"Le livre '{self.titre}' a été rendu.")
 else:
 print(f"Le livre '{self.titre}' était déjà disponible.")

c) Instanciation des objets.

auteur_tolkien = Auteur("Tolkien", "J.R.R.", "03/01/1892")
livre_compose = Livre("Le Hobbit", auteur_tolkien, 1937)

livre_compose.afficher_details()

Résultat :
'Le Hobbit' par J.R.R. Tolkien, publié en 1937. État : disponible.

Point méthode : La composition est souvent préférée à l'héritage quand il s'agit d'une relation "a un" (un livre a un auteur). Elle favorise une plus grande flexibilité et réduit la complexité des hiérarchies d'héritage.

Exercice 8 : Méthodes de Classe et Méthodes Statiques

Ajoute à la classe Livre :

a) Une méthode de classe (@classmethod) creer_livre_avec_annee_actuelle qui prend le titre et l'auteur et crée un livre avec l'année de publication par défaut à l'année courante.

b) Une méthode statique (@staticmethod) verifier_validite_titre qui prend un titre en chaîne de caractères et retourne True si le titre n'est pas vide et fait plus de 2 caractères, False sinon.

c) Utilise ces méthodes pour créer un livre et vérifier la validité d'un titre.

Barème indicatif : 4 points

Correction :

a) Une méthode de classe reçoit la classe elle-même (souvent nommée cls) comme premier argument, ce qui lui permet d'accéder aux attributs de classe ou de créer des instances de cette classe.

b) Une méthode statique ne reçoit aucun argument spécial (ni self, ni cls). Elle se comporte comme une fonction normale mais est logiquement groupée avec la classe.

import datetime

class Livre:
 def __init__(self, titre, auteur_obj, annee_publication, disponible=True):
 self.titre = titre
 self.auteur = auteur_obj
 self.disponible = disponible
 self.annee_publication = annee_publication

 @property
 def annee_publication(self):
 return self._annee_publication

 @annee_publication.setter
 def annee_publication(self, value):
 annee_actuelle = datetime.date.today().year
 if not isinstance(value, int) or value <= 0 or value > annee_actuelle:
 if not hasattr(self, '_annee_publication'):
 self._annee_publication = annee_actuelle
 print(f"Attention : L'année de publication '{value}' est invalide. Elle doit être un entier positif et non future. La valeur ne sera pas modifiée.")
 else:
 self._annee_publication = value

 def afficher_details(self):
 etat = "disponible" if self.disponible else "emprunté"
 print(f"'{self.titre}' par {self.auteur.nom_complet()}, publié en {self.annee_publication}. État : {etat}.")

 def emprunter(self):
 if self.disponible:
 self.disponible = False
 print(f"Le livre '{self.titre}' a été emprunté.")
 else:
 print(f"Le livre '{self.titre}' n'est pas disponible pour l'emprunt.")

 def rendre(self):
 if not self.disponible:
 self.disponible = True
 print(f"Le livre '{self.titre}' a été rendu.")
 else:
 print(f"Le livre '{self.titre}' était déjà disponible.")

 @classmethod
 def creer_livre_avec_annee_actuelle(cls, titre, auteur_obj):
 annee_actuelle = datetime.date.today().year
 return cls(titre, auteur_obj, annee_actuelle) # Appelle le constructeur de la classe (cls)

 @staticmethod
 def verifier_validite_titre(titre):
 return isinstance(titre, str) and len(titre) > 2

c) Utilisation des nouvelles méthodes.

auteur_orwell = Auteur("Orwell", "George", "25/06/1903")

# Utilisation de la méthode de classe
livre_moderne = Livre.creer_livre_avec_annee_actuelle("Un nouveau monde", auteur_orwell)
livre_moderne.afficher_details()

print("-" * 20)

# Utilisation de la méthode statique
titre_valide = "Le Grand Sommeil"
titre_invalide_court = "Un"
titre_invalide_vide = ""

print(f"'{titre_valide}' est valide ? {Livre.verifier_validite_titre(titre_valide)}")
print(f"'{titre_invalide_court}' est valide ? {Livre.verifier_validite_titre(titre_invalide_court)}")
print(f"'{titre_invalide_vide}' est valide ? {Livre.verifier_validite_titre(titre_invalide_vide)}")

Résultat :
'Un nouveau monde' par George Orwell, publié aujourd'hui. État : disponible.
--------------------
'Le Grand Sommeil' est valide ? True
'Un' est valide ? False
'' est valide ? False

Point méthode : Les méthodes de classe sont utiles pour des constructeurs alternatifs, tandis que les méthodes statiques sont pour des utilitaires liés à la classe mais qui n'ont pas besoin d'accéder à l'état de l'instance ou de la classe.

Exercice 9 : Méthodes Spéciales (Dunder Methods)

Ajoute à ta classe Livre les méthodes spéciales suivantes :

a) __str__ : Cette méthode doit retourner une représentation textuelle conviviale de l'objet Livre (ex: "Titre : [titre], Auteur : [auteur.nom_complet()], Année : [année]").

b) __repr__ : Cette méthode doit retourner une représentation textuelle permettant de recréer l'objet (ex: "Livre('Titre', AuteurObj, 2023)").

c) __eq__ : Permet de comparer deux livres par leur titre et leur auteur. Deux livres sont considérés égaux s'ils ont le même titre et le même auteur.

d) Teste ces méthodes avec des objets Livre.

Barème indicatif : 4 points

Correction :

Les méthodes spéciales, ou "dunder methods" (pour "double underscore"), permettent de définir le comportement de tes objets avec des opérations Python natives (impression, comparaison, etc.).

import datetime

class Auteur:
 def __init__(self, nom, prenom, date_naissance):
 self.nom = nom
 self.prenom = prenom
 self.date_naissance = date_naissance

 def nom_complet(self):
 return f"{self.prenom} {self.nom}"

 def __repr__(self): # Utile pour __repr__ de Livre
 return f"Auteur('{self.nom}', '{self.prenom}', '{self.date_naissance}')"

class Livre:
 def __init__(self, titre, auteur_obj, annee_publication, disponible=True):
 self.titre = titre
 self.auteur = auteur_obj
 self.disponible = disponible
 self.annee_publication = annee_publication

 @property
 def annee_publication(self):
 return self._annee_publication

 @annee_publication.setter
 def annee_publication(self, value):
 annee_actuelle = datetime.date.today().year
 if not isinstance(value, int) or value <= 0 or value > annee_actuelle:
 if not hasattr(self, '_annee_publication'):
 self._annee_publication = annee_actuelle
 print(f"Attention : L'année de publication '{value}' est invalide. Elle doit être un entier positif et non future. La valeur ne sera pas modifiée.")
 else:
 self._annee_publication = value

 def afficher_details(self):
 etat = "disponible" if self.disponible else "emprunté"
 print(f"'{self.titre}' par {self.auteur.nom_complet()}, publié en {self.annee_publication}. État : {etat}.")

 def emprunter(self):
 if self.disponible:
 self.disponible = False
 print(f"Le livre '{self.titre}' a été emprunté.")
 else:
 print(f"Le livre '{self.titre}' n'est pas disponible pour l'emprunt.")

 def rendre(self):
 if not self.disponible:
 self.disponible = True
 print(f"Le livre '{self.titre}' a été rendu.")
 else:
 print(f"Le livre '{self.titre}' était déjà disponible.")

 @classmethod
 def creer_livre_avec_annee_actuelle(cls, titre, auteur_obj):
 annee_actuelle = datetime.date.today().year
 return cls(titre, auteur_obj, annee_actuelle)

 @staticmethod
 def verifier_validite_titre(titre):
 return isinstance(titre, str) and len(titre) > 2

 # a) Méthode __str__
 def __str__(self):
 return f"Titre : {self.titre}, Auteur : {self.auteur.nom_complet()}, Année : {self.annee_publication}"

 # b) Méthode __repr__
 def __repr__(self):
 # Assure-toi que __repr__ d'Auteur est aussi défini pour un bon affichage
 return f"Livre('{self.titre}', {repr(self.auteur)}, {self.annee_publication}, disponible={self.disponible})"

 # c) Méthode __eq__
 def __eq__(self, other):
 if not isinstance(other, Livre):
 return NotImplemented # Indique la comparaison n'est pas implémentée pour ce type
 return self.titre == other.titre and self.auteur.nom_complet() == other.auteur.nom_complet()

d) Test des méthodes.

auteur_king = Auteur("King", "Stephen", "21/09/1947")
livre_it = Livre("Ça", auteur_king, 1986)
livre_shining = Livre("Shining", auteur_king, 1977)
livre_it_bis = Livre("Ça", auteur_king, 2000) # Même titre, même auteur mais année différente

print("--- Test de __str__ ---")
print(livre_it)

print("\n--- Test de __repr__ ---")
print(repr(livre_shining))

print("\n--- Test de __eq__ ---")
print(f"livre_it == livre_shining : {livre_it == livre_shining}")
print(f"livre_it == livre_it_bis : {livre_it == livre_it_bis}")
print(f"livre_it == 'une chaîne' : {livre_it == 'une chaîne'}")

Résultat :
--- Test de __str__ ---
Titre : Ça, Auteur : Stephen King, Année : 1986

--- Test de __repr__ ---
Livre('Shining', Auteur('King', 'Stephen', '21/09/1947'), 1977, disponible=True)

--- Test de __eq__ ---
livre_it == livre_shining : False
livre_it == livre_it_bis : True
livre_it == 'une chaîne' : False

Astuce : __str__ est pour l'utilisateur final (lisible), __repr__ est pour le développeur (non ambiguë et si possible recréable). __eq__ te donne un contrôle précis sur la notion d'égalité entre tes objets.

Comment ORBITECH Peut T'aider

ORBITECH AI Academy met à ta disposition des outils concrets pour réviser plus efficacement et progresser à ton rythme.

Tous ces outils sont disponibles sur ta plateforme ORBITECH. Connecte-toi et explore ceux qui correspondent le mieux à tes besoins !

Commencer gratuitement

Contenu en libre diffusion — partage autorisé sous réserve de mentionner ORBITECH AI Academy comme source.

COMMENCE DÈS MAINTENANT

Rejoins des milliers d’étudiants qui utilisent ORBITECH pour exceller.

Commencer gratuitement
🌍 ORBITECH AI Academy — Free education in 88 languages for 171 countries