Question 1Quelle est la description la plus précise du polymorphisme ?
Polymorphisme — Même nom de méthode, comportement différent par type
Apprends le polymorphisme Python. Surcharge les méthodes du parent dans les sous-classes pour que les appelants n'aient pas à réfléchir aux types, et remplace les branches if type(...) par de la POO propre — le tout avec des schémas.
La dernière fois on a couvert l'héritage multiple et le MRO. Pour clore la série POO, cet article couvre le troisième pilier — le polymorphisme.
C'est quoi le polymorphisme ?
Le polymorphisme est l'idée que la même interface (nom de méthode) peut faire des choses différentes selon le type. Suppose que tu veux une seule opération « calculer le salaire », mais que tu veux que les employés, les managers et les ingénieurs utilisent des formules différentes.
Définis calculate_salary sur la classe parente Employee, et fais en sorte que les sous-classes Manager et Engineer la surchargent avec leur propre formule. Maintenant le code appelant écrit juste employee.calculate_salary() sans se soucier de quelle classe est laquelle, et le bon calcul tourne.
Employee définit calculate_salary. Les sous-classes Manager / Engineer la surchargent avec leurs propres formules. Même nom de méthode, résultats différents par classe.Bâtir sur le parent, changer la formule dans chaque enfant
Construisons l'exemple du salaire pour de vrai. Employee est le parent, avec Manager (boost selon la taille de l'équipe) et Engineer (boost selon le niveau de compétence) comme enfants. Le point clé est que les trois définissent une méthode du même nom calculate_salary.
class Employee:
def __init__(self, name, base_salary):
self.name = name
self.base_salary = base_salary
def calculate_salary(self): # défaut = salaire de base seulement
return self.base_salary
class Manager(Employee):
def __init__(self, name, base_salary, team_size):
super().__init__(name, base_salary)
self.team_size = team_size
def calculate_salary(self): # bonus selon taille de l'équipe
return self.base_salary + self.team_size * 50000
class Engineer(Employee):
def __init__(self, name, base_salary, skill_level):
super().__init__(name, base_salary)
self.skill_level = skill_level
def calculate_salary(self): # bonus selon niveau de compétence
return self.base_salary + self.skill_level * 20000
emp — c'est la puissance du polymorphisme.Rassembler dans une liste — Une boucle qui se fiche des types
Le polymorphisme montre vraiment sa force quand tu mets des objets de types différents dans une seule liste et les traites tous ensemble. Enveloppe le calcul de salaire dans une classe PayrollSystem et le corps de la boucle se réduit à une seule ligne.
class PayrollSystem:
def __init__(self):
self.employees = []
def add(self, employee):
self.employees.append(employee)
def total(self):
result = 0
for emp in self.employees: # les types peuvent varier
result += emp.calculate_salary() # même nom fonctionne
return result
payroll = PayrollSystem()
payroll.add(Employee("Alice", 300000))
payroll.add(Manager("Léa", 800000, 8))
payroll.add(Engineer("Hugo", 300000, 4))
print(payroll.total()) # 1880000
employees = [...](types mélangés)for emp in self.employees:les parcourt- Appelle juste emp.calculate_salary()
- retourne base_salary
- base + team * 50k
- base + skill * 20k
PayrollSystem appelle simplement le même nom de méthode et le bon calcul tourne par type.Ce que ça donne sans polymorphisme
Essaie de faire la même chose sans polymorphisme et tu finis à écrire des branches if type(emp) == ...:. Ça fonctionne, mais chaque nouveau rôle veut dire une autre branche if — et le moment où tu en oublies une, tu as un bug.
# (MAUVAIS) sans polymorphisme (branchement par type)
def calc(emp):
if type(emp) is Manager:
return emp.base_salary + emp.team_size * 50000
elif type(emp) is Engineer:
return emp.base_salary + emp.skill_level * 20000
else:
return emp.base_salary
# (BON) avec polymorphisme (logique poussée dans les classes)
def calc(emp):
return emp.calculate_salary() # une ligne
if type sur l'appelant et grossit avec chaque nouveau type. Le polymorphisme garde l'appelant sur une seule ligne — ajouter un nouveau type signifie juste ajouter une classe.« L'appelant ne se soucie pas des types » — c'est le mantra
Un test fiable pour savoir si ton design est polymorphique : est-ce que le code de l'appelant a des piles de if type(...) ou if isinstance(...) ? Si oui, le refactor standard est de déplacer ce branchement dans des surcharges de méthodes sur les classes. « Ajouter une classe » et « ajouter un if » ont tendance à être un compromis.
Duck typing — Tout ce qu'il faut, c'est le même nom de méthode
Le polymorphisme de Python a aussi une version plus permissive : le duck typing. Le dicton « si ça marche comme un canard et que ça cancane comme un canard, c'est un canard » se transforme en « si une classe a la bonne méthode, peu importe quelle classe c'est ».
Dans l'exemple ci-dessous, Cat et Dog ne partagent pas du tout un parent Animal — mais tant que les deux ont une méthode speak(), la même fonction gère les deux. Python privilégie « est-ce qu'elle a la méthode ? » sur le graphe d'héritage.
class Cat:
def speak(self):
return "Miaou"
class Dog:
def speak(self):
return "Ouaf"
def shout(animal): # le type n'est pas imposé
print(animal.speak())
shout(Cat()) # Miaou
shout(Dog()) # Ouaf
Des langages comme Java ou C# exigent une classe parente partagée pour que le polymorphisme fonctionne. Python est content tant que la méthode est là au moment de l'appel. Ça te donne de la flexibilité, mais « s'assurer que les noms de méthodes ont la même signification entre classes » devient la responsabilité de l'appelant — garde ça en tête.
Les deux designs sur une seule diapo
- Code de l'appelant — une ligne
emp.calculate_salary() - Ajouter un nouveau type — juste écrire une nouvelle classe avec
calculate_salary - Rayon d'impact — reste à l'intérieur des classes
- Lisibilité — « même nom, varie par type » suffit à suivre
- Code de l'appelant — pile de
if type(emp) is ... - Ajouter un nouveau type — chaque branche doit être revue
- Rayon d'impact — déborde sur l'appelant
- Lisibilité — tu relis les branches à chaque fois
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Le polymorphisme tend à éliminer quelle structure du code de l'appelant ?
Question 3Quelle est la meilleure description du duck typing de Python ?