Python: Polimorfismo e Métodos Mágicos
Última atualização: 2026-09-22
As últimas duas lições cobriram a "forma" das classes e herança. Esta lição cobre o "espírito." Polimorfismo permite que objetos de diferentes classes sejam chamados da mesma forma — você não precisa saber sua classe específica, apenas que eles têm o método necessário. Métodos mágicos fazem classes personalizadas se comportarem tão naturalmente quanto tipos embutidos.
1. O que é Polimorfismo
Polimorfismo significa "muitas formas" — a mesma interface, implementações diferentes:
class Cachorro:
def falar(self):
return "Au au!"
class Gato:
def falar(self):
return "Miau!"
class Pato:
def falar(self):
return "Quack!"
# Polimorfismo: independente da class, desde que tenha um method falar()
animais = [Cachorro(), Gato(), Pato()]
for animal in animais:
print(animal.falar())
Saída:
Au au!
Miau!
Quack!
▶ Exemplo: Cálculo de Área de Formas (Dificuldade ⭐⭐)
▶ ExperimenteSaída:
Área: 12.00
Área: 78.54
Área: 24.00
2. Métodos Mágicos: Tornando Classes Mais "Pytônicas"
Métodos mágicos são métodos especiais com prefixos e sufixos de sublinhado duplo — __init__, __str__, __len__, etc. Eles fazem classes personalizadas se comportarem como tipos embutidos.
(1) __str__ e __repr__: Impressão Mais Amigável
class Pessoa:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
def __str__(self):
"""Representação amigável para o usuário"""
return f"{self.nome} ({self.idade} anos)"
def __repr__(self):
"""Representação para desenvolvedor"""
return f"Pessoa('{self.nome}', {self.idade})"
p = Pessoa("João Silva", 25)
print(p) # João Silva (25 anos) — chama __str__
print(repr(p)) # Pessoa('João Silva', 25) — chama __repr__
(2) __len__: Fazendo Objetos Suportarem len()
class Time:
def __init__(self, membros=None):
self.membros = membros or []
def add(self, membro):
self.membros.append(membro)
def __len__(self):
return len(self.membros)
def __str__(self):
return f"Time({', '.join(self.membros)})"
time = Time()
time.add("João Silva")
time.add("Maria Souza")
time.add("Pedro Santos")
print(len(time)) # 3 — chama __len__
print(time) # Time(João Silva, Maria Souza, Pedro Santos)
(3) __getitem__: Fazendo Objetos Suportarem Indexação
class Playlist:
def __init__(self):
self.musicas = []
def add(self, musica):
self.musicas.append(musica)
def __getitem__(self, index):
return self.musicas[index]
def __len__(self):
return len(self.musicas)
playlist = Playlist()
playlist.add("Dia Ensolarado")
playlist.add("Fragrância das Sete Léguas")
playlist.add("Noturno")
print(playlist[0]) # Dia Ensolarado — chama __getitem__
print(len(playlist)) # 3
# Suporta fatiamento e iteração
for musica in playlist:
print(musica, end=" ") # Dia Ensolarado Fragrância das Sete Léguas Noturno
3. Referência de Métodos Mágicos Comuns
class Demo:
def __init__(self, value): # Construtor
self.value = value
def __str__(self): # str() / print()
return str(self.value)
def __repr__(self): # repr() / depuração
return f"Demo({self.value})"
def __len__(self): # len()
return 1
def __bool__(self): # bool() / condição if
return self.value != 0
def __eq__(self, outro): # ==
return self.value == outro.value
def __lt__(self, outro): # < (para ordenação)
return self.value < outro.value
def __add__(self, outro): # operador +
return Demo(self.value + outro.value)
def __getitem__(self, chave): # obj[chave]
return chave
▶ Exemplo: Classe Fração (Dificuldade ⭐⭐⭐)
▶ ExperimenteSaída:
# Function defined successfully
▶ Exemplo: Área e Perímetro de Formas (Dificuldade ⭐⭐)
▶ ExperimenteSaída:
Rectangle: área=12.00, perímetro=14.00
Circle: área=78.54, perímetro=31.42
Square: área=36.00, perímetro=24.00
Área total: 126.54
4. @dataclass: Definindo Rapidamente Classes de Dados
Escrever uma classe apenas para armazenar dados, com __init__, __repr__, __eq__ manuais, é tedioso. @dataclass os gera automaticamente:
from dataclasses import dataclass
@dataclass
class Ponto:
x: float
y: float
@dataclass
class Aluno:
nome: str
idade: int
nota: float = 0.0 # Valor padrão
# Obtém automaticamente __init__, __repr__, __eq__
p1 = Ponto(3.0, 4.0)
p2 = Ponto(3.0, 4.0)
print(p1) # Ponto(x=3.0, y=4.0)
print(p1 == p2) # True (compara todos os campos automaticamente)
s = Aluno("João Silva", 20, 85.5)
print(s) # Aluno(nome='João Silva', idade=20, nota=85.5)
@dataclass: 1) Uma classe que armazena principalmente dados com poucos métodos. 2) Você precisa de código boilerplate como __init__, __repr__, __eq__. 3) Você não quer escrever isso manualmente. Para casos simples, é muito mais eficiente que escrever classes manualmente.
5. Casos de Uso Comuns
- Duck typing:
len(),for...in,sorted()do Python são todos baseados em protocolo — implemente o método correspondente e sua classe pode usá-los. - Containers de dados: Implemente
__getitem__+__len__para fazer sua classe suportar indexação e iteração. - Sobrecarga de operadores:
__add__,__eq__,__lt__permitem que classes personalizadas suportem+,==,<etc. - Classes de dados:
@dataclasspara definir rapidamente configurações, DTOs, objetos de valor.
❓ Perguntas Frequentes
print() chama __str__, len() chama __len__, == chama __eq__. Você não os chama diretamente; apenas os implementa, e Python os chama automaticamente no momento certo.@dataclass e uma classe regular?@dataclass gera automaticamente os métodos que você está cansado de escrever. É apenas um gerador de código — a classe gerada é idêntica a uma escrita manualmente. Classes decoradas com @dataclass também podem ter seus próprios métodos. Se você se pega repetidamente escrevendo __init__ para armazenar propriedades, é hora de usar @dataclass.📖 Resumo
- Polimorfismo: objetos de diferentes classes com o mesmo nome de método podem ser chamados da mesma forma
- Duck typing: "Se faz quack, é um pato" — importa os métodos, não os tipos
- Métodos mágicos:
__str__(impressão),__len__(comprimento),__getitem__(indexação),__eq__(igualdade),__add__(adição), etc. @dataclassgera automaticamente__init__,__repr__,__eq__— ideal para classes de dados puras
📝 Exercícios
-
Iniciante (Dificuldade ⭐): Defina uma classe
Livroimplementando__str__e__len__(retornando o número de páginas). Crie dois livros e exibaprint(livro)elen(livro). -
Intermediário (Dificuldade ⭐⭐): Use
@dataclasspara definir uma classeProduto(nome, preço, quantidade). Implemente__add__— mescle dois produtos com o mesmo nome (some quantidades, calcule preço médio). Dica: Implemente dentro da classe @dataclass. -
Avançado (Dificuldade ⭐⭐⭐): Defina uma classe
Baralho(um baralho de cartas) implementando__len__,__getitem__(suportando indexação e fatiamento) eembaralhar()(usandorandom.shuffle). Depois escreva código para criar um baralho e distribuir as primeiras 5 cartas como uma mão.