Accueil/Informatique & Développement /Chapitre 08 /Exercices corrigés

Python POO — exercices

Les TP3 et TP4 corrigés : encapsuler des données, puis les faire hériter.

🎯

La question à se poser à chaque fois

Qui a le droit de modifier cette donnée, et sous quelles conditions ? Si la réponse est « n'importe qui, n'importe comment », l'attribut doit devenir privé et passer par un setter qui contrôle.

01La classe Departement (TP3 · ex. 1)

1Attributs privés et contrôles Moyen

  1. Créer une classe Departement avec les attributs privés nom et budget.
  2. Créer get_nom() et get_budget().
  3. Créer ajouter_budget(montant) et retirer_budget(montant).
  4. Créer afficher() qui affiche le nom et le budget.
✅ Correction
python
class Departement:

    def __init__(self, nom, budget):
        self.__nom    = nom
        self.__budget = budget

    # --- getters ---
    def get_nom(self):
        return self.__nom

    def get_budget(self):
        return self.__budget

    # --- modifications contrôlées ---
    def ajouter_budget(self, montant):
        if montant > 0:
            self.__budget += montant
        else:
            print("Le montant doit être positif.")

    def retirer_budget(self, montant):
        if montant <= 0:
            print("Le montant doit être positif.")
        elif montant > self.__budget:
            print("Budget insuffisant.")
        else:
            self.__budget -= montant

    def afficher(self):
        print(f"Département : {self.__nom} | Budget : {self.__budget} €")
test
d = Departement("Informatique", 50000)
d.afficher()                  # Département : Informatique | Budget : 50000 €

d.ajouter_budget(10000)
d.afficher()                  # Budget : 60000 €

d.retirer_budget(80000)       # Budget insuffisant.
d.retirer_budget(-5)          # Le montant doit être positif.
d.afficher()                  # Budget : 60000 €   ← inchangé
🧰 Tout l'intérêt est dans retirer_budget

C'est là que l'encapsulation prend son sens. Si budget était public, on écrirait d.budget -= 80000 et le département se retrouverait avec un budget négatif — un état absurde.

La méthode impose deux contrôles : montant positif, et budget suffisant. L'objet ne peut plus devenir incohérent, quoi que fasse le reste du programme.

⚠️ Le double souligné, dans __init__ ET partout
python
def __init__(self, nom, budget):
    self.__nom = nom          # privé

def get_nom(self):
    return self.nom           # ✘ AttributeError : « nom » n'existe pas

L'attribut s'appelle __nom, pas nom. Toute méthode de la classe doit écrire self.__nom. C'est l'oubli le plus courant quand on passe du public au privé.

02La classe Entreprise (TP3 · ex. 2)

2Un objet qui en contient d'autres Exigeant

  1. Créer une classe Entreprise avec un nom et une liste de départements.
  2. ajouter_departement() et afficher_departements().
  3. budget_total() : la somme des budgets.
  4. rechercher_departement(nom) : retourne le département ou None.
  5. supprimer_departement(nom).
✅ Correction
python
class Entreprise:

    def __init__(self, nom):
        self.__nom          = nom
        self.__departements = []        # une liste D'OBJETS Departement

    def ajouter_departement(self, departement):
        self.__departements.append(departement)

    def afficher_departements(self):
        print(f"=== {self.__nom} ===")
        for d in self.__departements:
            d.afficher()                # on appelle la méthode de CHAQUE département

    def budget_total(self):
        total = 0
        for d in self.__departements:
            total += d.get_budget()
        return total

    def rechercher_departement(self, nom):
        for d in self.__departements:
            if d.get_nom() == nom:
                return d
        return None                     # rien trouvé

    def supprimer_departement(self, nom):
        d = self.rechercher_departement(nom)    # on réutilise la recherche !
        if d is not None:
            self.__departements.remove(d)
        else:
            print(f"Département « {nom} » introuvable.")
test
e = Entreprise("TechShop")
e.ajouter_departement(Departement("Informatique", 50000))
e.ajouter_departement(Departement("Commercial", 30000))
e.ajouter_departement(Departement("Logistique", 20000))

e.afficher_departements()
print("Budget total :", e.budget_total())     # 100000

d = e.rechercher_departement("Commercial")
if d is not None:
    d.ajouter_budget(5000)

print("Budget total :", e.budget_total())     # 105000

e.supprimer_departement("Logistique")
print("Budget total :", e.budget_total())     # 85000
🧰 La composition : un objet fait d'objets

Entreprise ne contient pas des chaînes ni des nombres, mais de vrais objets Departement. C'est ce qui rend budget_total() si court : on demande à chaque département son budget, sans savoir comment il le stocke.

Remarquez aussi que supprimer_departement réutilise rechercher_departement plutôt que de reparcourir la liste. Une méthode peut en appeler une autre du même objet via self.

⚠️ is not None, pas != None
python
if d is not None:      # ✔ la forme correcte en Python
if d != None:          # fonctionne, mais non idiomatique
if d:                  # ⚠ piège : faux si l'objet est « vide »

La dernière forme est dangereuse : un objet peut être considéré comme faux (liste vide, zéro), et l'on manquerait un résultat pourtant valide. Pour tester l'absence, on compare à l'identité avec is None.

03Équipement encapsulé (TP4 · partie 1)

3Protéger une adresse IP Moyen

Contexte : dans une entreprise, tous les équipements réseau possèdent une adresse IP, donnée sensible qui ne doit pas être accessible directement.

  1. Classe Equipement avec l'attribut privé ip, initialisé dans __init__.
  2. get_ip() et set_ip(nouvelle_ip) — l'IP doit être une chaîne non vide, sinon message d'erreur.
  3. afficher_ip(), qui utilise get_ip().
✅ Correction
python
class Equipement:

    def __init__(self, ip):
        self.__ip = ip

    def get_ip(self):
        return self.__ip

    def set_ip(self, nouvelle_ip):
        if isinstance(nouvelle_ip, str) and nouvelle_ip.strip() != "":
            self.__ip = nouvelle_ip
        else:
            print("Adresse IP invalide : elle doit être une chaîne non vide.")

    def afficher_ip(self):
        print(f"Adresse IP : {self.get_ip()}")
test
e = Equipement("192.168.1.1")
e.afficher_ip()             # Adresse IP : 192.168.1.1

e.set_ip("10.0.0.1")
e.afficher_ip()             # Adresse IP : 10.0.0.1

e.set_ip("")                # Adresse IP invalide…
e.set_ip(12345)             # Adresse IP invalide…
e.afficher_ip()             # Adresse IP : 10.0.0.1   ← inchangée
🧰 Deux contrôles, pas un

L'énoncé dit « une chaîne non vide » : cela fait deux vérifications.

isinstance(x, str)est-ce bien une chaîne ?
x.strip() != ""et n'est-elle pas vide, ni seulement des espaces ?

Sans isinstance, set_ip(12345) passerait — et 12345.strip() planterait avec une AttributeError. L'ordre des deux tests compte : and s'arrête au premier faux.

🧰 Pourquoi afficher_ip() passe par get_ip()

L'énoncé l'exige, et ce n'est pas un caprice. Si demain l'affichage de l'IP doit être partiellement masqué (192.168.*.* dans les journaux), on modifie uniquement get_ip() — et tout ce qui l'utilise en bénéficie.

C'est la règle générale : une classe passe par ses propres accesseurs plutôt que d'aller lire l'attribut en direct.

04Héritage : Serveur (TP4 · parties 2 et 3)

4Une fille qui ajoute un attribut Moyen

  1. Créer Serveur qui hérite de Equipement et ajoute l'attribut os. Utiliser super().__init__(ip).
  2. afficher_infos() : l'adresse IP (via get_ip()) et le système d'exploitation.
  3. Créer Serveur("192.168.1.10", "Linux") et tester.
✅ Correction
python
class Serveur(Equipement):

    def __init__(self, ip, os):
        super().__init__(ip)        # la partie « Equipement » de l'objet
        self.os = os                # ce qui est propre au serveur

    def afficher_infos(self):
        print(f"IP : {self.get_ip()}")
        print(f"OS : {self.os}")


s = Serveur("192.168.1.10", "Linux")
s.afficher_infos()
# IP : 192.168.1.10
# OS : Linux

s.set_ip("192.168.1.20")     # méthode HÉRITÉE, rien à réécrire
s.afficher_infos()           # IP : 192.168.1.20
🧰 Ce que l'héritage a évité

Serveur n'écrit aucune ligne concernant l'IP : ni l'attribut, ni le getter, ni le setter et ses contrôles. Tout vient de Equipement.

Ajoutez maintenant Routeur, Switch, PointAcces : chacun n'écrit que ce qui lui est propre. Et le jour où la validation de l'IP doit devenir plus stricte, on la corrige à un seul endroit.

⚠️ self.__ip est inaccessible depuis la fille
python
class Serveur(Equipement):
    def afficher_infos(self):
        print(self.__ip)      # ✘ AttributeError

Un attribut privé (__ip) n'est pas hérité de façon utilisable : Python l'a renommé _Equipement__ip, et depuis Serveur le self.__ip chercherait _Serveur__ip — qui n'existe pas.

C'est bien pour cela que l'énoncé impose get_ip(). Si l'on veut qu'une fille accède directement à l'attribut, on le déclare protégé (_ip, un seul souligné) plutôt que privé.

05Quatre bugs à trouver

5Diagnostic Exigeant

Ce code contient quatre erreurs. Trouvez-les.

python
class Equipement:
    def __init__(ip):
        self.__ip = ip

    def get_ip():
        return self.__ip

class Routeur(Equipement):
    def __init__(self, ip, interfaces):
        self.interfaces = interfaces

r = Routeur("10.0.0.1", 4)
print(r.__ip)
✅ Correction
#ErreurMessage obtenu
1def __init__(ip): — self manquant TypeError: __init__() takes 1 positional argument but 2 were given
2def get_ip(): — self manquant ici aussiidem
3super().__init__(ip) oublié dans Routeur : __ip n'est jamais créé AttributeError à la première lecture
4r.__ip — on lit un attribut privé de l'extérieur AttributeError: 'Routeur' object has no attribute '__ip'
python — version corrigée
class Equipement:
    def __init__(self, ip):
        self.__ip = ip

    def get_ip(self):
        return self.__ip


class Routeur(Equipement):
    def __init__(self, ip, interfaces):
        super().__init__(ip)
        self.interfaces = interfaces

    def afficher_infos(self):
        print(f"Routeur {self.get_ip()} — {self.interfaces} interfaces")


r = Routeur("10.0.0.1", 4)
r.afficher_infos()        # Routeur 10.0.0.1 — 4 interfaces
🧰 Lire les messages d'erreur de la POO
MessageCherchez…
takes 0 positional arguments but 1 was given self oublié dans la définition
object has no attribute '_Classe__x' super().__init__() oublié
object has no attribute '__x' accès à un privé depuis l'extérieur
missing 1 required positional argument un argument manque à l'appel du constructeur

Le nom mutilé _Classe__attribut dans un message est un indice précieux : il vous dit à quelle classe appartenait vraiment l'attribut.

⚡ Les erreurs qui coûtent le plus de points

  • self oublié dans la définition d'une méthode.
  • super().__init__() oublié dans le constructeur d'une fille.
  • self.nom au lieu de self.__nom après être passé au privé.
  • Lire un attribut privé de l'extérieur au lieu d'utiliser le getter.
  • Un setter sans contrôle — il ne sert alors à rien.
  • Oublier return dans un getter (renvoie None).
  • if d: au lieu de if d is not None:.

Sources exploitées

  • TP3 — POO : classes Departement et Entreprise (le professeur)
  • TP4 — POO : encapsulation et héritage, gestion d'équipements réseau
  • comptes rendus d'élève