Polimorfismo na prática
1. O que é polimorfismo e por que ele importa em Python?
Polimorfismo, do grego "muitas formas", é um dos pilares da programação orientada a objetos. Em sua definição clássica, significa que uma mesma interface pode ter múltiplas implementações. Em Python, esse conceito ganha contornos especiais devido à natureza dinâmica da linguagem.
Diferente de linguagens estáticas como Java ou C++, onde o polimorfismo exige hierarquias de classes formais, Python adota uma abordagem mais flexível. Aqui, o que importa não é o tipo do objeto, mas sim seus comportamentos. Isso nos leva diretamente ao conceito de duck typing.
2. Duck typing: o polimorfismo implícito do Python
O duck typing resume-se na frase: "se anda como pato e grasna como pato, então é pato". Em termos práticos, significa que Python não verifica o tipo de um objeto, mas sim se ele possui os métodos necessários.
class Pato:
def quack(self):
return "Quack! Quack!"
class Pessoa:
def quack(self):
return "Eu imito um pato: Quack!"
class Carro:
def buzinar(self):
return "Bip bip!"
def fazer_quack(objeto):
return objeto.quack()
# Funciona com Pato e Pessoa, mas falha com Carro
print(fazer_quack(Pato())) # Quack! Quack!
print(fazer_quack(Pessoa())) # Eu imito um pato: Quack!
# print(fazer_quack(Carro())) # AttributeError: 'Carro' object has no attribute 'quack'
A vantagem é a flexibilidade: qualquer objeto com o método quack() funciona. O risco? Erros só aparecem em tempo de execução.
3. Polimorfismo com herança e sobrescrita de métodos
A forma mais tradicional de polimorfismo em POO é através da herança. Classes filhas sobrescrevem métodos da classe pai, mantendo a mesma interface.
class Animal:
def fazer_som(self):
raise NotImplementedError("Subclasse deve implementar este método")
class Cachorro(Animal):
def fazer_som(self):
return "Au au!"
class Gato(Animal):
def fazer_som(self):
return "Miau!"
class Vaca(Animal):
def fazer_som(self):
return "Muuu!"
# Laço polimórfico
animais = [Cachorro(), Gato(), Vaca(), Cachorro()]
for animal in animais:
print(animal.fazer_som())
# Au au!
# Miau!
# Muuu!
# Au au!
Note como o mesmo método fazer_som() produz resultados diferentes dependendo do objeto. Isso é polimorfismo em ação.
4. Protocolos e classes abstratas (ABC)
Para garantir que subclasses implementem métodos obrigatórios, Python oferece o módulo abc (Abstract Base Classes).
from abc import ABC, abstractmethod
import math
class Forma(ABC):
@abstractmethod
def area(self):
pass
class Quadrado(Forma):
def __init__(self, lado):
self.lado = lado
def area(self):
return self.lado ** 2
class Circulo(Forma):
def __init__(self, raio):
self.raio = raio
def area(self):
return math.pi * self.raio ** 2
class Triangulo(Forma):
def __init__(self, base, altura):
self.base = base
self.altura = altura
def area(self):
return (self.base * self.altura) / 2
# Tentar instanciar Forma() diretamente causa TypeError
# forma = Forma() # TypeError!
formas = [Quadrado(5), Circulo(3), Triangulo(4, 6)]
for forma in formas:
print(f"Área: {forma.area():.2f}")
ABCs são ideais quando você precisa de uma interface formal e deseja que erros de implementação apareçam em tempo de desenvolvimento.
5. Polimorfismo com funções de ordem superior
Python trata funções como objetos de primeira classe, permitindo polimorfismo através de callables.
def somar(a, b):
return a + b
def subtrair(a, b):
return a - b
def multiplicar(a, b):
return a * b
def executar_operacao(a, b, operacao):
return f"Resultado: {operacao(a, b)}"
# Funções comuns
print(executar_operacao(10, 5, somar)) # Resultado: 15
print(executar_operacao(10, 5, subtrair)) # Resultado: 5
# Lambdas
print(executar_operacao(10, 5, lambda x, y: x / y)) # Resultado: 2.0
# Objetos com __call__
class Potencia:
def __call__(self, x, y):
return x ** y
potencia = Potencia()
print(executar_operacao(2, 3, potencia)) # Resultado: 8
Qualquer objeto que possa ser chamado como função funciona. Isso é polimorfismo sem classes.
6. Operadores polimórficos com métodos dunder
Os métodos "dunder" (double underscore) permitem que objetos personalizados se comportem como tipos nativos do Python.
class Vetor2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, outro):
return Vetor2D(self.x + outro.x, self.y + outro.y)
def __mul__(self, escalar):
return Vetor2D(self.x * escalar, self.y * escalar)
def __eq__(self, outro):
return self.x == outro.x and self.y == outro.y
def __repr__(self):
return f"Vetor2D({self.x}, {self.y})"
def __len__(self):
return int((self.x ** 2 + self.y ** 2) ** 0.5)
v1 = Vetor2D(3, 4)
v2 = Vetor2D(1, 2)
print(v1 + v2) # Vetor2D(4, 6)
print(v1 * 2) # Vetor2D(6, 8)
print(v1 == Vetor2D(3, 4)) # True
print(len(v1)) # 5 (magnitude do vetor)
len(), str(), repr() e operadores como +, *, == são polimórficos por natureza. Cada tipo implementa seu próprio comportamento.
7. Estudo de caso: sistema de plugins polimórfico
Vamos construir um sistema de processamento de arquivos que aceita novos formatos sem modificar o código existente.
from abc import ABC, abstractmethod
import json
class Processador(ABC):
@abstractmethod
def processar(self, caminho):
pass
class ProcessadorCSV(Processador):
def processar(self, caminho):
with open(caminho, 'r') as f:
linhas = f.readlines()
cabecalho = linhas[0].strip().split(',')
dados = [dict(zip(cabecalho, linha.strip().split(',')))
for linha in linhas[1:]]
return dados
class ProcessadorJSON(Processador):
def processar(self, caminho):
with open(caminho, 'r') as f:
return json.load(f)
class ProcessadorXML(Processador):
def processar(self, caminho):
import xml.etree.ElementTree as ET
tree = ET.parse(caminho)
root = tree.getroot()
return {child.tag: child.text for child in root}
class ProcessadorArquivos:
def __init__(self):
self._processadores = {}
def registrar(self, extensao, processador):
self._processadores[extensao] = processador
def processar(self, caminho):
extensao = caminho.split('.')[-1]
if extensao not in self._processadores:
raise ValueError(f"Formato .{extensao} não suportado")
return self._processadores[extensao].processar(caminho)
# Uso
sistema = ProcessadorArquivos()
sistema.registrar('csv', ProcessadorCSV())
sistema.registrar('json', ProcessadorJSON())
sistema.registrar('xml', ProcessadorXML())
# Para adicionar um novo formato, basta criar uma nova classe
# e registrá-la, sem modificar o código existente
8. Boas práticas e armadilhas comuns
Quando usar cada abordagem:
- Duck typing: Para funções utilitárias simples e quando a flexibilidade é prioridade
- Herança: Quando há relação clara "é um" entre as classes
- ABCs: Para APIs públicas, bibliotecas e quando você precisa garantir uma interface
Armadilhas a evitar:
# EVITE: uso excessivo de isinstance/type
def processar_pagamento(metodo):
if isinstance(metodo, CartaoCredito):
# ...
elif isinstance(metodo, Boleto):
# ...
# Isso quebra o polimorfismo!
# PREFIRA: delegar para o método do objeto
def processar_pagamento(metodo):
return metodo.processar()
Testando código polimórfico:
from unittest.mock import Mock
def test_fazer_quack():
pato_mock = Mock()
pato_mock.quack.return_value = "Mock Quack!"
resultado = fazer_quack(pato_mock)
assert resultado == "Mock Quack!"
pato_mock.quack.assert_called_once()
O polimorfismo em Python é uma ferramenta poderosa que, quando usada corretamente, resulta em código mais flexível, reutilizável e fácil de manter. A chave está em programar para interfaces, não para implementações concretas.
Referências
- Python Documentation: Duck Typing — Definição oficial de duck typing e seu papel no design de software Python
- Real Python: Python Polymorphism — Guia completo sobre polimorfismo em Python com exemplos práticos
- PEP 3119 – Introducing Abstract Base Classes — Proposta original que introduziu ABCs em Python
- Python Docs: abc module — Documentação oficial do módulo
abccom exemplos de uso - GeeksforGeeks: Polymorphism in Python — Tutorial abrangente cobrindo duck typing, herança e ABCs
- Python Tricks: Duck Typing and EAFP — Artigo aprofundado sobre duck typing e a filosofia "Easier to Ask for Forgiveness than Permission"
- Fluent Python: Protocolos e ABCs — Capítulo do livro referência sobre Python abordando protocolos e classes abstratas