L'héritage et super : des classes semblables sans code dupliqué
- Expliquer quand l’héritage évite du code dupliqué, et le vérifier avec le test « est une sorte de »
- Définir une classe parente et une classe enfant, et comprendre comment Python cherche une méthode — d’abord dans l’enfant puis dans le parent
- Redéfinir une méthode dans l’enfant pour changer son comportement
- Utiliser
super()— à la fois dans__init__et pour étendre une méthode du parent
Dans cette leçon
Cette leçon est construite comme une seule histoire, dans le même ordre que les diapositives :
- Chapitre 1 — pourquoi l’héritage : deux fois le même code, et comment hériter au lieu de copier
- Chapitre 2 — la redéfinition et
super(): hériter, mais pas exactement — ne changer qu’une partie du comportement
À la fin de la leçon — de la pratique, et un mini-projet qui relie les deux chapitres.
Chapitre 1 : Pourquoi l’héritage — parent, enfant et la relation « est une sorte de »
Qu’allons-nous apprendre ?
- Pourquoi l’héritage est nécessaire : le problème des classes presque identiques.
- La classe parente (Parent Class, Base Class) et la classe enfant (Child Class, Subclass).
- La syntaxe de l’héritage en Python.
- Ce qui se transmet par l’héritage.
- Le test is-a : quand l’héritage convient et quand il ne convient pas.
Pourquoi en avons-nous besoin ?
Dans la leçon sur les classes et les objets, nous nous sommes demandé : que se passe-t-il quand on ajoute un « compte d’épargne » semblable à un compte ordinaire, mais avec des intérêts ? Dans la version procédurale, chaque fonction se remplissait de if account["type"] == .... Voyons le même problème en POO, sans héritage :
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def monthly_cost(self):
return self.salary * 1.25
def describe(self):
return self.name + " earns " + str(self.salary)
class Manager:
def __init__(self, name, salary, team_size):
self.name = name
self.salary = salary
self.team_size = team_size
def monthly_cost(self):
return self.salary * 1.25
def describe(self):
return self.name + " earns " + str(self.salary)
def team_report(self):
return self.name + " manages " + str(self.team_size) + " people"Manager copie presque tout Employee : name, salary, monthly_cost, describe. Que se passe-t-il quand le coût employeur passe de 1.25 à 1.3 ? Il faut corriger à deux endroits, et s’il y a aussi Intern, Contractor et Director, à cinq. C’est une violation de DRY au niveau des classes.
Et autre chose : un manager est un employé. Tout ce qui est vrai pour un employé est aussi vrai pour un manager, et en plus le manager a d’autres choses. Le code doit refléter cette relation.
Explication à partir des bases
L’héritage
L’héritage (Inheritance) = un mécanisme dans lequel une nouvelle classe reçoit automatiquement tous les attributs et toutes les méthodes d’une classe existante, et peut en ajouter ou en modifier une partie.
- La classe parente (Parent, Base, Superclass) — la classe générale.
Employee. - La classe enfant (Child, Derived, Subclass) — la classe particulière, qui hérite.
Manager.
Nous avons un modèle de fabrication d’une « voiture » (le parent) : 4 roues, un moteur, une direction, des freins. Maintenant, nous voulons une « voiture de sport ». Nous ne concevons pas une voiture à partir de zéro. Nous prenons le modèle existant, et nous ajoutons un aileron et changeons le moteur. Chaque voiture de sport est une voiture (elle roule, elle freine), mais toute voiture n’est pas une voiture de sport.
La relation « est une sorte de » (is-a)
L’héritage ne convient que lorsqu’on peut dire honnêtement : « l’enfant est une sorte de parent ».
| Phrase | Héritage ? |
|---|---|
| Un manager **est une sorte d’**employé | ✓ Manager(Employee) |
| Un chien **est une sorte d’**animal | ✓ Dog(Animal) |
| Un compte d’épargne est une sorte de compte bancaire | ✓ SavingsAccount(BankAccount) |
| Une voiture est une sorte de moteur ? | ✗ Une voiture a un moteur (has-a), et c’est de la composition, le thème de la conception orientée objet |
| Une commande est une sorte de client ? | ✗ Une commande a un client |
Qu’est-ce qui se transmet par l’héritage ?
L’enfant reçoit tout du parent :
- Le
__init__(si l’enfant ne définit pas son propre__init__). - Toutes les méthodes.
- Les Class Attributes.
Et l’enfant peut :
- Ajouter de nouvelles méthodes et de nouveaux attributs.
- Modifier (Override) des méthodes existantes. Nous l’apprendrons au chapitre suivant, avec
super().
La recherche par couches — encore
Quand vous écrivez manager.describe(), Python cherche : (1) sur l’objet, (2) dans sa classe (Manager), (3) dans le parent (Employee)… et s’arrête au premier endroit où il l’a trouvée. C’est la même idée de recherche par couches que nous avons vue avec LEGB et avec l’ordre de recherche des attributs.
Syntaxe
class Parent:
def __init__(self, x):
self.x = x
def shared_method(self):
return ...
class Child(Parent): # Child inherits from Parent
def extra_method(self): # new method, only in Child
return ...
c = Child(5) # uses Parent.__init__ (Child has none of its own)
c.shared_method() # inherited
c.extra_method() # Child's own- Le nom du parent vient entre parenthèses après le nom de l’enfant.
- Un enfant sans aucun ajout :
class Child(Parent): passest une classe valide identique au parent.
Premier exemple de code
week-3/employees.py :
class Employee:
EMPLOYER_COST_RATE = 1.25
def __init__(self, name, salary):
self.name = name
self.salary = salary
def monthly_cost(self):
return self.salary * Employee.EMPLOYER_COST_RATE
def describe(self):
return self.name + " earns " + str(self.salary)
class Manager(Employee):
def team_report(self, team_size):
return self.name + " manages " + str(team_size) + " people"
dana = Employee("Dana", 12000)
avi = Manager("Avi", 20000)
print(dana.describe())
print(avi.describe())
print(avi.monthly_cost())
print(avi.team_report(5))Sortie :
Dana earns 12000
Avi earns 20000
25000.0
Avi manages 5 peopleAnalyse du code ligne par ligne
| Code | Explication |
|---|---|
class Manager(Employee): | Manager hérite de Employee. |
Manager("Avi", 20000) | Manager n’a pas son propre __init__, donc Python trouve celui d’Employee et l’utilise. Avi reçoit un name et un salary. |
avi.describe() | Il n’y a pas de describe dans Manager. Python remonte à Employee et la trouve. self est toujours Avi. |
avi.monthly_cost() | Pareil, et EMPLOYER_COST_RATE est aussi trouvé via le parent. |
avi.team_report(5) | Une méthode qui n’existe que dans Manager. |
dana.team_report(...) | Lancerait une AttributeError : un employé ordinaire n’hérite pas du manager. L’héritage va de haut en bas seulement. |
Et la correction de 1.25 → 1.3 ? On change une seule ligne, dans Employee, et tous les managers (et à l’avenir, tous les types d’employés) sont mis à jour.
Un autre exemple
Une hiérarchie avec plusieurs enfants : Animal / Dog / Cat. week-3/animals.py :
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
def birthday(self):
self.age = self.age + 1
def describe(self):
return self.name + " (" + str(self.age) + " years old)"
class Dog(Animal):
def fetch(self):
return self.name + " brings back the ball"
class Cat(Animal):
def climb(self):
return self.name + " climbs the tree"
rex = Dog("Rex", 3)
mitzi = Cat("Mitzi", 5)
rex.birthday()
print(rex.describe(), "|", rex.fetch())
print(mitzi.describe(), "|", mitzi.climb())
for animal in [rex, mitzi]:
print(type(animal).__name__, "->", animal.describe())Sortie :
Rex (4 years old) | Rex brings back the ball
Mitzi (5 years old) | Mitzi climbs the tree
Dog -> Rex (4 years old)
Cat -> Mitzi (5 years old)Analyse :
- Deux enfants du même parent :
DogetCatpartagent tout ce qui est commun (name,age,birthday,describe), et chacun ajoute ce qui lui est propre. rex.birthday()— une méthode écrite une seule fois dansAnimal, qui modifie l’état de rex.- Dans la boucle,
animal.describe()fonctionne sur tout type d’Animal, sans vérifier de quel type il s’agit. C’est un avant-goût du polymorphisme, que nous approfondirons dans la leçon suivante. type(animal).__name__— le nom de la classe sous forme de texte.
Animal
/ \
Dog CatRésumé
- L’héritage = une classe enfant reçoit tous les attributs et méthodes d’une classe parente, et peut en ajouter et en modifier.
- Syntaxe :
class Child(Parent):. - Il résout la duplication entre des classes semblables (DRY), et reflète la relation « est une sorte de » (is-a).
- Python cherche une méthode dans la classe de l’objet, et s’il ne l’a pas trouvée, remonte au parent.
- L’héritage est à sens unique : le parent ne sait pas ce qui a été ajouté dans l’enfant.
- N’héritez pas quand la relation est « a un » (has-a). On utilise alors la composition (le thème de la conception orientée objet).
Documentation officielle
Chapitre 2 : La redéfinition et super() — ne changer que ce qui est différent
Qu’allons-nous apprendre ?
super(): comment un enfant utilise le code du parent.__init__dans un enfant : ajouter des attributs sans copier ceux du parent.- La redéfinition de méthode (Method Overriding) : un enfant remplace une méthode du parent.
- L’extension : un enfant ajoute à la méthode du parent, au lieu de la remplacer complètement.
Pourquoi en avons-nous besoin ?
C’est là que l’héritage simple bloque : un Truck qui hérite d’un Vehicle a besoin d’un attribut supplémentaire (cargo_kg), c’est-à-dire de son propre __init__. Mais si nous écrivons un nouveau __init__ dans Truck, il remplace celui de Vehicle, et alors brand et wheels ne seront pas initialisés, à moins de copier le code. Et c’est exactement ce que l’héritage devait éviter.
Et un deuxième problème : parfois l’enfant doit se comporter différemment pour la même action. Un manager reçoit une prime dans le calcul du coût. Un compte d’épargne ne permet pas de retirer en dessous d’un minimum. Il nous faut un moyen de modifier une méthode reçue par héritage.
Explication à partir des bases
La redéfinition de méthode (Method Overriding)
Si dans l’enfant vous définissez une méthode du même nom que dans le parent, la version de l’enfant l’emporte pour les objets de type enfant. C’est ce qu’on appelle une redéfinition (Override). Pourquoi ? Souvenez-vous de l’ordre de recherche : Python cherche d’abord dans la classe de l’objet, et seulement s’il n’a pas trouvé, remonte au parent. Si la méthode existe dans l’enfant, Python n’atteint même jamais la version du parent.
super() — « la version du parent »
super() renvoie un objet spécial à travers lequel on appelle la méthode du parent, sur le même self. Les deux usages principaux :
- Dans
__init__:super().__init__(...)— « initialise d’abord tout ce que le parent initialise », puis ajoute ce qui est propre à l’enfant. - Pour étendre une méthode :
super().describe()— « donne-moi ce que renvoie le parent », puis ajoute-y quelque chose.
Grand-mère (le parent) fait un gâteau au chocolat selon une recette. Vous (l’enfant) héritez de la recette. Redéfinir = vous décidez de faire un gâteau complètement différent sous le même nom, « gâteau au chocolat », mais végan, à partir de zéro. Étendre avec super() = « fais exactement comme grand-mère » (super().bake()), puis ajoute un glaçage que vous avez inventé. Vous ne recopiez pas toute la recette de grand-mère. Vous dites simplement « comme grand-mère, plus… ».
Trois options pour une méthode dans un enfant
| Option | Ce que vous faites | Quand |
|---|---|---|
| Hériter | Vous n’écrivez rien | Le comportement du parent convient |
| Redéfinir | Vous écrivez une méthode du même nom, sans super() | Le comportement est complètement différent |
| Étendre | Vous écrivez une méthode du même nom, et à l’intérieur super().method() | « Comme le parent, plus quelque chose » |
Syntaxe
class Parent:
def __init__(self, a):
self.a = a
def describe(self):
return "a=" + str(self.a)
def greet(self):
return "Hello from Parent"
class Child(Parent):
def __init__(self, a, b):
super().__init__(a) # let Parent set up self.a
self.b = b # then add what is new in Child
def describe(self): # EXTEND: reuse Parent's result
return super().describe() + ", b=" + str(self.b)
def greet(self): # OVERRIDE: replace completely
return "Hello from Child"Remarquez : dans super().__init__(a), on ne passe pas self. super() « sait » déjà de quel objet il s’agit.
Premier exemple de code
Résoudre le problème de Truck. week-3/vehicles_super.py :
class Vehicle:
def __init__(self, brand, wheels):
self.brand = brand
self.wheels = wheels
def describe(self):
return self.brand + " with " + str(self.wheels) + " wheels"
class Truck(Vehicle):
def __init__(self, brand, max_load_kg):
super().__init__(brand, 6)
self.max_load_kg = max_load_kg
self.cargo_kg = 0
def load(self, kg):
if self.cargo_kg + kg > self.max_load_kg:
raise ValueError("over the limit: max " + str(self.max_load_kg) + " kg")
self.cargo_kg = self.cargo_kg + kg
def describe(self):
return super().describe() + ", carrying " + str(self.cargo_kg) + "/" + str(self.max_load_kg) + " kg"
volvo = Truck("Volvo", 1000)
volvo.load(600)
print(volvo.describe())
try:
volvo.load(500)
except ValueError as error:
print("Rejected:", error)
print(volvo.brand, volvo.wheels)Sortie :
Volvo with 6 wheels, carrying 600/1000 kg
Rejected: over the limit: max 1000 kg
Volvo 6Analyse du code ligne par ligne
| Code | Explication |
|---|---|
def __init__(self, brand, max_load_kg): | Truck a son propre __init__, avec des paramètres adaptés à un camion. |
super().__init__(brand, 6) | « Exécute le __init__ de Vehicle sur cet objet ». Cela crée self.brand et self.wheels. Un camion a toujours 6 roues, c’est donc l’enfant qui décide de la valeur. |
self.max_load_kg = ..., self.cargo_kg = 0 | Seulement ce qui est nouveau dans Truck. Sans copier une seule ligne de Vehicle. |
def describe(self): dans Truck | L’extension : le même nom que dans Vehicle. |
super().describe() | Reçoit "Volvo with 6 wheels" du parent, et y ajoute les informations de chargement. |
volvo.brand, volvo.wheels | Ils existent, car super().__init__ les a créés. |
Si Vehicle change demain, par exemple ajoute self.year, les camions le recevront automatiquement via super().__init__. Si nous avions copié le code, il faudrait penser à mettre à jour Truck aussi.
Un autre exemple
Une redéfinition complète, une extension, et super() dans des règles métier. week-3/staff.py :
class Employee:
EMPLOYER_COST_RATE = 1.25
def __init__(self, name, salary):
self.name = name
self.salary = salary
def monthly_cost(self):
return self.salary * Employee.EMPLOYER_COST_RATE
def title(self):
return "Employee"
def describe(self):
return self.title() + " " + self.name + " - cost " + str(self.monthly_cost())
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
def monthly_cost(self):
return super().monthly_cost() + self.bonus
def title(self):
return "Manager"
class Intern(Employee):
def monthly_cost(self):
return self.salary
def title(self):
return "Intern"
staff = [Employee("Dana", 12000), Manager("Avi", 20000, 3000), Intern("Noa", 5000)]
for person in staff:
print(person.describe())
print("Total monthly cost:", sum(person.monthly_cost() for person in staff))Sortie :
Employee Dana - cost 15000.0
Manager Avi - cost 28000.0
Intern Noa - cost 5000
Total monthly cost: 48000.0Analyse :
Manager.monthly_cost— l’extension : « comme un employé ordinaire (super().monthly_cost()= 25 000), plus une prime ». Si le taux du coût employeur change, les managers recevront la modification automatiquement.Intern.monthly_cost— une redéfinition complète : un stagiaire n’entraîne pas de coûts employeur, donc un calcul complètement différent, sanssuper().title()— chaque classe la redéfinit.- Le point intéressant :
describe()n’est écrite qu’une seule fois, dansEmployee. Elle appelleself.title()etself.monthly_cost(). Quand l’objet est un Manager,self.monthly_cost()atteint la version du Manager ! La méthode du parent utilise automatiquement les versions redéfinies de l’enfant. C’est la base du polymorphisme (la leçon suivante). Internn’a pas défini de__init__, il utilise donc celui d’Employee(l’héritage ordinaire).
Résumé
- Redéfinir (Override) = une méthode dans l’enfant du même nom que dans le parent. La version de l’enfant l’emporte.
super()= l’accès à la version du parent, sur le mêmeself.- Un enfant avec son propre
__init__doit appelersuper().__init__(...)(sansself), puis n’ajouter que ce qui est nouveau. - Étendre :
super().method()+ un ajout. Une redéfinition complète : sanssuper(). self.method()dans une méthode redéfinie = une récursion. Pour la version du parent :super().method().- Une méthode du parent qui appelle
self.x()atteindra automatiquement la version redéfinie de l’enfant. - Une méthode redéfinie garde la même signature (les mêmes paramètres).
Documentation officielle
Exercices
Les exercices n’utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d’abord seul, et n’ouvrez la solution qu’ensuite.
Ce que vous pratiquez sur cette page : l’héritage — parent et enfant, le test « est une sorte de », étendre __init__ avec super(), et redéfinir une méthode (Override).
À la fin de la page, vous saurez :
- Écrire un enfant qui hérite d’un parent et n’ajoute que ce qui est nouveau
- Décider quand l’héritage convient, grâce au test « est une sorte de »
- Appeler
super().__init__pour que le parent initialise sa partie - Redéfinir une méthode, et utiliser la version du parent à l’intérieur
Exercice 1: Un employé et un manager
Ce que vous construisez : un employé et un manager — où un manager est une sorte d’employé, pour ne pas réécrire ce qui est commun.
Ce qu’il faut écrire :
- Une classe nommée
Employeeavec un nom et un salaire. - Dans
Employee:describequi renvoie le nom et le salaire, comme dans la sortie. - Une classe nommée
Managerqui hérite d’Employee— sans son propre__init__. - Dans
Manageruniquement :managequi reçoit la taille d’une équipe et renvoie une phrase comme dans la sortie.
Code de test :
dana = Employee("Dana", 12000)
avi = Manager("Avi", 20000)
print(dana.describe())
print(avi.describe())
print(avi.manage(5))La sortie souhaitée :
Dana: 12000
Avi: 20000
Avi manages 5 peopleAfficher la solution
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def describe(self):
return self.name + ": " + str(self.salary)
class Manager(Employee):
def manage(self, team_size):
return self.name + " manages " + str(team_size) + " people"
dana = Employee("Dana", 12000)
avi = Manager("Avi", 20000)
print(dana.describe())
print(avi.describe())
print(avi.manage(5))Dana: 12000
Avi: 20000
Avi manages 5 peopleManager hérite de __init__ et de describe — sans les réécrire — et n’ajoute que ce qui est nouveau.
Exercice 2: is-a — est une sorte de
Ce que vous faites : décider — sans code — quand l’héritage convient.
La tâche : pour chaque paire, répondez : le premier doit-il hériter du second ? Expliquez avec la phrase « X est une sorte de Y » — et si la phrase n’est pas vraie, quelle est en réalité la relation entre eux.
DogetAnimalCaretEngineSavingsAccountetBankAccountSquareetColor
Ce qu’il faut rendre : oui ou non pour chaque paire, et une phrase d’explication.
Afficher la solution
- Oui — un chien **est une sorte d’**animal.
- Non — une voiture n’est pas une sorte de moteur. Une voiture a un moteur (has-a). C’est un cas de composition, pas d’héritage.
- Oui — un compte d’épargne est une sorte de compte bancaire.
- Non — un carré n’est pas une sorte de couleur. Un carré a peut-être une couleur.
L’héritage seulement quand la phrase « X est une sorte de Y » est vraie.
Exercice 3: Un arbre d’animaux
Ce que vous construisez : un petit arbre d’héritage — un animal, et deux sortes d’animaux, chacune avec une action propre.
Ce qu’il faut écrire :
- Une classe nommée
Animalavec un nom et un âge, etdescribequi renvoie le nom et l’âge comme dans la sortie. - Une classe nommée
Dogqui hérite d’Animal, avecfetchqui renvoie la phrase de la sortie. - Une classe nommée
Catqui hérite d’Animal, avecclimbqui renvoie la phrase de la sortie.
Code de test :
rex = Dog("Rex", 4)
mitzi = Cat("Mitzi", 5)
print(rex.describe(), "|", rex.fetch())
print(mitzi.describe(), "|", mitzi.climb())La sortie souhaitée :
Rex (4 years old) | Rex brings back the ball
Mitzi (5 years old) | Mitzi climbs the treeAfficher la solution
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
def describe(self):
return self.name + " (" + str(self.age) + " years old)"
class Dog(Animal):
def fetch(self):
return self.name + " brings back the ball"
class Cat(Animal):
def climb(self):
return self.name + " climbs the tree"
rex = Dog("Rex", 4)
mitzi = Cat("Mitzi", 5)
print(rex.describe(), "|", rex.fetch())
print(mitzi.describe(), "|", mitzi.climb())Rex (4 years old) | Rex brings back the ball
Mitzi (5 years old) | Mitzi climbs the treeExercice 4: super().init
Ce que vous construisez : un camion — un véhicule qui a toujours 6 roues, et qui a aussi un poids de chargement maximum.
Ce qu’il faut écrire :
- Une classe nommée
Vehicleavec une marque et un nombre de roues. - Une classe nommée
Truckqui hérite deVehicle. Son__init__reçoit une marque et un poids maximum. - Dans le
__init__deTruck: appelezsuper().__init__avec la marque et 6 roues, et initialisez un poids actuel de 0. load— reçoit des kilogrammes et les ajoute au poids. Si le maximum est dépassé : uneValueErroravec le message de la sortie.
Code de test :
truck = Truck("Volvo", 1000)
truck.load(600)
print(truck.brand, truck.wheels, truck.load_kg)
try:
truck.load(500)
except ValueError as error:
print("error:", error)La sortie souhaitée :
Volvo 6 600
error: overloaded: max 1000 kgAfficher la solution
class Vehicle:
def __init__(self, brand, wheels):
self.brand = brand
self.wheels = wheels
class Truck(Vehicle):
def __init__(self, brand, max_load_kg):
super().__init__(brand, 6)
self.max_load_kg = max_load_kg
self.load_kg = 0
def load(self, kg):
if self.load_kg + kg > self.max_load_kg:
raise ValueError("overloaded: max " + str(self.max_load_kg) + " kg")
self.load_kg = self.load_kg + kg
truck = Truck("Volvo", 1000)
truck.load(600)
print(truck.brand, truck.wheels, truck.load_kg)
try:
truck.load(500)
except ValueError as error:
print("error:", error)Volvo 6 600
error: overloaded: max 1000 kgsuper().__init__(brand, 6) initialise la partie du parent. Ensuite, l’enfant ajoute ses propres attributs.
Exercice 5: Redéfinir avec super
Ce que vous construisez : un manager dont le coût mensuel est le salaire plus une prime.
Ce qu’il faut écrire :
- Une classe nommée
Employeeavec un nom et un salaire, etmonthly_costqui renvoie le salaire. - Une classe nommée
Managerqui hérite d’Employee, et reçoit aussi une prime. Dans son__init__, appelezsuper().__init__. - Dans
Manager, redéfinissezmonthly_cost: la version du parent, viasuper(), plus la prime.
Code de test :
print(Employee("Dana", 12000).monthly_cost())
print(Manager("Avi", 20000, 3000).monthly_cost())La sortie souhaitée :
12000
23000Afficher la solution
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def monthly_cost(self):
return self.salary
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
def monthly_cost(self):
return super().monthly_cost() + self.bonus
print(Employee("Dana", 12000).monthly_cost())
print(Manager("Avi", 20000, 3000).monthly_cost())12000
23000super().monthly_cost() appelle la version du parent. Si le calcul du salaire de base change, Manager recevra la modification automatiquement.
Mini-projet: Les employés d’une entreprise
Quel système vous construisez : un système d’employés pour une entreprise. L’entreprise a des employés ordinaires, des managers et des stagiaires. Ce sont tous des employés — ils ont un nom et un salaire — mais chaque type s’affiche différemment, et un manager a en plus une prime. Le système affiche chaque employé et calcule le coût mensuel de tous ensemble.
Ce que vous apprendrez du projet :
- Construire un parent avec tout ce qui est commun, et des enfants qui n’ajoutent que ce qui est différent
- Appeler
super().__init__depuis l’enfant - Redéfinir une méthode, et étendre une méthode avec
super() - Voir comment une méthode écrite une seule fois dans le parent fonctionne correctement pour chaque type
Ce qu’il faut construire — étape par étape :
Étape 1 — un employé (Employee). __init__ reçoit un nom et un salaire. Trois méthodes :
title— renvoie le mot Employee ;monthly_cost— renvoie le salaire ;describe— renvoie le nom, le rôle et le coût mensuel, comme dans la sortie. Elle utilisetitleetmonthly_cost.
Étape 2 — un manager (Manager). Hérite d’Employee et reçoit aussi une prime. Dans __init__, appelez super().__init__ et enregistrez la prime. Redéfinissez title — le mot Manager. Étendez monthly_cost : le résultat de super() plus la prime.
Étape 3 — un stagiaire (Intern). Hérite d’Employee et reçoit aussi un nombre de mois de stage. Dans __init__, vérifiez que le nombre est entre 1 et 12 — sinon ValueError — puis appelez super().__init__. Redéfinissez title — le mot Intern. N’écrivez pas monthly_cost ni describe : ils viennent par héritage.
Le message d’erreur — exactement ainsi :
months must be 1-12Code de test :
staff = [
Employee("Dana", 12000),
Manager("Avi", 20000, 3000),
Intern("Noa", 6000, 6),
]
for person in staff:
print(person.describe())
try:
Intern("Tom", 5000, 0)
except ValueError as error:
print("error:", error)
total = 0
for person in staff:
total = total + person.monthly_cost()
print("Total monthly cost:", total)La sortie souhaitée :
Dana (Employee): 12000
Avi (Manager): 23000
Noa (Intern): 6000
error: months must be 1-12
Total monthly cost: 41000À la fin du projet, vous saurez :
- Concevoir une hiérarchie : ce qui monte dans le parent et ce qui reste dans l’enfant
- Distinguer trois options pour une méthode dans un enfant : hériter, redéfinir, ou étendre avec
super() - Comprendre pourquoi
describemontre à chaque employé son propre rôle et son propre coût, alors qu’elle n’a été écrite qu’une fois
Bonus : ajoutez à Manager une méthode manage qui reçoit la taille d’une équipe et renvoie une phrase comme Avi manages 5 people, et affichez-la uniquement pour les managers de la liste.
Afficher la solution
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def title(self):
return "Employee"
def monthly_cost(self):
return self.salary
def describe(self):
return self.name + " (" + self.title() + "): " + str(self.monthly_cost())
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
def title(self):
return "Manager"
def monthly_cost(self):
return super().monthly_cost() + self.bonus
class Intern(Employee):
def __init__(self, name, salary, months):
if months < 1 or months > 12:
raise ValueError("months must be 1-12")
super().__init__(name, salary)
self.months = months
def title(self):
return "Intern"
staff = [
Employee("Dana", 12000),
Manager("Avi", 20000, 3000),
Intern("Noa", 6000, 6),
]
for person in staff:
print(person.describe())
try:
Intern("Tom", 5000, 0)
except ValueError as error:
print("error:", error)
total = 0
for person in staff:
total = total + person.monthly_cost()
print("Total monthly cost:", total)Dana (Employee): 12000
Avi (Manager): 23000
Noa (Intern): 6000
error: months must be 1-12
Total monthly cost: 41000describea été écrite une seule fois dansEmployee, mais chaque employé reçoit son propretitle()et son propremonthly_cost()— car Python cherche la méthode d’abord dans l’enfant.Manager.monthly_costétend :super().monthly_cost()renvoie le salaire, et la prime s’y ajoute.titleredéfinit complètement.Internn’écrit pasmonthly_cost— il en hérite telle quelle.
Bonus :
def manage(self, team_size):
return self.name + " manages " + str(team_size) + " people"(Une méthode dans Manager.)
for person in staff:
if person.title() == "Manager":
print(person.manage(5))Avi manages 5 people