In Python, l'ereditarietà multipla è una caratteristica che permette a una classe di ereditare attributi e metodi da più di una classe padre. Questo consente di creare una classe che combina comportamenti o attributi da diverse altre classi, il che può essere molto potente. Tuttavia, con il potere arriva la complessità, e l'ereditarietà multipla dovrebbe essere usata con giudizio.
Per comprendere l'ereditarietà multipla, consideriamo un esempio di un sistema di file multimediali. Supponiamo di avere una classe Video e una classe Audio. Ora, vogliamo creare una classe per file multimediali che abbiano sia video che audio. Possiamo ottenere questo usando l'ereditarietà multipla:
class Video: # Definisce una classe chiamata 'Video'
def __init__(self):
self.video_codec = 'H.264'
def play_video(self):
return 'Playing video...'
class Audio: # Definisce una classe chiamata 'Audio'
def __init__(self):
self.audio_codec = 'AAC'
def play_audio(self):
return 'Playing audio...'
class Multimedia(Video, Audio): # Definisce una sottoclasse chiamata 'Multimedia' che eredita da 'Video' e 'Audio'
def __init__(self):
Video.__init__(self) # Chiama il metodo '__init__' della classe padre 'Video'
Audio.__init__(self) # Chiama il metodo '__init__' della classe padre 'Audio'
def play(self):
return self.play_video() + ' ' + self.play_audio() # Riproduce sia video che audio
multimedia = Multimedia() # Crea un'istanza della classe 'Multimedia'
print(multimedia.play()) # Output: 'Playing video... Playing audio...'
In questo esempio, la classe Multimedia eredita sia dalla classe Video che dalla classe Audio. Nel costruttore di Multimedia, chiamiamo i costruttori delle classi Video e Audio per inizializzare correttamente tutti gli attributi dell'istanza di Multimedia. Il metodo play() della classe Multimedia combina il metodo play_video() di Video e il metodo play_audio() di Audio.
L'ereditarietà multipla fornisce un modo per combinare comportamenti da più classi. Tuttavia, dovrebbe essere usata con attenzione poiché può portare a situazioni complesse.
🤔
Se le classi padre hanno metodi con lo stesso nome, l'ordine di ereditarietà è importante, poiché Python cercherà i metodi nell'ordine delle classi padre elencate nella definizione della sottoclasse.
Ordine di Risoluzione dei Metodi
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 utilizza un algoritmo chiamato linearizzazione C3, o semplicemente C3, per determinare l'ordine di risoluzione dei metodi quando le classi ereditano da più genitori. L'idea è di preservare l'ordine in cui le classi sono definite nella dichiarazione della sottoclasse, mantenendo anche l'ordine dalle classi padre. Tuttavia, senza entrare nelle complessità dell'algoritmo, puoi facilmente controllare il MRO per una classe usando il metodo mro():
Sfida: La Classe Frog
In una terra magica della programmazione, ci sono diverse classi di animali: Fish, WaterAnimal e LandAnimal.
Fish, che eredita da WaterAnimal e ha un metodo swim().
WaterAnimal ha un metodo live_in_water().
LandAnimal ha un metodo live_on_land().
Il tuo compito è creare una classe Frog che eredita sia da WaterAnimal che da LandAnimal. Questa classe dovrebbe essere in grado di chiamare tutti i metodi delle sue classi padre.
Ogni classe dovrebbe avere un attributo species che deve essere specificato quando viene creato un oggetto.
Tutti i metodi, quando invocati, dovrebbero stampare un messaggio appropriato che indica l'azione e il nome della specie (vedi l'esempio sotto per il formato).