Herança permite criar uma nova classe a partir de uma existente.
A classe filha herda atributos e métodos da classe mãe.
Primeiramente, isso promove reutilização de código e hierarquias.
Por exemplo, Caminhao e Moto herdam de Veiculo.
Além disso, a classe filha pode sobrescrever ou adicionar novos métodos.
A voz passiva é usada aqui: “os métodos são herdados automaticamente”.
Quando utilizar herança? Quando há uma relação “é um” entre conceitos.
Por exemplo, “Gato é um Animal” e “Cachorro é um Animal”.
Python suporta herança simples (uma mãe) e múltipla (várias mães).
Vamos explorar ambos os tipos com exemplos práticos.
Três subtítulos guiarão você pelo universo da herança.
Ao final, você dominará hierarquias de classes em Python.
Herança simples: estendendo classes
Herança simples envolve apenas uma classe mãe.
Use class Filha(Mae): para declarar herança.
O método super() chama o construtor da classe mãe.
Quando usar herança simples? Em hierarquias lineares claras.
Por exemplo, sistemas de funcionários ou formas geométricas.
A voz passiva é aplicada: “os atributos são inicializados pela mãe”.
Exemplo de herança simples:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# Classe base (mãe) class Animal: """Classe base para todos os animais.""" def __init__(self, nome, idade): self.nome = nome self.idade = idade def fazer_som(self): return "Som genérico de animal" def mover(self): return f"{self.nome} está se movendo" def __str__(self): return f"Animal: {self.nome}, {self.idade} anos" # Classe filha (herda de Animal) class Cachorro(Animal): """Cachorro herda de Animal e adiciona comportamentos específicos.""" def __init__(self, nome, idade, raca): # Chama o construtor da classe mãe super().__init__(nome, idade) self.raca = raca # Sobrescrevendo o método da mãe (override) def fazer_som(self): return "Au au au!" # Método exclusivo da classe filha def abanar_rabo(self): return f"{self.nome} está abanando o rabo" def __str__(self): return f"Cachorro: {self.nome} ({self.raca}), {self.idade} anos" class Gato(Animal): """Gato herda de Animal.""" def __init__(self, nome, idade, cor): super().__init__(nome, idade) self.cor = cor def fazer_som(self): return "Miau miau!" def arranhar(self): return f"{self.nome} está arranhando o sofá" def __str__(self): return f"Gato: {self.nome} ({self.cor}), {self.idade} anos" # Demonstração print("=== Herança Simples ===\n") animal = Animal("Bicho", 5) cachorro = Cachorro("Rex", 3, "Labrador") gato = Gato("Mimi", 2, "Branco") print(f"Animal: {animal.fazer_som()}") print(f"Cachorro: {cachorro.fazer_som()}") print(f"Gato: {gato.fazer_som()}") print(f"\n{cachorro.abanar_rabo()}") print(f"{gato.arranhar()}") print(f"{cachorro.mover()}") # Método herdado print(f"\n{animal}") print(cachorro) print(gato) # Verificando tipos print(f"\nCachorro é Animal? {isinstance(cachorro, Animal)}") print(f"Animal é Cachorro? {isinstance(animal, Cachorro)}") print(f"Cachorro é subclasse de Animal? {issubclass(Cachorro, Animal)}") # Exemplo com métodos da mãe ainda disponíveis class Passaro(Animal): def __init__(self, nome, idade, envergadura): super().__init__(nome, idade) self.envergadura = envergadura # Não sobrescreve fazer_som, usa o da mãe def voar(self): return f"{self.nome} está voando com envergadura de {self.envergadura}cm" passaro = Passaro("Piu", 1, 25) print(f"\nPássaro: {passaro.fazer_som()}") # Usa método da mãe print(passaro.voar()) |
A classe filha herda tudo que não foi sobrescrito.
super() é essencial para inicializar corretamente a mãe.
Herança múltipla: combinando comportamentos
Herança múltipla permite uma classe herdar de várias mães.
Use class Filha(Mae1, Mae2): para declarar.
Python usa o MRO (Method Resolution Order) para resolver conflitos.
Quando usar herança múltipla? Em mixins e interfaces.
Por exemplo, combinando comportamentos como “Voador” e “Nadador”.
A voz passiva é aplicada: “as classes são pesquisadas em ordem específica”.
Exemplo de herança múltipla:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# Classes base (mixins) class Voador: """Mixin para seres que voam.""" def __init__(self, altitude_maxima=100): self.altitude_maxima = altitude_maxima def voar(self): return f"Voando até {self.altitude_maxima} metros" def planar(self): return "Planando suavemente" class Nadador: """Mixin para seres que nadam.""" def __init__(self, profundidade_maxima=50): self.profundidade_maxima = profundidade_maxima def nadar(self): return f"Nadando até {self.profundidade_maxima} metros" def mergulhar(self): return "Mergulhando profundamente" class Corredor: """Mixin para seres que correm.""" def __init__(self, velocidade_maxima=30): self.velocidade_maxima = velocidade_maxima def correr(self): return f"Correndo a {self.velocidade_maxima} km/h" def acelerar(self): return "Acelerando rapidamente" # Classe que combina múltiplas habilidades class Pato(Voador, Nadador, Corredor): """Pato herda de Voador, Nadador e Corredor.""" def __init__(self, nome, altitude=100, profundidade=50, velocidade=20): # Inicializa todos os mixins Voador.__init__(self, altitude) Nadador.__init__(self, profundidade) Corredor.__init__(self, velocidade) self.nome = nome def fazer_som(self): return "Quack quack!" def __str__(self): return f"Pato: {self.nome}" # Demonstração print("\n=== Herança Múltipla ===\n") pato = Pato("Donald", altitude=80, profundidade=30, velocidade=15) print(f"{pato}") print(f"{pato.fazer_som()}") print(f"{pato.voar()}") print(f"{pato.planar()}") print(f"{pato.nadar()}") print(f"{pato.mergulhar()}") print(f"{pato.correr()}") print(f"{pato.acelerar()}") # Exemplo de mixins em sistemas de arquivos class Serializable: """Mixin para serialização.""" def to_dict(self): return {k: v for k, v in self.__dict__.items()} def to_json(self): import json return json.dumps(self.to_dict()) class Loggable: """Mixin para logging.""" def log(self, mensagem): print(f"[LOG] {self.__class__.__name__}: {mensagem}") class Personagem(Serializable, Loggable): """Personagem com habilidades de serialização e log.""" def __init__(self, nome, nivel): self.nome = nome self.nivel = nivel def subir_nivel(self): self.nivel += 1 self.log(f"Subiu para nível {self.nivel}") print("\n=== Mixins em Ação ===") personagem = Personagem("Aragorn", 10) personagem.subir_nivel() print(f"Personagem em JSON: {personagem.to_json()}") # Demonstração do MRO (Method Resolution Order) print(f"\nMRO do Pato: {[c.__name__ for c in Pato.__mro__]}") |
Herança múltipla é poderosa, mas use com moderação. Mixins são classes pequenas que adicionam comportamentos específicos.
Sobrescrita e super() em herança múltipla
super() funciona de forma especial em herança múltipla.
Ele segue o MRO para chamar o próximo método na hierarquia.
Isso permite que todos os pais sejam inicializados corretamente.
Quando usar super()? Em praticamente todos os construtores.
Também em métodos que querem estender (não substituir) o comportamento.
A voz passiva é aplicada: “as chamadas são delegadas ao próximo na MRO”.
Exemplo de super() em herança múltipla:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# Demonstração do fluxo de super() class A: def __init__(self): print("Inicializando A") self.valor_a = "A" def metodo(self): print("Método de A") return "A" class B: def __init__(self): print("Inicializando B") self.valor_b = "B" def metodo(self): print("Método de B") return "B" class C(A, B): def __init__(self): print("Iniciando C") super().__init__() # Chama o próximo no MRO (A) # A já inicializou, agora precisamos inicializar B manualmente? # Na verdade, A não chama B. Por isso, usamos super() em todas. print("C inicializado") def metodo(self): print("Início do método de C") resultado = super().metodo() # Chama A.metodo print(f"Resultado de super(): {resultado}") return "C modificado" # Classe bem comportada (todos usam super) class BomA: def __init__(self): print("BomA iniciando") super().__init__() self.valor_a = "BomA" def metodo(self): print("BomA.metodo") return super().metodo() if hasattr(super(), 'metodo') else "A" class BomB: def __init__(self): print("BomB iniciando") super().__init__() self.valor_b = "BomB" def metodo(self): print("BomB.metodo") return super().metodo() if hasattr(super(), 'metodo') else "B" class BomC(BomA, BomB): def __init__(self): print("BomC iniciando") super().__init__() print("BomC finalizado") def metodo(self): print("BomC.metodo") return super().metodo() print("=== Comportamento sem super() adequada ===") c = C() print(c.metodo()) print("\n=== Comportamento com super() adequada ===") bom_c = BomC() print(bom_c.metodo()) print(f"Valores: {bom_c.valor_a}, {bom_c.valor_b}") # Exemplo prático: logger com timestamp class TimestampMixin: """Adiciona timestamp às mensagens.""" def log(self, mensagem): from datetime import datetime timestamp = datetime.now().strftime("%H:%M:%S") return super().log(f"[{timestamp}] {mensagem}") if hasattr(super(), 'log') else print(f"[{timestamp}] {mensagem}") class FileLogger: """Logger que escreve em arquivo.""" def __init__(self, arquivo="log.txt"): self.arquivo = arquivo def log(self, mensagem): with open(self.arquivo, "a") as f: f.write(f"{mensagem}\n") return mensagem class ConsoleLogger: """Logger que escreve no console.""" def log(self, mensagem): print(f"CONSOLE: {mensagem}") return mensagem # Combinando mixins class TimestampFileLogger(TimestampMixin, FileLogger): pass class TimestampConsoleLogger(TimestampMixin, ConsoleLogger): pass print("\n=== Logger com Timestamp ===") logger = TimestampFileLogger("meu_log.txt") logger.log("Mensagem de teste") logger2 = TimestampConsoleLogger() logger2.log("Mensagem no console") |
A fórmula do MRO para herança múltipla: \(\text{MRO}(C(B_1, B_2, …, B_n)) = [C] + \text{merge}(\text{MRO}(B_1), …, \text{MRO}(B_n), [B_1, …, B_n])\) Herança é uma ferramenta poderosa quando usada com sabedoria. Prefira composição sobre herança em casos duvidosos. Use herança para relações “é um” genuínas. Seu código será mais limpo e manutenível.