En Python, la herencia múltiple es una característica que permite que una clase herede atributos y métodos de más de una clase padre. Esto te da la posibilidad de crear una clase que combine comportamientos o atributos de varias otras clases, lo cual puede resultar muy útil. Sin embargo, con esa flexibilidad también viene la complejidad, por lo que se debe utilizar la herencia múltiple de forma cuidadosa.
Para comprender la herencia múltiple, consideremos un ejemplo de un sistema de archivos multimedia. Supongamos que tenemos una clase Video y una clase Audio. Ahora queremos crear una clase para archivos multimedia que incluyan tanto video como audio. Podemos hacerlo usando herencia múltiple:
class Video: # Define una clase llamada 'Video'
def __init__(self):
self.video_codec = 'H.264'
def play_video(self):
return 'Playing video...'
class Audio: # Define una clase llamada 'Audio'
def __init__(self):
self.audio_codec = 'AAC'
def play_audio(self):
return 'Playing audio...'
class Multimedia(Video, Audio): # Define una subclase llamada 'Multimedia' que hereda de 'Video' y 'Audio'
def __init__(self):
Video.__init__(self) # Llama al método '__init__' de la clase padre 'Video'
Audio.__init__(self) # Llama al método '__init__' de la clase padre 'Audio'
def play(self):
return self.play_video() + ' ' + self.play_audio() # Reproduce tanto video como audio
multimedia = Multimedia() # Crea una instancia de la clase 'Multimedia'
print(multimedia.play()) # Salida: 'Playing video... Playing audio...'
En este ejemplo, la clase Multimedia hereda de las clases Video y Audio. Llamamos a los constructores de Video y Audio dentro del constructor de Multimedia para inicializar correctamente todos los atributos de la instancia de Multimedia. El método play() de la clase Multimedia combina el método play_video() de Video y el método play_audio() de Audio.
La herencia múltiple ofrece una forma de combinar comportamientos de diferentes clases. No obstante, se debe usar con precaución, ya que puede generar situaciones complejas.
🤔
Si las clases padre tienen métodos con los mismos nombres, el orden de la herencia importa, puesto que Python buscará los métodos en el orden de las clases padre que figuran en la definición de la subclase.
Orden de Resolución de Métodos
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 utiliza un algoritmo llamado C3 linearization, o simplemente C3, para determinar el orden de resolución de métodos cuando las clases heredan de múltiples padres. La idea es preservar el orden en que se definen las clases en la declaración de la subclase, y al mismo tiempo mantener el orden de las clases padre. Sin entrar en los detalles del algoritmo, puedes verificar fácilmente el MRO de una clase usando el método mro():
Desafío: La Clase Frog
En una tierra mágica de programación, existen varias clases de animales: Fish, WaterAnimal y LandAnimal.
Fish, que hereda de WaterAnimal y tiene un método swim().
WaterAnimal tiene un método live_in_water().
LandAnimal tiene un método live_on_land().
Tu tarea es crear una clase Frog que herede tanto de WaterAnimal como de LandAnimal. Esta clase debe poder llamar a todos los métodos de sus clases padre.
Cada clase debe tener un atributo species que se especifique al crear un objeto.
Todos los métodos, al ser invocados, deben imprimir un mensaje adecuado que indique la acción y el nombre de la especie (consulta el ejemplo a continuación para ver el formato).