In Python ist Mehrfachvererbung eine Funktionalität, bei der eine Klasse Attribute und Methoden von mehr als einer Elternklasse erben kann. Damit lassen sich Klassen erstellen, die mehrere Verhaltensweisen oder Eigenschaften anderer Klassen kombinieren – was sehr mächtig sein kann. Mit dieser Macht geht allerdings auch Komplexität einher, und man sollte Mehrfachvererbung daher mit Bedacht einsetzen.
Um Mehrfachvererbung besser zu verstehen, betrachten wir ein Beispiel für ein Multimedia-Dateisystem. Angenommen, wir haben eine Klasse Video und eine Klasse Audio. Jetzt möchten wir eine Klasse für Multimediadateien erstellen, die sowohl Video- als auch Audiofunktionen besitzen. Das erreichen wir mittels Mehrfachvererbung:
class Video: # Definiert eine Klasse namens 'Video'
def __init__(self):
self.video_codec = 'H.264'
def play_video(self):
return 'Playing video...'
class Audio: # Definiert eine Klasse namens 'Audio'
def __init__(self):
self.audio_codec = 'AAC'
def play_audio(self):
return 'Playing audio...'
class Multimedia(Video, Audio): # Definiert eine Unterklasse namens 'Multimedia', die von 'Video' und 'Audio' erbt
def __init__(self):
Video.__init__(self) # Ruft die '__init__'-Methode der Elternklasse 'Video' auf
Audio.__init__(self) # Ruft die '__init__'-Methode der Elternklasse 'Audio' auf
def play(self):
return self.play_video() + ' ' + self.play_audio() # Spielt sowohl Video als auch Audio ab
multimedia = Multimedia() # Erzeugt eine Instanz der Klasse 'Multimedia'
print(multimedia.play()) # Output: 'Playing video... Playing audio...'
In diesem Beispiel erbt die Klasse Multimedia sowohl von Video als auch von Audio. Im Konstruktor von Multimedia werden die Konstruktoren von Video und Audio aufgerufen, um sämtliche Attribute der Multimedia-Instanz korrekt zu initialisieren. Die Methode play() in der Klasse Multimedia kombiniert dann die Methode play_video() aus Video mit der Methode play_audio() aus Audio.
Die Mehrfachvererbung bietet also eine Möglichkeit, das Verhalten mehrerer Klassen zu vereinen. Allerdings sollte man sie mit Vorsicht einsetzen, da sie zu komplexen Situationen führen kann.
🤔
Wenn die Elternklassen Methoden mit identischen Namen haben, ist die Reihenfolge der Vererbung wichtig, weil Python die Methoden in der Reihenfolge der Elternklassen im Subklassendefinitionskopf sucht.
Method Resolution Order
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 nutzt einen Algorithmus namens C3-Linearization (oft einfach C3 genannt), um die Method Resolution Order zu bestimmen, wenn eine Klasse von mehreren Eltern erbt. Das Ziel besteht darin, die Reihenfolge der Definition in der Subklasse beizubehalten und gleichzeitig die Reihenfolge der Elternklassen zu berücksichtigen. Ohne zu sehr ins Detail zu gehen, kann man die MRO einer Klasse leicht mit der Methode mro() überprüfen:
Challenge: The Frog Class
In einem magischen Programmierland gibt es verschiedene Tierklassen: Fish, WaterAnimal und LandAnimal.
Fish, der von WaterAnimal erbt und die Methode swim() besitzt.
WaterAnimal mit einer Methode live_in_water().
LandAnimal mit einer Methode live_on_land().
Deine Aufgabe besteht darin, eine Klasse Frog zu erstellen, die sowohl von WaterAnimal als auch von LandAnimal erbt. Sie soll in der Lage sein, alle Methoden ihrer Elternklassen aufzurufen.
Jede Klasse sollte ein Attribut species besitzen, das beim Erzeugen eines Objekts festgelegt wird.
Alle Methoden sollten beim Aufruf eine passende Nachricht ausgeben, in der Aktion und Art (Species) genannt werden (sieh dir unten das Beispiel für das Format an).