Python

Herança e Polimorfismo em Python: MRO e super() na Prática

16 min de leitura

Herança e Polimorfismo em Python: MRO e super() na Prática

Fundamentos de Herança em Python A herança é um dos pilares da Programação Orientada a Objetos (POO) e permite que uma classe herde atributos e métodos de outra classe, promovendo reutilização de código e criação de hierarquias lógicas. Em Python, quando você cria uma classe que herda de outra, a classe filha (ou subclasse) recebe automaticamente toda a funcionalidade da classe pai (ou superclasse), podendo também estendê-la ou modificá-la conforme necessário. Considere um cenário real: você está desenvolvendo um sistema de gestão de funcionários. Uma classe base poderia conter dados comuns como nome e salário, enquanto classes especializadas como e herdariam dessas informações e adicionariam características próprias. Sem herança, você teria que replicar código em múltiplas classes, violando o princípio DRY (Don't Repeat Yourself). A sintaxe básica é simples: declare a classe pai entre parênteses na definição da classe filha. Todos os atributos e métodos da classe pai estão disponíveis para a filha, mas você pode sobrescrever (override) qualquer método

<h2>Fundamentos de Herança em Python</h2>

<p>A herança é um dos pilares da Programação Orientada a Objetos (POO) e permite que uma classe herde atributos e métodos de outra classe, promovendo reutilização de código e criação de hierarquias lógicas. Em Python, quando você cria uma classe que herda de outra, a classe filha (ou subclasse) recebe automaticamente toda a funcionalidade da classe pai (ou superclasse), podendo também estendê-la ou modificá-la conforme necessário.</p>

<p>Considere um cenário real: você está desenvolvendo um sistema de gestão de funcionários. Uma classe base <code>Funcionario</code> poderia conter dados comuns como nome e salário, enquanto classes especializadas como <code>Gerente</code> e <code>Desenvolvedor</code> herdariam dessas informações e adicionariam características próprias. Sem herança, você teria que replicar código em múltiplas classes, violando o princípio DRY (Don&#039;t Repeat Yourself).</p>

<pre><code class="language-python">class Funcionario:

def __init__(self, nome, salario):

self.nome = nome

self.salario = salario

def calcular_bonus(self):

return self.salario * 0.10

class Gerente(Funcionario):

def __init__(self, nome, salario, departamento):

super().__init__(nome, salario)

self.departamento = departamento

def calcular_bonus(self):

return self.salario * 0.20

class Desenvolvedor(Funcionario):

def __init__(self, nome, salario, linguagem):

super().__init__(nome, salario)

self.linguagem = linguagem

def calcular_bonus(self):

return self.salario * 0.15

Testando

gerente = Gerente(&quot;Alice&quot;, 5000, &quot;TI&quot;)

dev = Desenvolvedor(&quot;Bob&quot;, 4000, &quot;Python&quot;)

print(f&quot;{gerente.nome} recebe bônus de R${gerente.calcular_bonus()}&quot;)

print(f&quot;{dev.nome} recebe bônus de R${dev.calcular_bonus()}&quot;)</code></pre>

<p>A sintaxe básica é simples: declare a classe pai entre parênteses na definição da classe filha. Todos os atributos e métodos da classe pai estão disponíveis para a filha, mas você pode sobrescrever (override) qualquer método para implementar comportamento diferente, como fizemos com <code>calcular_bonus()</code>.</p>

<h2>Method Resolution Order (MRO) e a Herança Múltipla</h2>

<p>O MRO (Ordem de Resolução de Métodos) é um algoritmo que define a sequência em que Python procura por um método ou atributo em uma hierarquia de classes. Isso é especialmente crítico quando você trabalha com herança múltipla, onde uma classe herda de duas ou mais classes pai. Sem um mecanismo bem definido, haveria ambiguidade: qual método chamar primeiro?</p>

<p>Python utiliza o algoritmo C3 Linearization para determinar o MRO, garantindo que: (1) classes filhas sejam consultadas antes das pai; (2) a ordem de herança declarada seja respeitada; (3) cada classe apareça apenas uma vez na sequência. Você pode visualizar o MRO de qualquer classe usando o método <code>mro()</code> ou o atributo <code>__mro__</code>.</p>

<pre><code class="language-python">class Animal:

def fazer_som(self):

return &quot;Som genérico&quot;

class Terrestre:

def mover(self):

return &quot;Andando&quot;

class Aquatico:

def mover(self):

return &quot;Nadando&quot;

class Anfibio(Terrestre, Aquatico, Animal):

pass

Visualizando o MRO

print(Anfibio.mro())

Saída: [&lt;class &#039;Anfibio&#039;&gt;, &lt;class &#039;Terrestre&#039;&gt;, &lt;class &#039;Aquatico&#039;&gt;, &lt;class &#039;Animal&#039;&gt;, &lt;class &#039;object&#039;&gt;]

Ou usando __mro__

for classe in Anfibio.__mro__:

print(classe.__name__)

Testando a resolução

anf = Anfibio()

print(anf.mover()) # Saída: Andando (encontrado em Terrestre primeiro)

print(anf.fazer_som()) # Saída: Som genérico</code></pre>

<p>Observe que <code>mover()</code> retorna &quot;Andando&quot; porque <code>Terrestre</code> aparece antes de <code>Aquatico</code> na herança. Se a ordem fosse invertida (<code>class Anfibio(Aquatico, Terrestre, Animal)</code>), o resultado seria &quot;Nadando&quot;. O MRO garante previsibilidade e segurança em hierarquias complexas, evitando o problema do &quot;diamante&quot; (diamond problem) que outras linguagens enfrentam.</p>

<h3>Evitando Armadilhas com MRO</h3>

<p>Um erro comum é criar hierarquias que violam o MRO, resultando em exceções. Por exemplo, se você tiver uma estrutura circular ou uma ordem inconsistente nas heranças, Python levantará um <code>TypeError</code>. Sempre verifique o MRO ao trabalhar com herança múltipla e garanta que a ordem das classes pai faz sentido semanticamente para seu domínio.</p>

<pre><code class="language-python"># Exemplo que gera erro (comentado para não quebrar)

class A(B, C): pass

class B(C): pass

class C(A): pass # Circular - TypeError!

Ordem correta

class Veiculo:

def ligar(self):

return &quot;Ligando motor&quot;

class Terrestre(Veiculo):

def andar(self):

return &quot;Andando na terra&quot;

class Aquatico(Veiculo):

def nadar(self):

return &quot;Nadando na água&quot;

class Carro(Terrestre):

pass

class Barco(Aquatico):

pass

print(Carro.mro())

print(Barco.mro())</code></pre>

<h2>A Função super() e Chamadas Cooperativas</h2>

<p>A função <code>super()</code> é o coração da reutilização de código eficiente em Python. Ela retorna um objeto proxy que delega chamadas de métodos a uma classe pai ou irmã na cadeia de herança, seguindo o MRO. Diferentemente de chamar a classe pai diretamente pelo nome (<code>Funcionario.__init__(self, ...)</code>), <code>super()</code> é dinamicamente resolvido, tornando seu código mais flexível e seguro para refatorações futuras.</p>

<p>O grande benefício de usar <code>super()</code> transparece em cenários de herança múltipla ou profunda. Se você usar <code>super()</code>, cada classe colabora automaticamente sem conhecer exatamente quem vem a seguir no MRO. Isso é especialmente valioso quando você estende uma hierarquia existente sem quebrar código que já funciona.</p>

<pre><code class="language-python">class Pessoa:

def __init__(self, nome, idade):

self.nome = nome

self.idade = idade

print(f&quot;Pessoa.__init__ chamado para {self.nome}&quot;)

def apresentar(self):

return f&quot;Olá, meu nome é {self.nome}&quot;

class Estudante(Pessoa):

def __init__(self, nome, idade, matricula):

super().__init__(nome, idade)

self.matricula = matricula

print(f&quot;Estudante.__init__ chamado para {self.nome}&quot;)

def apresentar(self):

pai = super().apresentar()

return f&quot;{pai} e sou estudante com matrícula {self.matricula}&quot;

class Bolsista(Estudante):

def __init__(self, nome, idade, matricula, valor_bolsa):

super().__init__(nome, idade, matricula)

self.valor_bolsa = valor_bolsa

print(f&quot;Bolsista.__init__ chamado para {self.nome}&quot;)

def apresentar(self):

pai = super().apresentar()

return f&quot;{pai}. Bolsa: R${self.valor_bolsa}&quot;

Testando a cadeia

bolsista = Bolsista(&quot;Carlos&quot;, 20, &quot;2024001&quot;, 1500)

print(bolsista.apresentar())

Saída do __init__:

Pessoa.__init__ chamado para Carlos

Estudante.__init__ chamado para Carlos

Bolsista.__init__ chamado para Carlos

Saída do apresentar():

Olá, meu nome é Carlos e sou estudante com matrícula 2024001. Bolsa: R$1500</code></pre>

<p>Neste exemplo, note como <code>super()</code> encadeia as chamadas respeitando o MRO. Sem <code>super()</code>, você teria que chamar <code>Pessoa.__init__(self, ...)</code> em <code>Estudante</code>, depois <code>Estudante.__init__(self, ...)</code> em <code>Bolsista</code>, criando dependências rígidas. Com <code>super()</code>, cada classe sabe apenas que deve chamar o próximo na fila, promovendo desacoplamento.</p>

<h3>Sintaxe Moderna vs. Clássica</h3>

<p>Em Python 3, você pode chamar <code>super()</code> sem argumentos dentro de um método (forma moderna). Python automaticamente deduz a classe e a instância. Na forma clássica, você passa a classe e a instância explicitamente. Ambas funcionam, mas a forma moderna é mais legível e recomendada.</p>

<pre><code class="language-python"># Forma clássica (ainda funciona, mas evite)

class Pai:

def metodo(self):

return &quot;Pai&quot;

class Filho(Pai):

def metodo(self):

return super(Filho, self).metodo() + &quot; + Filho&quot;

Forma moderna (Python 3, recomendada)

class Pai2:

def metodo(self):

return &quot;Pai&quot;

class Filho2(Pai2):

def metodo(self):

return super().metodo() + &quot; + Filho&quot;

f = Filho2()

print(f.metodo()) # Saída: Pai + Filho</code></pre>

<h2>Polimorfismo: Comportamentos Diferentes, Interface Comum</h2>

<p>Polimorfismo significa &quot;muitas formas&quot; e, em POO, refere-se à capacidade de objetos de diferentes classes responderem ao mesmo método com comportamentos distintos. É a consecução natural da herança: quando você define um método em uma classe base e sobrescreve em subclasses, cada uma implementa sua própria versão. Quem chama não precisa saber a classe exata, apenas que aquele método existe.</p>

<p>Polimorfismo torna seu código mais genérico e reutilizável. Em vez de escrever lógicas diferentes para cada tipo de objeto, você escreve uma única função que funciona com qualquer objeto que implemente a interface esperada. Isso é uma aplicação prática do princípio SOLID chamado &quot;Liskov Substitution Principle&quot;: objetos de subclasses devem poder substituir objetos de classes pai sem quebrar a aplicação.</p>

<pre><code class="language-python">from abc import ABC, abstractmethod

class Pagamento(ABC):

@abstractmethod

def processar(self, valor):

pass

@abstractmethod

def validar(self):

pass

class PagamentoCartao(Pagamento):

def __init__(self, numero, cvv):

self.numero = numero

self.cvv = cvv

def processar(self, valor):

if self.validar():

return f&quot;Processando R${valor} via Cartão {self.numero[-4:]}&quot;

return &quot;Cartão inválido&quot;

def validar(self):

return len(self.numero) == 16 and len(self.cvv) == 3

class PagamentoBoleto(Pagamento):

def __init__(self, codigo):

self.codigo = codigo

def processar(self, valor):

if self.validar():

return f&quot;Processando R${valor} via Boleto {self.codigo}&quot;

return &quot;Boleto inválido&quot;

def validar(self):

return len(self.codigo) == 47

class PagamentoPix(Pagamento):

def __init__(self, chave):

self.chave = chave

def processar(self, valor):

if self.validar():

return f&quot;Processando R${valor} via Pix {self.chave}&quot;

return &quot;Chave Pix inválida&quot;

def validar(self):

return &quot;@&quot; in self.chave

Função polimórfica

def processar_compra(pagamento, valor):

return pagamento.processar(valor)

Testando polimorfismo

cartao = PagamentoCartao(&quot;1234567890123456&quot;, &quot;123&quot;)

boleto = PagamentoBoleto(&quot;12345678901234567890123456789012345678901234567&quot;)

pix = PagamentoPix(&quot;usuario@pix&quot;)

for metodo in [cartao, boleto, pix]:

print(processar_compra(metodo, 100))</code></pre>

<p>Perceba que <code>processar_compra()</code> não precisa saber qual tipo de pagamento está recebendo. Ela apenas chama <code>processar()</code>, e cada subclasse faz sua própria coisa. Se no futuro você adicionar <code>PagamentoApplePay</code>, a função continua funcionando sem modificação. Isso é o verdadeiro poder do polimorfismo.</p>

<h3>Uso de Classes Abstratas</h3>

<p>No exemplo acima, usamos <code>ABC</code> (Abstract Base Class) e <code>@abstractmethod</code> para forçar que qualquer subclasse implemente certos métodos. Isso garante que a interface é respeitada e evita instanciação acidental de classes incompletas.</p>

<pre><code class="language-python"># Isto levanta TypeError

pagamento = Pagamento(&quot;algo&quot;) # TypeError: Can&#039;t instantiate abstract class</code></pre>

<h2>Padrões e Boas Práticas</h2>

<p>Ao trabalhar com herança e polimorfismo, algumas práticas minimizam bugs e melhoram manutenibilidade. Primeiro, prefira composição à herança quando a relação não for claramente &quot;é um&quot; (is-a). Por exemplo, se uma classe <code>Carro</code> precisa de um <code>Motor</code>, é melhor ter um atributo que referencie a classe <code>Motor</code> do que herdar dela. Segundo, sempre use <code>super()</code> em vez de chamar a classe pai pelo nome direto—garante que mudanças futuras na hierarquia não quebrem seu código. Terceiro, documente o contrato esperado de uma classe base, deixando claro quais métodos subclasses devem (ou podem) sobrescrever.</p>

<pre><code class="language-python">class Veiculo:

&quot;&quot;&quot;Classe base para todos os veículos.

Subclasses devem implementar:

  • ligar(): inicia o motor
  • desligar(): desliga o motor
  • dirigir(distancia): realiza deslocamento

&quot;&quot;&quot;

def __init__(self, marca, modelo):

self.marca = marca

self.modelo = modelo

self._ligado = False

def ligar(self):

self._ligado = True

return f&quot;{self.marca} {self.modelo} ligado&quot;

def desligar(self):

self._ligado = False

return f&quot;{self.marca} {self.modelo} desligado&quot;

def dirigir(self, distancia):

if not self._ligado:

raise RuntimeError(&quot;Veículo desligado!&quot;)

return f&quot;Dirigindo {distancia}km&quot;

class Carro(Veiculo):

def __init__(self, marca, modelo, portas):

super().__init__(marca, modelo)

self.portas = portas

def abrir_porta(self):

return f&quot;Abrindo {self.portas} portas&quot;

class Moto(Veiculo):

def __init__(self, marca, modelo, cilindrada):

super().__init__(marca, modelo)

self.cilindrada = cilindrada

def fazer_manobra(self):

if not self._ligado:

raise RuntimeError(&quot;Moto desligada!&quot;)

return &quot;Fazendo manobra!&quot;

Testando

carro = Carro(&quot;Toyota&quot;, &quot;Corolla&quot;, 4)

moto = Moto(&quot;Honda&quot;, &quot;CB500&quot;, 500)

print(carro.ligar())

print(carro.abrir_porta())

print(carro.dirigir(10))

print(moto.ligar())

print(moto.fazer_manobra())

print(moto.dirigir(50))</code></pre>

<p>Outra prática valiosa é manter hierarquias rasas. Hierarquias muito profundas (muitos níveis de herança) tornam o código difícil de entender e modificar. Se você se vê criando uma cadeia com mais de 3 ou 4 níveis, reconsidere se composição não seria mais adequada. Finalmente, teste suas hierarquias. Use ferramentas como <code>unittest</code> para validar que subclasses realmente funcionam como esperado e que o MRO não causa surpresas.</p>

<pre><code class="language-python">import unittest

class TestVeiculo(unittest.TestCase):

def test_carro_dirigir_desligado(self):

carro = Carro(&quot;Toyota&quot;, &quot;Corolla&quot;, 4)

with self.assertRaises(RuntimeError):

carro.dirigir(10)

def test_carro_dirigir_ligado(self):

carro = Carro(&quot;Toyota&quot;, &quot;Corolla&quot;, 4)

carro.ligar()

resultado = carro.dirigir(10)

self.assertIn(&quot;Dirigindo&quot;, resultado)

def test_moto_manobra_desligada(self):

moto = Moto(&quot;Honda&quot;, &quot;CB500&quot;, 500)

with self.assertRaises(RuntimeError):

moto.fazer_manobra()

if __name__ == &quot;__main__&quot;:

unittest.main()</code></pre>

<h2>Conclusão</h2>

<p>Dominar herança e polimorfismo em Python repousa em três pilares bem compreendidos: (1) <strong>MRO governa a resolução de métodos</strong> — use <code>mro()</code> para visualizar a ordem e evitar armadilhas em herança múltipla; (2) <strong><code>super()</code> é essencial para código cooperativo</strong> — sempre prefira <code>super()</code> a chamadas diretas de classe pai, garantindo flexibilidade e manutenibilidade; (3) <strong>Polimorfismo torna seu código genérico e extensível</strong> — implemente interfaces esperadas em subclasses e deixe que objetos respondam ao mesmo método com comportamentos próprios, respeitando o Liskov Substitution Principle.</p>

<p>Esses conceitos, quando bem aplicados, transformam código repetitivo em hierarquias limpas e reutilizáveis. A prática consistente — escrevendo pequenos projetos com herança múltipla, testando MRO e refatorando para usar <code>super()</code> — consolidará seu entendimento.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy" target="_blank" rel="noopener noreferrer">Python Official Documentation - Data Model</a></li>

<li><a href="https://realpython.com/inheritance-composition-python/" target="_blank" rel="noopener noreferrer">Real Python - Inheritance and Composition in Python</a></li>

<li><a href="https://www.python.org/dev/peps/pep-0c3/" target="_blank" rel="noopener noreferrer">Python MRO (Method Resolution Order) - C3 Linearization</a></li>

<li><a href="https://realpython.com/super-python/" target="_blank" rel="noopener noreferrer">Real Python - super() in Python</a></li>

<li><a href="https://refactoring.guru/design-patterns/python" target="_blank" rel="noopener noreferrer">Design Patterns in Python - Refactoring Guru</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Python

Dominando Mypy em Python: Verificação Estática de Tipos no Projeto Real em Projetos Reais
Dominando Mypy em Python: Verificação Estática de Tipos no Projeto Real em Projetos Reais

Introdução ao Mypy: Por Que Type Checking Importa Quando você trabalha em pro...

Clean Architecture em Python: Estruturando Projetos para Escalar na Prática
Clean Architecture em Python: Estruturando Projetos para Escalar na Prática

Clean Architecture em Python: Estruturando Projetos para Escalar A Clean Arch...

Mocks em Python: unittest.mock, patch e pytest-mock na Prática: Do Básico ao Avançado
Mocks em Python: unittest.mock, patch e pytest-mock na Prática: Do Básico ao Avançado

Introdução: Por que Mocks são Essenciais Quando desenvolvemos software profis...