En Python, l'héritage multiple est une fonctionnalité qui permet à une classe d'hériter des attributs et des méthodes de plusieurs classes parentes. Cela vous permet de créer une classe qui combine les comportements ou attributs de plusieurs autres classes, ce qui peut être très puissant. Cependant, avec ce pouvoir vient la complexité, et l'héritage multiple doit être utilisé avec discernement.
Pour comprendre l'héritage multiple, considérons un exemple de système de fichiers multimédia. Supposons que nous ayons une classe Video et une classe Audio. Maintenant, nous voulons créer une classe pour les fichiers multimédias qui ont à la fois de la vidéo et de l'audio. Nous pouvons réaliser cela en utilisant l'héritage multiple :
class Video: # Définir une classe nommée 'Video'
def __init__(self):
self.video_codec = 'H.264'
def play_video(self):
return 'Playing video...'
class Audio: # Définir une classe nommée 'Audio'
def __init__(self):
self.audio_codec = 'AAC'
def play_audio(self):
return 'Playing audio...'
class Multimedia(Video, Audio): # Définir une sous-classe nommée 'Multimedia' qui hérite de 'Video' et 'Audio'
def __init__(self):
Video.__init__(self) # Appeler la méthode '__init__' de la classe parente 'Video'
Audio.__init__(self) # Appeler la méthode '__init__' de la classe parente 'Audio'
def play(self):
return self.play_video() + ' ' + self.play_audio() # Jouer à la fois la vidéo et l'audio
multimedia = Multimedia() # Créer une instance de la classe 'Multimedia'
print(multimedia.play()) # Sortie : 'Playing video... Playing audio...'
Dans cet exemple, la classe Multimedia hérite à la fois des classes Video et Audio. Nous appelons les constructeurs des classes Video et Audio dans le constructeur de Multimedia pour initialiser correctement tous les attributs de l'instance Multimedia. La méthode play() de la classe Multimedia combine la méthode play_video() de Video et la méthode play_audio() de Audio.
L'héritage multiple fournit un moyen de combiner les comportements de plusieurs classes. Cependant, il doit être utilisé avec précaution car il peut mener à des situations complexes.
🤔
Si les classes parentes ont des méthodes portant le même nom, l'ordre d'héritage est important, car Python recherchera les méthodes dans l'ordre des classes parentes listées dans la définition de la sous-classe.
Ordre de résolution des méthodes
Python uses an algorithm called C3 linearization, or just C3, to determine the method resolution order when classes inherit from multiple parents. The idea is to preserve the order in which classes are defined in the subclass declaration, while also maintaining the order from the parent classes. However, without getting into the complexities of the algorithm, you can easily check the MRO for a class using the mro() method:
class A: # Define a class named 'A'
def process(self):
return 'A process'
class B(A): # Define a class named 'B' that inherits from 'A'
def process(self):
return 'B process'
class C(A): # Define a class named 'C' that inherits from 'A'
def process(self):
return 'C process'
class D(B, C): # Define a class named 'D' that inherits from 'B' and 'C'
pass
print(D.mro()) # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
In the example above, the MRO for class D is D -> B -> C -> A -> object. This means that when you call a method on an instance of D, Python first looks at the method in D. If it doesn't find it there, it moves on to B, then C, and then A.
If you want to call a specific parent class's method, you can directly call it using the parent class's name. However, this is generally not recommended because it can lead to code that is hard to maintain.
Let's modify class D to call the process method of class A:
class D(B, C): # Define a class named 'D' that inherits from 'B' and 'C'
def process(self):
return A.process(self)
d = D() # Create an instance of the 'D' class
print(d.process()) # A process
In the example above, even though class D inherits from B and C (both of which have a process method), we specifically call the process method from class A. This ensures that no matter what the MRO is, class A's process method is used.
Notice how the self is passed explicitly as an argument. In Python, self is a reference to the current instance of the class and is used to access variables and methods associated with that instance. It's implicitly passed as the first argument to instance methods. When we're calling A.process(self), we're saying "Call the process method of class A on this specific instance (represented by self)". Even though the method is defined in A, it's being executed in the context of the instance of class D. This means it has access to any attributes or methods defined in class D or its parent classes. In short, passing self to A.process allows us to invoke the process method from A on the instance of D.
Python utilise un algorithme appelé linéarisation C3, ou simplement C3, pour déterminer l'ordre de résolution des méthodes lorsque des classes héritent de plusieurs parents. L'idée est de préserver l'ordre dans lequel les classes sont définies dans la déclaration de la sous-classe, tout en maintenant l'ordre des classes parentes. Toutefois, sans entrer dans les complexités de l'algorithme, vous pouvez facilement vérifier le MRO d'une classe en utilisant la méthode mro() :
Défi : La classe Frog
Dans un pays magique de programmation, il existe plusieurs classes d'animaux : Fish, WaterAnimal et LandAnimal.
Fish, qui hérite de WaterAnimal et possède une méthode swim().
WaterAnimal a une méthode live_in_water().
LandAnimal a une méthode live_on_land().
Votre tâche est de créer une classe Frog qui hérite à la fois de WaterAnimal et LandAnimal. Cette classe doit pouvoir appeler toutes les méthodes de ses classes parentes.
Chaque classe doit avoir un attribut species qui doit être spécifié lors de la création d'un objet.
Toutes les méthodes, lorsqu'elles sont invoquées, doivent afficher un message approprié qui indique l'action et le nom de l'espèce (voir l'exemple ci-dessous pour le format).