A herança é um dos pilares da Progrmaação Orientada a Objetos (POO). Em Python, ela permite que uma nova classe, chamada de classe derivada ou subclasse, herde atributos e métodos de uma ou mais classes existentes, conhecidas como classes base ou superclasses.
Tipos de Herança
Python suporta nativamente dois modelos principais de herança: a herança simples (um único pai) e a herança múltipla (múltiplos pais).
class BasePrincipal:
def exibir_origem(self):
print("Método da BasePrincipal")
class BaseSecundaria:
def exibir_info(self):
print("Método da BaseSecundaria")
# Herança Simples
class DerivadaSimples(BasePrincipal):
pass
# Herança Múltipla
class DerivadaMultipla(BasePrincipal, BaseSecundaria):
pass
Para inspecionar as classes das quais uma subclasse herda, utilizamos o atributo especial __bases__, que retorna uma tupla com as classes pai.
print(DerivadaSimples.__bases__)
# Saída: (<class '__main__.BasePrincipal'>,)
print(DerivadaMultipla.__bases__)
# Saída: (<class '__main__.BasePrincipal'>, <class '__main__.BaseSecundaria'>)
Abstração e Reaproveitamento de Código
A herança surge de um processo de abstração, onde identificamos características comuns entre diferentes objetos e as agrupamos em uma estrutura genérica. Isso evita a duplicidade de código e facilita a manutenção.
Considere o exemplo abaixo, onde definimos uma base para dispositivos eletrônicos antes de especializá-los:
class Dispositivo:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
def ligar(self):
print(f"{self.modelo} está ligado.")
class Smartphone(Dispositivo):
def __init__(self, marca, modelo, sistema_operacional):
# Chamada explícita ao construtor da classe base
Dispositivo.__init__(self, marca, modelo)
self.sistema_operacional = sistema_operacional
class Laptop(Dispositivo):
def __init__(self, marca, modelo, memoria_ram):
Dispositivo.__init__(self, marca, modelo)
self.memoria_ram = memoria_ram
celular = Smartphone("Apple", "iPhone 13", "iOS")
computador = Laptop("Dell", "XPS 15", "32GB")
print(celular.marca)
print(computador.modelo)
Especialização e Sobrescrita (Overriding)
Uma subclasse pode introduzir novos comportamentos (métodos derivados) ou modificar comportamentos existentes da classe pai (sobrescrita). Quando um método com o mesmo nome existe tanto na classe pai quanto na filha, a versão da classe filha tem prioridade durante a execução.
O uso da função super()
Embora possamos chamar métodos da classe pai usando o nome da classe, a forma idiomática e recomendada em Python 3 é utilizar a função super(). Ela garante a resolução correta dos métodos, especialmente em hieraruqias complexas.
class Atleta:
def treinar(self):
print("Realizando aquecimento físico.")
class Corredor(Atleta):
def treinar(self):
super().treinar()
print("Iniciando corrida de 10km.")
atleta = Corredor()
atleta.treinar()
# Saída:
# Realizando aquecimento físico.
# Iniciando corrida de 10km.
O super() facilita a manutenção, pois não exige que o programador referencie explicitamente o nome da classe pai no corpo dos métodos.
Herança Múltipla e Method Resolution Order (MRO)
Diferente de linguagens como Java, Python permite que uma classe herde de várias outras. Isso introduz a necessidade de uma ordem clara de busca para atributos e métodos, conhecida como MRO (Ordem de Resolução de Métodos).
Em Python 3, o algoritmo utilizado é o C3 Linearization, que prioriza uma busca em largura estruturada para resolver o "problema do diamante".
class X:
def rotina(self):
print("Rotina X")
class Y(X):
def rotina(self):
print("Rotina Y")
class Z(X):
def rotina(self):
print("Rotina Z")
class Final(Y, Z):
pass
obj = Final()
obj.rotina()
# Para verificar a ordem de busca:
print(Final.mro())
A ordem de busca segue o caminho: Final -> Y -> Z -> X -> object. Isso significa que, se Final não implementar rotina, Python buscará em Y. Se não estiver em Y, buscará em Z, e assim por diante.
Conclusão da Estrutura de Herança
A herança estabelece uma relação do tipo "é um" (ex: um Carro é um Veículo). Seus principais benefícios incluem a redução de redundância, a melhoria na legibilidade do projeto e a criação de padrões extensíveis. Os conceitos-chave para dominar essa técnica são:
- Abstração: Generalizar características comuns.
- Herança: Estender classes base para criar novas funcionalidades.
- Derivação: Adicionar especificidades que não existem na classe original.
- MRO: Entender a hierarquia de execução em sistemsa complexos.