Python

Herança e Polimorfismo Já leu

7 min de leitura

Herança e Polimorfismo
No artigo anterior criamos classes independentes. Mas e quando duas classes compartilham características comuns? Reescrever o mesmo código em cada

No artigo anterior criamos classes independentes. Mas e quando duas classes compartilham características comuns? Reescrever o mesmo código em cada uma viola um princípio fundamental do desenvolvimento de software: DRY — Don't Repeat Yourself. Herança resolve esse problema permitindo que uma classe herde atributos e métodos de outra, e polimorfismo permite que objetos de tipos diferentes respondam ao mesmo comando de formas distintas.


Herança Simples

Uma classe filha herda tudo da classe pai e pode estender ou modificar esse comportamento:

class Animal:
    def __init__(self, nome, idade):
        self.nome  = nome
        self.idade = idade

    def apresentar(self):
        return f"Eu sou {self.nome} e tenho {self.idade} ano(s)."

    def emitir_som(self):
        return "..."


class Cachorro(Animal):
    def emitir_som(self):
        return "Au au!"

    def buscar(self, objeto):
        return f"{self.nome} buscou o {objeto}!"


class Gato(Animal):
    def emitir_som(self):
        return "Miau!"

    def arranhar(self):
        return f"{self.nome} arranhou o sofá."


rex    = Cachorro("Rex",    3)
mimi   = Gato("Mimi",   5)

print(rex.apresentar())     # Eu sou Rex e tenho 3 ano(s).
print(rex.emitir_som())     # Au au!
print(rex.buscar("graveto")) # Rex buscou o graveto!
print(mimi.emitir_som())    # Miau!

Cachorro e Gato herdam apresentar() de Animal sem precisar reescrevê-lo. Cada uma sobrescreve emitir_som() com seu próprio comportamento — isso é sobrescrita de método (method overriding).


A Função super()

super() permite chamar métodos da classe pai a partir da classe filha — essencial para estender o construtor:

class Veiculo:
    def __init__(self, marca, modelo, ano):
        self.marca  = marca
        self.modelo = modelo
        self.ano    = ano

    def descricao(self):
        return f"{self.ano} {self.marca} {self.modelo}"


class Carro(Veiculo):
    def __init__(self, marca, modelo, ano, portas):
        super().__init__(marca, modelo, ano)   # chama __init__ do pai
        self.portas = portas

    def descricao(self):
        base = super().descricao()             # reutiliza o método do pai
        return f"{base} ({self.portas} portas)"


class CarroEletrico(Carro):
    def __init__(self, marca, modelo, ano, portas, autonomia_km):
        super().__init__(marca, modelo, ano, portas)
        self.autonomia_km = autonomia_km

    def descricao(self):
        base = super().descricao()
        return f"{base} — Elétrico, {self.autonomia_km}km de autonomia"


civic = Carro("Honda", "Civic", 2023, 4)
model3 = CarroEletrico("Tesla", "Model 3", 2024, 4, 560)

print(civic.descricao())
# 2023 Honda Civic (4 portas)

print(model3.descricao())
# 2024 Tesla Model 3 (4 portas) — Elétrico, 560km de autonomia

Polimorfismo

Polimorfismo significa "muitas formas". Em Python, objetos de classes diferentes podem ser tratados de forma uniforme se implementarem os mesmos métodos:

class Circulo:
    def __init__(self, raio):
        self.raio = raio

    def area(self):
        import math
        return math.pi * self.raio ** 2

    def perimetro(self):
        import math
        return 2 * math.pi * self.raio


class Retangulo:
    def __init__(self, largura, altura):
        self.largura = largura
        self.altura  = altura

    def area(self):
        return self.largura * self.altura

    def perimetro(self):
        return 2 * (self.largura + self.altura)


class Triangulo:
    def __init__(self, base, altura, lado_a, lado_b, lado_c):
        self.base    = base
        self.altura  = altura
        self.lados   = (lado_a, lado_b, lado_c)

    def area(self):
        return (self.base * self.altura) / 2

    def perimetro(self):
        return sum(self.lados)


# Polimorfismo em ação — mesma interface, comportamentos diferentes
formas = [
    Circulo(5),
    Retangulo(4, 6),
    Triangulo(3, 4, 3, 4, 5),
]

for forma in formas:
    nome = type(forma).__name__
    print(f"{nome:12} | Área: {forma.area():7.2f} | Perímetro: {forma.perimetro():.2f}")

Saída:

Circulo      | Área:   78.54 | Perímetro: 31.42
Retangulo    | Área:   24.00 | Perímetro: 20.00
Triangulo    | Área:    6.00 | Perímetro: 12.00

Nenhuma das classes herda da outra — mesmo assim podemos tratá-las uniformemente. Isso é o duck typing do Python: se o objeto tem o método area(), ele pode ser usado onde area() for chamado. "Se anda como pato e grasna como pato, é um pato."


Classes Abstratas

Quando você quer garantir que subclasses implementem determinados métodos, use o módulo abc:

from abc import ABC, abstractmethod

class Forma(ABC):
    """Classe base abstrata — não pode ser instanciada diretamente."""

    @abstractmethod
    def area(self):
        """Toda forma deve implementar area()."""
        pass

    @abstractmethod
    def perimetro(self):
        """Toda forma deve implementar perimetro()."""
        pass

    def descricao(self):
        """Método concreto — herdado por todas as subclasses."""
        return (f"{type(self).__name__}: "
                f"área={self.area():.2f}, "
                f"perímetro={self.perimetro():.2f}")


class Quadrado(Forma):
    def __init__(self, lado):
        self.lado = lado

    def area(self):
        return self.lado ** 2

    def perimetro(self):
        return 4 * self.lado


class Hexagono(Forma):
    def __init__(self, lado):
        self.lado = lado

    def area(self):
        import math
        return (3 * math.sqrt(3) / 2) * self.lado ** 2

    def perimetro(self):
        return 6 * self.lado


# Forma()     # TypeError — não pode instanciar classe abstrata
q = Quadrado(5)
h = Hexagono(4)

print(q.descricao())  # Quadrado: área=25.00, perímetro=20.00
print(h.descricao())  # Hexagono: área=41.57, perímetro=24.00

Herança Múltipla e MRO

Python suporta herança múltipla — uma classe pode herdar de várias classes ao mesmo tempo:

class Nadador:
    def nadar(self):
        return f"{self.nome} está nadando."

class Corredor:
    def correr(self):
        return f"{self.nome} está correndo."

class Ciclista:
    def pedalar(self):
        return f"{self.nome} está pedalando."

class Triatleta(Nadador, Corredor, Ciclista):
    def __init__(self, nome):
        self.nome = nome

    def competir(self):
        return [self.nadar(), self.pedalar(), self.correr()]


atleta = Triatleta("Carlos")
for atividade in atleta.competir():
    print(atividade)

# Carlos está nadando.
# Carlos está pedalando.
# Carlos está correndo.

Quando há ambiguidade sobre qual método usar, Python segue o MRO — Method Resolution Order, calculado pelo algoritmo C3:

print(Triatleta.__mro__)
# (<class 'Triatleta'>, <class 'Nadador'>, <class 'Corredor'>,
#  <class 'Ciclista'>, <class 'object'>)

Verificando Herança

rex = Cachorro("Rex", 3)

print(isinstance(rex, Cachorro))  # True
print(isinstance(rex, Animal))    # True — herança
print(isinstance(rex, Gato))      # False

print(issubclass(Cachorro, Animal))  # True
print(issubclass(Gato, Cachorro))    # False

Exemplo Completo: Sistema de Pagamentos

from abc import ABC, abstractmethod

class MetodoPagamento(ABC):
    def __init__(self, titular):
        self.titular = titular

    @abstractmethod
    def processar(self, valor):
        pass

    @abstractmethod
    def descricao(self):
        pass

    def recibo(self, valor):
        return (f"Recibo — {self.titular}\n"
                f"Método: {self.descricao()}\n"
                f"Valor:  R${valor:.2f}\n"
                f"Status: {self.processar(valor)}")


class CartaoCredito(MetodoPagamento):
    def __init__(self, titular, numero, limite):
        super().__init__(titular)
        self.numero = f"**** **** **** {numero[-4:]}"
        self.limite = limite
        self._gasto = 0

    def processar(self, valor):
        if self._gasto + valor > self.limite:
            return "RECUSADO — limite insuficiente"
        self._gasto += valor
        return "APROVADO"

    def descricao(self):
        return f"Cartão de Crédito {self.numero}"


class Pix(MetodoPagamento):
    def __init__(self, titular, chave):
        super().__init__(titular)
        self.chave = chave

    def processar(self, valor):
        return "APROVADO — transferência instantânea"

    def descricao(self):
        return f"Pix ({self.chave})"


class Boleto(MetodoPagamento):
    def __init__(self, titular):
        super().__init__(titular)

    def processar(self, valor):
        return "PENDENTE — aguardando compensação"

    def descricao(self):
        return "Boleto Bancário"


pagamentos = [
    CartaoCredito("Ana",    "1234567890123456", 2000),
    Pix("Bruno",  "bruno@email.com"),
    Boleto("Carla"),
]

valores = [150.00, 89.90, 320.00]

for metodo, valor in zip(pagamentos, valores):
    print(metodo.recibo(valor))
    print("-" * 40)

Resumo

  • Herança permite que uma classe filha reutilize código da classe pai
  • super() acessa métodos e o construtor da classe pai sem acoplamento direto
  • Polimorfismo trata objetos de tipos diferentes de forma uniforme pela mesma interface
  • Duck typing: Python não exige herança formal — basta o objeto ter os métodos esperados
  • Classes abstratas com ABC e @abstractmethod garantem que subclasses implementem contratos
  • Herança múltipla é suportada; o MRO define a ordem de resolução de métodos
  • isinstance() verifica instância considerando herança; issubclass() verifica hierarquia de classes

Referências e Leituras Complementares

Comentários

Mais em Python

Tuplas e Sets: imutabilidade e unicidade
Tuplas e Sets: imutabilidade e unicidade

Python oferece mais de uma forma de agrupar dados. Enquanto listas s&atilde;o...

Módulos, Pacotes e Organização de Projetos
Módulos, Pacotes e Organização de Projetos

&Agrave; medida que um programa cresce, colocar tudo em um &uacute;nico arqui...

Decoradores e Metaprogramação
Decoradores e Metaprogramação

Decoradores s&atilde;o um dos recursos mais elegantes do Python. Eles permite...