Polimorfismo significa “muitas formas” em grego.
Ele permite que objetos diferentes respondam à mesma mensagem.
Primeiramente, isso torna o código mais flexível e extensível.
Por exemplo, cachorro.falar() e gato.falar() têm resultados diferentes.
Além disso, você pode tratar objetos distintos de forma uniforme.
Assim, a complexidade do sistema diminui significativamente.
Consequentemente, a manutenção se torna muito mais fácil.
Quando utilizar polimorfismo? Em sistemas que precisam de extensibilidade.
Também quando você quer código que funcione com tipos futuros.
Por outro lado, para scripts pequenos, polimorfismo é exagero.
Python implementa polimorfismo de forma natural e flexível.
Então, vamos explorar três tipos principais com exemplos práticos.
Três subtítulos guiarão você pelo polimorfismo em Python.
Portanto, ao final, você escreverá código mais genérico e reutilizável.
Polimorfismo por herança (subtipagem)
Classes filhas podem sobrescrever métodos da classe mãe. Uma variável do tipo mãe pode receber qualquer filha. Quando usar polimorfismo por herança? Em hierarquias claras. Por exemplo, um sistema de formas geométricas. Além disso, a herança fornece uma estrutura sólida. Exemplo de polimorfismo por herança:
|
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 |
# Classe base (abstrata conceitualmente) class Forma: """Classe base para todas as formas.""" def area(self): raise NotImplementedError("Subclasse deve implementar area()") def perimetro(self): raise NotImplementedError("Subclasse deve implementar perimetro()") def descricao(self): return f"Área: {self.area():.2f}, Perímetro: {self.perimetro():.2f}" # Classes filhas com implementações específicas class Retangulo(Forma): 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 Circulo(Forma): def __init__(self, raio): self.raio = raio def area(self): return 3.14159 * self.raio ** 2 def perimetro(self): return 2 * 3.14159 * self.raio class Triangulo(Forma): def __init__(self, base, altura, lado1, lado2, lado3): self.base = base self.altura = altura self.lado1 = lado1 self.lado2 = lado2 self.lado3 = lado3 def area(self): return (self.base * self.altura) / 2 def perimetro(self): return self.lado1 + self.lado2 + self.lado3 # Função polimórfica (aceita qualquer Forma) def imprimir_informacoes(formas): """Processa qualquer lista de formas polimorficamente.""" print("=== Informações das Formas ===") for i, forma in enumerate(formas, 1): print(f"{i}. {forma.__class__.__name__}: {forma.descricao()}") # Demonstração print("=== Polimorfismo por Herança ===\n") formas = [ Retangulo(5, 3), Circulo(4), Triangulo(6, 4, 3, 4, 5), Retangulo(2, 8), Circulo(2.5) ] imprimir_informacoes(formas) # Outro exemplo com animais class Animal: def som(self): pass class Cachorro(Animal): def som(self): return "Au au!" class Gato(Animal): def som(self): return "Miau!" class Vaca(Animal): def som(self): return "Muuu!" def fazer_barulho(animais): """Função polimórfica para sons de animais.""" for animal in animais: print(f"{animal.__class__.__name__}: {animal.som()}") print("\n=== Sons de Animais ===") animais = [Cachorro(), Gato(), Vaca()] fazer_barulho(animais) |
O mesmo código imprimir_informacoes funciona com qualquer forma.
Novas formas podem ser adicionadas sem modificar a função.
Portanto, o sistema cresce sem quebrar o existente.
Polimorfismo por duck typing
Python usa tipagem pato: “se anda como pato, é um pato”. O tipo do objeto não importa, apenas seus métodos. Quando usar duck typing? Em código que prioriza flexibilidade. Por exemplo, funções que aceitam qualquer iterável. Assim, você obtém máxima reutilização de código. Exemplo de duck typing:
|
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 |
# Classes completamente independentes (sem herança comum) class Pato: def falar(self): return "Quack quack!" def voar(self): return "Pato voando baixo" class Papagaio: def falar(self): return "Olá! Quer biscoito?" def voar(self): return "Papagaio voando colorido" class Cachorro: def falar(self): return "Au au!" def correr(self): return "Cachorro correndo" class Carro: def buzinar(self): return "Biiiiii!" def acelerar(self): return "Vruum vruum" # Função polimórfica por duck typing def fazer_falar(animal): """Aceita qualquer objeto com método 'falar'.""" # Verifica se o método existe (opcional, mas elegante) if hasattr(animal, 'falar'): print(f"{animal.__class__.__name__}: {animal.falar()}") else: print(f"{animal.__class__.__name__} não sabe falar!") def fazer_voar(ser_voador): """Aceita qualquer objeto com método 'voar'.""" if hasattr(ser_voador, 'voar'): print(f"{ser_voador.__class__.__name__}: {ser_voador.voar()}") else: print(f"{ser_voador.__class__.__name__} não pode voar") print("=== Duck Typing em Ação ===\n") animais = [Pato(), Papagaio(), Cachorro(), Carro()] print("Fazendo falar:") for animal in animais: fazer_falar(animal) print("\nFazendo voar:") for ser in animais: fazer_voar(ser) # Exemplo com diferentes objetos que suportam len() print("\n=== Duck Typing com len() ===") objetos = [ [1, 2, 3], "Python", {"a": 1, "b": 2}, range(10) ] for obj in objetos: print(f"{type(obj).__name__}: len={len(obj)}") # Função que aceita qualquer coisa que possa ser iterada def somar_tudo(iteravel): """Soma todos os números de qualquer iterável.""" total = 0 for item in iteravel: total += item return total print("\nSomar tudo:") print(f"Lista: {somar_tudo([1, 2, 3, 4])}") print(f"Tupla: {somar_tudo((5, 6, 7))}") print(f"Range: {somar_tudo(range(1, 6))}") |
Duck typing é extremamente flexível e pythonico. Ele prioriza interfaces implícitas sobre herança explícita. Portanto, você escreve menos código com mais resultado.
Polimorfismo com métodos especiais
Métodos especiais (dunder) permitem polimorfismo nativo.
Por exemplo, __add__ define o comportamento de +.
Quando usar métodos especiais? Para tornar objetos mais naturais.
Também para integrar seu código com a linguagem.
Além disso, a legibilidade do código melhora muito.
Exemplo de polimorfismo com métodos especiais:
|
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 |
# Classe que implementa métodos especiais class Dinheiro: """Representa um valor monetário.""" def __init__(self, valor, moeda="BRL"): self.valor = float(valor) self.moeda = moeda def __add__(self, outro): """Polimorfismo para o operador +.""" if isinstance(outro, Dinheiro): if self.moeda != outro.moeda: raise ValueError("Moedas diferentes não podem ser somadas") return Dinheiro(self.valor + outro.valor, self.moeda) elif isinstance(outro, (int, float)): return Dinheiro(self.valor + outro, self.moeda) return NotImplemented def __sub__(self, outro): """Polimorfismo para o operador -.""" if isinstance(outro, Dinheiro): return Dinheiro(self.valor - outro.valor, self.moeda) return Dinheiro(self.valor - outro, self.moeda) def __mul__(self, escalar): """Multiplicação por escalar.""" if isinstance(escalar, (int, float)): return Dinheiro(self.valor * escalar, self.moeda) return NotImplemented def __rmul__(self, escalar): """Escalar * Dinheiro.""" return self.__mul__(escalar) def __eq__(self, outro): """Comparação de igualdade.""" if isinstance(outro, Dinheiro): return self.valor == outro.valor and self.moeda == outro.moeda return self.valor == outro def __lt__(self, outro): """Menor que (<).""" if isinstance(outro, Dinheiro): return self.valor < outro.valor return self.valor < outro def __str__(self): simbolos = {"BRL": "R$", "USD": "$", "EUR": "€"} simbolo = simbolos.get(self.moeda, self.moeda) return f"{simbolo}{self.valor:.2f}" def __repr__(self): return f"Dinheiro({self.valor}, '{self.moeda}')" # Demonstração print("=== Polimorfismo com Métodos Especiais ===\n") r1 = Dinheiro(100, "BRL") r2 = Dinheiro(50, "BRL") r3 = Dinheiro(30, "USD") print(f"r1 = {r1}") print(f"r2 = {r2}") print(f"r1 + r2 = {r1 + r2}") print(f"r1 + 25 = {r1 + 25}") print(f"3 * r1 = {3 * r1}") print(f"r1 - r2 = {r1 - r2}") print(f"r1 > r2? {r1 > r2}") print(f"r1 == 100? {r1 == 100}") # Exemplo com diferentes objetos que implementam métodos especiais print("\n=== Operações Polimórficas ===") valores = [ Dinheiro(200, "BRL"), 150, Dinheiro(75, "BRL"), 300 ] # Soma tudo (polimorfismo puro) total = valores[0] for v in valores[1:]: total = total + v print(f"Soma total: {total}") # Exemplo de função que funciona com qualquer tipo que tenha __len__ def tamanho_duplicado(obj): """Retorna o dobro do tamanho de qualquer objeto com __len__.""" return len(obj) * 2 print(f"\nTamanho duplicado:") print(f"Lista: {tamanho_duplicado([1, 2, 3])}") print(f"String: {tamanho_duplicado('Python')}") print(f"Dict: {tamanho_duplicado({'a':1, 'b':2})}") print(f"Dinheiro: {tamanho_duplicado(Dinheiro(100))}") # Não tem __len__ |
Métodos especiais tornam seus objetos cidadãos de primeira classe. A fórmula do polimorfismo pode ser expressa assim: \(P = \frac{N_{\text{tipos suportados}}}{N_{\text{código repetido}}}\) Polimorfismo reduz drasticamente a duplicação de código. Portanto, domine essa técnica para sistemas verdadeiramente flexíveis. Seu código será mais limpo, curto e elegante. Finalmente, pratique com exemplos reais do seu dia a dia.