Em Python, a herança múltipla é uma característica onde uma classe pode herdar atributos e métodos de mais de uma classe pai. Isso permite que você crie uma classe que combina comportamentos ou atributos de múltiplas outras classes, o que pode ser bastante poderoso. No entanto, com grande poder vem grande complexidade, e a herança múltipla deve ser usada com cautela.
Para entender a herança múltipla, vamos considerar um exemplo de um sistema de arquivos multimídia. Suponha que temos uma classe Video e uma classe Audio. Agora, queremos criar uma classe para arquivos multimídia que tenham tanto vídeo quanto áudio. Podemos conseguir isso usando herança múltipla:
class Video: # Definir uma classe chamada 'Video'
def __init__(self):
self.video_codec = 'H.264'
def play_video(self):
return 'Playing video...'
class Audio: # Definir uma classe chamada 'Audio'
def __init__(self):
self.audio_codec = 'AAC'
def play_audio(self):
return 'Playing audio...'
class Multimedia(Video, Audio): # Definir uma subclasse chamada 'Multimedia' que herda de 'Video' e 'Audio'
def __init__(self):
Video.__init__(self) # Chamar o método '__init__' da classe pai 'Video'
Audio.__init__(self) # Chamar o método '__init__' da classe pai 'Audio'
def play(self):
return self.play_video() + ' ' + self.play_audio() # Reproduzir vídeo e áudio
multimedia = Multimedia() # Criar uma instância da classe 'Multimedia'
print(multimedia.play()) # Saída: 'Playing video... Playing audio...'
Neste exemplo, a classe Multimedia herda das classes Video e Audio. Chamamos os construtores das classes Video e Audio no construtor de Multimedia para inicializar adequadamente todos os atributos da instância de Multimedia. O método play() da classe Multimedia combina o método play_video() de Video e o método play_audio() de Audio.
A herança múltipla fornece uma maneira de combinar comportamentos de várias classes. No entanto, deve ser usada com cuidado, pois pode levar a algumas situações complexas.
🤔
Se as classes pai tiverem métodos com os mesmos nomes, a ordem da herança importa, já que o Python irá procurar pelos métodos na ordem das classes pai listadas na definição da subclasse.
Ordem de Resolução 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.
O Python usa um algoritmo chamado linearização C3, ou simplesmente C3, para determinar a ordem de resolução de métodos quando classes herdam de múltiplos pais. A ideia é preservar a ordem em que as classes são definidas na declaração da subclasse, mantendo também a ordem das classes pai. No entanto, sem entrar nas complexidades do algoritmo, você pode facilmente verificar a MRO de uma classe usando o método mro():
Desafio: A Classe Frog
Em uma terra mágica da programação, existem várias classes de animais: Fish, WaterAnimal e LandAnimal.
Fish, que herda de WaterAnimal e tem um método swim().
WaterAnimal tem um método live_in_water().
LandAnimal tem um método live_on_land().
Sua tarefa é criar uma classe Frog que herde de WaterAnimal e LandAnimal. Esta classe deve ser capaz de chamar todos os métodos de suas classes pai.
Cada classe deve ter um atributo species que deve ser especificado quando um objeto é criado.
Todos os métodos, quando invocados, devem imprimir uma mensagem apropriada que indique a ação e o nome da espécie (veja o exemplo abaixo para o formato).