A classe pai não precisa ter uma implementação real — ela só precisa declarar que o método existe. Isso é o 'contrato': qualquer código que receber um objeto dessa família pode confiar que o método estará lá.
Python não obriga uma subclasse a sobrescrever o método. Se você esquecer, a versão do pai é herdada automaticamente — incluindo um return 0 indesejado. Isso é o problema que Classes Abstratas (Aula 10) resolvem.
Dica: Sempre que criar uma classe base para polimorfismo, deixe um comentário explícito que o método deve ser sobrescrito. Ou use ABC — veremos na próxima aula.
Duck Typing é uma consequência do Python ser dinamicamente tipado. Não existe verificação de tipo em tempo de compilação — por isso o Python toma a decisão em runtime: 'esse objeto tem o método? Então executa.'
Use hasattr() (LBYL) quando processar dados vindos de fontes externas onde objetos sem o método são um caso comum e esperado. Use try/except (EAFP) quando o fluxo normal é sempre ter o método — e a ausência é uma exceção rara e inesperada.
# LBYL — para quando erro é comum
if hasattr(obj, 'pagar'):
obj.pagar(valor)
# EAFP — para quando erro é exceção
try:
obj.pagar(valor)
except AttributeError:
print('Método não encontrado') Regra prática: em código que você controla (suas próprias classes), confie no Duck Typing sem verificação. Em código que recebe dados externos (API, input do usuário, plugins), adicione proteção.
Tradução literal: 'Olhe antes de pular'. A filosofia é simples: verifique se a condição é segura antes de executar a ação. No código, isso aparece como um if/hasattr antes de chamar o método.
# LBYL: 'esse objeto tem o método? então chamo'
if hasattr(obj, 'pagar'):
obj.pagar(valor) É o estilo mais comum em linguagens como Java e C#, onde verificar antes de agir é considerado mais seguro e explícito.
Tradução literal: 'É mais fácil pedir perdão do que permissão'. A filosofia é: tente executar direto e trate o erro se ele acontecer. É o estilo preferido da comunidade Python — mais conciso e mais rápido quando o caminho feliz é o caso comum.
# EAFP: 'tenta executar — se falhar, trata'
try:
obj.pagar(valor)
except AttributeError:
print('objeto não tem pagar()') A frase EAFP vem do mundo real: é mais prático tentar entrar num restaurante e ser avisado que está lotado do que ligar antes para cada possibilidade de problema.
Python define 'protocolos' informais — conjuntos de métodos que, quando implementados, integram sua classe ao comportamento nativo. Não é herança. É puro Duck Typing no nível da linguagem.
__len__: Protocolo Sized — len(obj) funciona.__str__: Protocolo de representação textual — print(obj) e str(obj).__getitem__: Protocolo de indexação — obj[0] funciona.__iter__: Protocolo de iteração — for item in obj funciona.__eq__: Protocolo de igualdade — obj1 == obj2 funciona.A função len() funciona com string, lista, dicionário e sua classe Turma — sem herança comum entre eles. É Duck Typing: todos têm __len__, então todos 'são medíveis' para o Python.
Programação Orientada a Objetos com Python
Tecnologia em Análise e Desenvolvimento de Sistemas
plano de voo
Por que código sem polimorfismo se torna um pesadelo.
O que é polimorfismo de verdade, sem jargão.
Polimorfismo por herança e Duck Typing — quando usar cada um.
Sistemas reais que usam polimorfismo sem você perceber.
conexão com a aula anterior
Sobrescrita (override) é o mecanismo. Polimorfismo é o resultado que enxergamos de fora.
começando pelo problema
elif. Se você tem if/elif verificando tipos de objetos para decidir o que fazer — seu código está pedindo polimorfismo.
o problema em código
Veja o que acontece quando você tem 3 tipos de funcionário e não usa polimorfismo.
tipo existe só para compensar a falta de polimorfismo.class Clt:
def __init__(self, salario_base):
self.salario_base = salario_base
class Freelancer:
def __init__(self, valor_hora, horas):
self.valor_hora = valor_hora
self.horas = horas
class Socio:
def __init__(self, lucro, percentual):
self.lucro = lucro
self.percentual = percentual
# Toda a lógica centralizada aqui — e presa aqui para sempre
def calcular_pagamento(funcionario, tipo):
if tipo == 'clt':
return funcionario.salario_base
elif tipo == 'freelancer':
return funcionario.valor_hora * funcionario.horas
elif tipo == 'socio':
return funcionario.lucro * funcionario.percentual
# novo tipo? precisa editar aqui. sempre. a solução
calcular_pagamento precisa saber como cada tipo de funcionário é pago? Mova a lógica para dentro da classe. Deixe o objeto responsável por saber o que fazer quando você mandar a mesma ordem.
a solução em código
Movemos a lógica para dentro de cada classe. Todas têm o mesmo método — mas cada uma implementa do seu jeito.
calcular_salario() — mas com lógica própria.class Clt:
def __init__(self, salario_base):
self.salario_base = salario_base
def calcular_salario(self):
return self.salario_base
class Freelancer:
def __init__(self, valor_hora, horas):
self.valor_hora = valor_hora
self.horas = horas
def calcular_salario(self):
return self.valor_hora * self.horas
class Socio:
def __init__(self, lucro, percentual):
self.lucro = lucro
self.percentual = percentual
def calcular_salario(self):
return self.lucro * self.percentual o resultado do polimorfismo
Com o polimorfismo no lugar, o código que usa os objetos fica radicalmente mais simples — e nunca mais precisa ser alterado.
calcular_salario() e adicione na lista. Zero mudança no loop.funcionarios = [
Clt(3000),
Freelancer(80, 40),
Socio(50000, 0.05),
Clt(4500), # novo clt? só adicionar na lista
]
# Mesmo comando para todos. Cada um responde do seu jeito.
for f in funcionarios:
print(f.calcular_salario())
# Saída:
# 3000
# 3200
# 2500.0
# 4500 definição
1️⃣ Cada classe tem o método com o mesmo nome.
2️⃣ Você cria uma lista com objetos de tipos diferentes.
3️⃣ Você chama o método em todos — sem if, sem perguntar quem é quem.
antes e depois · lado a lado
exemplo do cotidiano
Você, como usuário, manda a mesma ordem. O sistema não te pergunta 'qual bandeira você quer usar para processar?'. Ele simplesmente processa — cada objeto do jeito que sabe.
as duas formas de polimorfismo em python
Herança quando os objetos têm parentesco lógico real (Cão é Animal). Duck Typing quando objetos não relacionados precisam responder ao mesmo comando.
polimorfismo por herança
O nome do método precisa ser idêntico em todas as classes da hierarquia. É o nome que garante a interface comum.
herança · exemplo clássico
Todos são animais. Todos 'falam'. Cada um fala do seu jeito.
falar().Leao(Animal) com falar(). O loop já funciona com ela — sem alterar nada.class Animal:
def falar(self):
pass # a classe pai apenas declara que o método existe
class Cachorro(Animal):
def falar(self):
print("Au! Au!")
class Gato(Animal):
def falar(self):
print("Miau!")
class Papagaio(Animal):
def falar(self):
print("Quem vive, vê!")
# Polimorfismo em ação:
animais = [Cachorro(), Gato(), Papagaio(), Cachorro()]
for animal in animais:
animal.falar() # mesma ordem — reações diferentes herança · formas geométricas
O exemplo clássico da literatura de POO — funciona muito bem para mostrar o conceito.
Forma base retorna 0 — subclasses que esquecerem de implementar area() vão herdar esse zero silenciosamente.ABC) vão forçar que toda subclasse implemente o método, ou o Python vai reclamar.class Forma:
def area(self):
return 0 # ⚠️ provisório — Aula 10 resolve com ABC
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 3.14 * (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
formas = [Quadrado(4), Circulo(3), Triangulo(6, 4)]
for f in formas:
print(f"Área: {f.area()}") # mesma chamada, resultados diferentes por que isso importa
Adicione um novo tipo criando uma nova classe. O restante do sistema não precisa ser tocado.
Não existe mais 'se é X faça isso, se é Y faça aquilo'. Os objetos se viram sozinhos.
Quem usa os objetos não precisa conhecer os detalhes internos de cada um. Só precisa saber o nome do método.
duck typing — caminho 2
Nessas linguagens, para ter polimorfismo você obrigatoriamente precisa herdar de uma classe base ou implementar uma interface. Em Python, basta ter o método.
duck typing · o clássico do pato
Pato e Pessoa não têm nenhuma relação de herança. Mas ambos sabem fazer quack — e isso é suficiente.
Pessoa não herda de Pato. Não existe classe base em comum. Não importa.quack()?' Se sim, executa.class Pato:
def quack(self):
print("Quack! Quack!")
class Pessoa:
def quack(self):
print("Eu estou imitando um pato!")
# Nenhuma herança. Nem precisamos.
# Se tem quack() — funciona.
def faz_quack(objeto):
objeto.quack()
faz_quack(Pato()) # Quack! Quack!
faz_quack(Pessoa()) # Eu estou imitando um pato! duck typing · sistema real
Três formas de pagar. Nenhuma herança. Puro Duck Typing.
Cripto? Crie a classe com pagar(). A função finalizar_venda já funciona com ela.finalizar_venda não sabe — e não precisa saber — qual tipo de pagamento está recebendo.class Pix:
def pagar(self, valor):
print(f"QR Code gerado — R$ {valor:.2f}")
class CartaoCredito:
def __init__(self, final):
self.final = final
def pagar(self, valor):
print(f"Cartão {self.final} autorizado — R$ {valor:.2f}")
class Boleto:
def pagar(self, valor):
print(f"Boleto gerado — R$ {valor:.2f} (vence em 3 dias)")
# Nenhuma herança. Todos têm pagar(). Isso basta.
def finalizar_venda(metodo, total):
metodo.pagar(total)
finalizar_venda(Pix(), 150.00)
finalizar_venda(CartaoCredito("1234"), 300.00)
finalizar_venda(Boleto(), 89.90) duck typing · cuidado
hasattr) ou tentar e tratar o erro (try/except). Em código bem escrito com Duck Typing, você sabe quais objetos vai receber. O tratamento de erro é para casos externos — APIs, dados de usuário, plugins.
duck typing · tratamento de erro
LBYL verifica antes. EAFP tenta e trata. O Python prefere EAFP — mas LBYL tem seu lugar.
hasattr(): Mais explícito. Bom quando receber um objeto sem o método é um caso comum esperado.try/except: O estilo preferido do Python. Mais conciso. Bom quando o erro é uma exceção rara.# LBYL — Look Before You Leap
# Verifica se o método existe antes de chamar
def finalizar_venda_segura(metodo, total):
if hasattr(metodo, 'pagar'):
metodo.pagar(total)
else:
print(f"Erro: {type(metodo).__name__} não tem método pagar()")
# EAFP — Easier to Ask Forgiveness than Permission
# Tenta executar e trata o erro se acontecer
def finalizar_venda_pythonica(metodo, total):
try:
metodo.pagar(total)
except AttributeError:
print(f"Erro: {type(metodo).__name__} não sabe pagar()") comparativo técnico
polimorfismo que você já usa
len(lista), len(string), len(dicionário): Funcionam porque todos implementam __len__. Duck Typing nativo do Python. print(objeto): Funciona em qualquer objeto porque Python chama __str__ — que você já aprendeu a sobrescrever. Métodos com duplo underscore (__) que integram sua classe ao comportamento nativo do Python. Ao implementar __len__, sua classe passa a ser compatível com len() — sem herdar nada.
dunder methods na prática
Ao implementar __len__, sua classe passa a responder à função nativa len() — exatamente como listas e strings.
tamanho() ou quantidade(). Você fez sua classe falar a língua nativa do Python.len() funciona em tudo que tem __len__ — lista, string, dicionário, e agora Turma.class Turma:
def __init__(self, alunos):
self.alunos = alunos
def __len__(self):
return len(self.alunos)
def __str__(self):
return f"Turma com {len(self)} alunos"
aula = Turma(['Ana', 'Bruno', 'Carla', 'Diego'])
print(len(aula)) # 4 — Python chamou __len__ internamente
print(aula) # Turma com 4 alunos — Python chamou __str__ conhecendo antes de evitar
É uma função nativa do Python que verifica se um objeto pertence a uma determinada classe. Parece útil — e às vezes é. Mas tem uma armadilha.
isinstance(obj, Classe) retorna True ou False. Útil para validar entrada de dados.if/elif de tipo que vimos no início da aula.class Cachorro:
def falar(self):
print("Au!")
class Gato:
def falar(self):
print("Miau!")
dog = Cachorro()
# isinstance() pergunta: 'esse objeto é dessa classe?'
print(isinstance(dog, Cachorro)) # True
print(isinstance(dog, Gato)) # False
# O problema: usar isso para decidir o que fazer
def fazer_falar(animal):
if isinstance(animal, Cachorro):
animal.falar()
elif isinstance(animal, Gato):
animal.falar()
# novo animal? precisa abrir aqui e adicionar mais um elif boas práticas
isinstance(obj, Cachorro) pergunta 'quem você é?'. Polimorfismo não pergunta — só manda agir. obj.agir() e deixe cada objeto saber o que 'agir' significa. Validação de entrada de dados (verificar se o usuário passou o tipo certo), debugging e logging de erros. O problema é quando isinstance() substitui polimorfismo em lógica de negócio.
resumo de conceitos
| item | detalhe |
|---|---|
| Polimorfismo | Mesma chamada de método em objetos diferentes — cada um responde com sua própria implementação. |
| Interface Comum | O nome do método que todos os objetos compartilham. É o 'contrato' informal que permite o polimorfismo. |
| Duck Typing | Estilo Python: não importa a classe do objeto, importa se ele tem o método que você precisa. |
| Dunder Method | Método com __ duplo que integra sua classe ao comportamento nativo Python (len, str, comparações etc). |
| Late Binding | Python decide qual método chamar no momento da execução — por isso listas heterogêneas funcionam. |
| EAFP | Easier to Ask Forgiveness than Permission — estilo Pythonico de tentar e tratar o erro, em vez de verificar antes. |
laboratório em sala · duplas
Em duplas. 20 minutos. O objetivo é implementar um sistema de notificações usando Duck Typing — sem herança obrigatória.
Classes Email, SMS e PushNotification. Cada uma com método enviar(msg) que imprime uma mensagem diferente.
Função alerta_geral(canais, mensagem) que percorre a lista e chama enviar() em cada canal. Sem if. Sem isinstance.
Coloque um try/except AttributeError no disparador para que um canal sem enviar() não quebre o sistema — apenas pule e avise.
Adicione um objeto qualquer na lista (ex: um número inteiro) e confirme que o sistema sobrevive sem travar.
resolução · laboratório em sala
As três classes não têm nenhum parentesco entre si. O único ponto em comum é o nome do método: enviar(msg). Isso é suficiente para o Duck Typing funcionar.
resolução · passo 1
Classes independentes, mesmo método, saídas diferentes.
SMS já tem lógica própria (limite de 50 chars) — exatamente o ponto do polimorfismo: cada um resolve do seu jeito.class Email:
def enviar(self, msg):
print(f"📧 [EMAIL] De: sistema@app.com")
print(f" Mensagem: {msg}")
class SMS:
def enviar(self, msg):
resumo = msg[:50] + '...' if len(msg) > 50 else msg
print(f"📱 [SMS] {resumo}")
class PushNotification:
def enviar(self, msg):
print(f"🔔 [PUSH] ⚠️ Alerta: {msg}") resolução · passo 2
A função alerta_geral não sabe — e não precisa saber — qual tipo de canal está recebendo.
enviar() é chamado.WhatsApp? Crie a classe com enviar() e adicione na lista. Zero mudança na função.def alerta_geral(canais, mensagem):
print(f"--- Disparando alerta para {len(canais)} canal(is) ---")
for canal in canais:
canal.enviar(mensagem) # Duck Typing puro — sem if, sem isinstance
print("--- Fim do disparo ---")
canais = [
Email(),
SMS(),
PushNotification(),
Email(),
]
alerta_geral(canais, "Servidor em manutenção às 23h") resolução · passo 3 e 4
Adicionando try/except para que um canal quebrado não derrube o sistema inteiro.
try/except AttributeError captura qualquer objeto que não tenha enviar() e segue para o próximo.def alerta_geral(canais, mensagem):
print(f"--- Disparando alerta para {len(canais)} canal(is) ---")
for canal in canais:
try:
canal.enviar(mensagem)
except AttributeError:
nome = type(canal).__name__
print(f"⚠️ [{nome}] ignorado — não implementa enviar()")
print("--- Fim do disparo ---")
canais = [
Email(),
SMS(),
42, # ← vai falhar
PushNotification(),
"texto qualquer", # ← também vai falhar
]
alerta_geral(canais, "Teste de resiliência") resolução · saída esperada
O que o terminal exibe ao rodar o código final com os objetos inválidos na lista.
Email, SMS e PushNotification executaram normalmente via Duck Typing.int e str foram capturados pelo except — o sistema sobreviveu sem travar.--- Disparando alerta para 5 canal(is) ---
📧 [EMAIL] De: sistema@app.com
Mensagem: Teste de resiliência
📱 [SMS] Teste de resiliência
⚠️ [int] ignorado — não implementa enviar()
🔔 [PUSH] ⚠️ Alerta: Teste de resiliência
⚠️ [str] ignorado — não implementa enviar()
--- Fim do disparo --- encerramento
exercícios para casa · visão geral
Do fácil ao expert — construindo polimorfismo camada por camada.
Interface comum em classes simples.
Listas heterogêneas e Dunder Methods.
Sistema de plugins via Duck Typing.
Refatorar if/elif para polimorfismo.
exercícios · fácil
Use herança. Crie a classe pai Instrumento com o método tocar(). Depois crie Piano, Violao e Bateria herdando dela — cada uma sobrescreve tocar() com seu próprio som. Percorra uma lista com os três chamando tocar() em cada um.
Use Duck Typing — sem herança, sem classe pai. Crie Carro, Aviao e Barco diretamente, cada um com o método mover() imprimindo como se locomove. Percorra uma lista com os três chamando mover().
exercícios · médio
Use herança. Crie a classe pai Forma com o método area(). Depois crie Circulo(raio), Retangulo(base, altura) e Triangulo(base, altura) herdando dela — cada uma implementa area() com sua fórmula. Por fim, crie a função calcular_area_total(lista) que percorre a lista e soma todas as áreas sem nenhum if.
Crie a classe Playlist(nome, musicas) onde musicas é uma lista de strings. Implemente __len__ para que len(playlist) retorne a quantidade de músicas, e __str__ para que print(playlist) exiba algo como Playlist 'Rock Clássico' — 5 músicas.
exercícios · difícil e expert
Use Duck Typing puro — sem herança. Crie PDFExporter e CSVExporter, cada um com o método exportar(dados) que imprime os dados formatados do seu jeito. Crie a função exportar_relatorio(exporter, dados) que chama exportar() sem saber qual classe está recebendo. Bônus: adicione um try/except AttributeError para o caso de receber um objeto inválido.
Refatore o código abaixo para polimorfismo — cada classe Usuario deve implementar acessar() com sua própria lógica, eliminando todos os if/elif:def liberar_acesso(usuario, tipo):
if tipo == 'admin':
print('Acesso total')
elif tipo == 'editor':
print('Acesso a conteúdo')
elif tipo == 'cliente':
print('Acesso básico')
revisão final
Lembra do return 0 na classe Forma? Na próxima aula vamos resolver isso de vez com Classes Abstratas — forçando que toda subclasse implemente os métodos necessários.