Herança em Python: O que É e Como Funciona | Python Brasil
Entenda herança em Python: MRO, C3 linearization, super(), mixins, classes abstratas e composição vs herança. Exemplos práticos completos.
O que é Herança?
Herança é um dos pilares da Programação Orientada a Objetos que permite que uma classe herde atributos e métodos de outra classe. A classe que herda é chamada de classe filha (ou subclasse), e a classe da qual se herda é a classe pai (ou superclasse).
Isso promove a reutilização de código e estabelece relações hierárquicas entre classes. Herança deve modelar uma relação clara de “é um”: um Cachorro é um Animal. Se a relação for “tem um”, prefira composição.
Herança simples
class Animal:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
def falar(self):
raise NotImplementedError("Subclasses devem implementar este método")
def info(self):
return f"{self.nome}, {self.idade} anos"
class Cachorro(Animal):
def __init__(self, nome, idade, raca):
super().__init__(nome, idade) # Chama o construtor da classe pai
self.raca = raca
def falar(self):
return "Au au!"
def info(self):
base = super().info() # Reutiliza o método da classe pai
return f"{base}, raça: {self.raca}"
class Gato(Animal):
def falar(self):
return "Miau!"
rex = Cachorro("Rex", 5, "Labrador")
mimi = Gato("Mimi", 3)
print(rex.info()) # Rex, 5 anos, raça: Labrador
print(rex.falar()) # Au au!
print(mimi.falar()) # Miau!
print(isinstance(rex, Animal)) # True
MRO: Ordem de Resolução de Métodos
Quando Python precisa encontrar um atributo ou método, ele segue a Method Resolution Order (MRO). Em herança simples isso é direto, mas em herança múltipla o Python usa o algoritmo C3 Linearization para determinar a ordem de forma consistente e previsível.
class A:
def metodo(self):
return "A"
class B(A):
def metodo(self):
return f"B -> {super().metodo()}"
class C(A):
def metodo(self):
return f"C -> {super().metodo()}"
class D(B, C):
def metodo(self):
return f"D -> {super().metodo()}"
print(D.__mro__)
# D -> B -> C -> A -> object
print(D().metodo())
# D -> B -> C -> A
O C3 Linearization garante que: a classe filha sempre aparece antes das classes pai; a ordem das classes pai é preservada; nenhuma classe aparece antes de suas dependências. Você pode inspecionar o MRO de qualquer classe com Classe.__mro__ ou Classe.mro().
super() em profundidade
super() não retorna “a classe pai” — ele retorna um proxy que segue o MRO da classe corrente. Isso é fundamental em herança múltipla:
class Logavel:
def __init__(self, **kwargs):
super().__init__(**kwargs)
print(f"Logavel inicializado")
class Serializavel:
def __init__(self, **kwargs):
super().__init__(**kwargs)
print(f"Serializavel inicializado")
class Base:
def __init__(self, nome, **kwargs):
super().__init__(**kwargs)
self.nome = nome
print(f"Base inicializado com nome={nome}")
class Entidade(Logavel, Serializavel, Base):
def __init__(self, nome, **kwargs):
super().__init__(nome=nome, **kwargs)
print("Entidade inicializada")
e = Entidade("Produto")
# Base inicializado com nome=Produto
# Serializavel inicializado
# Logavel inicializado
# Entidade inicializada
Note o uso de **kwargs para que cada classe no MRO passe os argumentos restantes adiante sem quebrar a cadeia de chamadas.
Herança múltipla e o padrão Mixin
O padrão Mixin é uma das formas mais elegantes de usar herança múltipla. Um mixin é uma classe que fornece um conjunto específico de funcionalidades, mas que não é destinada a ser instanciada sozinha.
class JsonMixin:
"""Mixin que adiciona serialização JSON a qualquer classe."""
def to_json(self):
import json
return json.dumps(self.__dict__, ensure_ascii=False, default=str)
@classmethod
def from_json(cls, json_str):
import json
dados = json.loads(json_str)
return cls(**dados)
class LogMixin:
"""Mixin que adiciona logging automático a métodos."""
def log(self, mensagem):
import logging
logging.info(f"[{self.__class__.__name__}] {mensagem}")
class ValidacaoMixin:
"""Mixin que adiciona validação de campos obrigatórios."""
campos_obrigatorios = []
def validar(self):
for campo in self.campos_obrigatorios:
if not getattr(self, campo, None):
raise ValueError(f"Campo obrigatório ausente: {campo}")
return True
class Usuario(JsonMixin, LogMixin, ValidacaoMixin):
campos_obrigatorios = ['nome', 'email']
def __init__(self, nome, email, idade=None):
self.nome = nome
self.email = email
self.idade = idade
u = Usuario("Ana", "ana@email.com", 28)
print(u.to_json()) # {"nome": "Ana", "email": "ana@email.com", "idade": 28}
u.validar() # True
u.log("Usuário criado com sucesso")
Classes abstratas como contratos
O módulo abc permite criar classes que definem uma interface obrigatória para suas subclasses:
from abc import ABC, abstractmethod
class Repositorio(ABC):
"""Define o contrato que todos os repositórios devem cumprir."""
@abstractmethod
def buscar_por_id(self, id: int):
pass
@abstractmethod
def salvar(self, entidade) -> None:
pass
@abstractmethod
def deletar(self, id: int) -> None:
pass
def buscar_todos(self): # Método concreto — opcional sobrescrever
return []
class RepositorioEmMemoria(Repositorio):
def __init__(self):
self._dados = {}
def buscar_por_id(self, id):
return self._dados.get(id)
def salvar(self, entidade):
self._dados[entidade['id']] = entidade
def deletar(self, id):
self._dados.pop(id, None)
# Repositorio() # TypeError: não pode instanciar classe abstrata
repo = RepositorioEmMemoria()
repo.salvar({'id': 1, 'nome': 'Produto A'})
print(repo.buscar_por_id(1)) # {'id': 1, 'nome': 'Produto A'}
Composição vs herança
A herança muitas vezes é usada em excesso. Antes de criar uma hierarquia de herança, pergunte-se: a relação é realmente “é um”?
# Herança: "Motor É um Carro?" — Não faz sentido!
class Carro(Motor): # Errado
pass
# Composição: "Carro TEM um Motor" — Correto
class Motor:
def __init__(self, potencia):
self.potencia = potencia
def ligar(self):
return f"Motor de {self.potencia}cv ligado"
class Carro:
def __init__(self, modelo, potencia):
self.modelo = modelo
self.motor = Motor(potencia) # Composição
def ligar(self):
return f"{self.modelo}: {self.motor.ligar()}"
c = Carro("Fusca", 65)
print(c.ligar()) # Fusca: Motor de 65cv ligado
A composição favorece o Princípio da Responsabilidade Única e torna o código mais flexível para mudanças futuras. Você pode trocar o motor sem alterar a classe Carro.
Princípio de Substituição de Liskov (LSP)
O LSP afirma que objetos de uma subclasse devem poder substituir objetos da superclasse sem quebrar o programa. Violações comuns ocorrem quando a subclasse restringe o comportamento esperado:
# Violação do LSP — Quadrado "quebrando" o contrato de Retangulo
class Retangulo:
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
def area(self):
return self.largura * self.altura
class Quadrado(Retangulo): # Problema: quadrado viola o contrato
def __init__(self, lado):
super().__init__(lado, lado)
@Retangulo.largura.setter
def largura(self, valor):
self._largura = valor
self._altura = valor # Força lado igual — quebra o contrato!
# Solução LSP: ambos herdam de uma abstração comum
class Forma(ABC):
@abstractmethod
def area(self): pass
class Retangulo(Forma):
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
def area(self):
return self.largura * self.altura
class Quadrado(Forma):
def __init__(self, lado):
self.lado = lado
def area(self):
return self.lado ** 2
Exemplo prático: refatorando com herança
# Antes: código duplicado em cada classe de notificação
class NotificacaoEmail:
def enviar(self, destinatario, mensagem):
print(f"[LOG] Enviando email para {destinatario}")
# lógica de email...
print(f"[LOG] Email enviado")
class NotificacaoSMS:
def enviar(self, destinatario, mensagem):
print(f"[LOG] Enviando SMS para {destinatario}")
# lógica de SMS...
print(f"[LOG] SMS enviado")
# Depois: herança elimina duplicação
class Notificacao(ABC):
def enviar(self, destinatario, mensagem):
print(f"[LOG] Enviando {self.__class__.__name__} para {destinatario}")
self._executar_envio(destinatario, mensagem)
print(f"[LOG] {self.__class__.__name__} enviado")
@abstractmethod
def _executar_envio(self, destinatario, mensagem):
pass
class NotificacaoEmail(Notificacao):
def _executar_envio(self, destinatario, mensagem):
print(f"Email: {mensagem} -> {destinatario}")
class NotificacaoSMS(Notificacao):
def _executar_envio(self, destinatario, mensagem):
print(f"SMS: {mensagem} -> {destinatario}")
Erros comuns
Chamar super() sem argumentos funciona apenas dentro de métodos de classe. Em Python 2 era necessário passar a classe e self explicitamente — em Python 3 isso é desnecessário. Outro erro comum é não usar super().__init__() na subclasse quando a superclasse possui inicialização, o que resulta em atributos ausentes.
Quando usar herança?
Use herança quando: a subclasse é um tipo da superclasse; você quer reutilizar e estender o comportamento; as classes compartilham uma interface comum obrigatória. Evite herança apenas para reutilização de código — para isso, prefira composição ou mixins bem delimitados.
Termos Relacionados
- Classe - Base da programação orientada a objetos
- Polimorfismo - Múltiplas formas para mesma interface
- Encapsulamento - Proteção de dados internos