O polimorfismo nos deu uma vitória clara: deixamos de escrever if/elif gigantes para verificar o tipo de cada objeto. Cada classe respondia area() do seu jeito, e a função que usava nem precisava saber com qual subclasse estava falando.
A classe-mãe Forma tinha um método area() com return 0 dentro. Era só um placeholder, um lembrete educado para o programador implementar o método de verdade na subclasse.
O problema é que um lembrete educado não é um obstáculo. Se o programador esquecer, o Python aceita — e o sistema roda com áreas zeradas.
A aula de hoje resolve isso na raiz: em vez de pedir educadamente, fazemos o Python obrigar.
Uma classe abstrata é uma classe projetada para ser somente herdada. Ela define o contrato — quais métodos toda subclasse precisa ter — sem implementar esses métodos. Em Python, isso é feito herdando da classe ABC do módulo abc.
Forma() levanta TypeError.O ABC usa metaclasses (ABCMeta) para interceptar o __new__ da classe. Quando você tenta criar um objeto, o Python verifica se a classe ainda tem algum método marcado como abstrato. Se tiver, recusa.
from abc import ABC, abstractmethod
class Repositorio(ABC):
@abstractmethod
def salvar(self, obj): pass
@abstractmethod
def buscar(self, id): pass
class RepoMemoria(Repositorio):
def __init__(self):
self._dados = {}
def salvar(self, obj):
self._dados[obj.id] = obj
def buscar(self, id):
return self._dados.get(id)
repo = RepoMemoria() # funciona
# Repositorio() # TypeError Pense em uma classe abstrata como o gabarito de prova: define as perguntas (métodos), mas as respostas (implementações) são de quem faz a prova (as subclasses).
@abstractmethod é um decorador padrão do módulo abc. Ele marca o método como parte do contrato. A marca fica visível para o Python através do atributo __isabstractmethod__ que ele coloca no método.
from abc import ABC, abstractmethod
class Notificador(ABC):
@abstractmethod
def enviar(self, mensagem: str) -> bool:
pass # corpo vazio por convenção pass, você pode escrever uma implementação parcial. A subclasse pode chamá-la com super().metodo().@property: uma propriedade abstrata é decorada com @property e @abstractmethod juntas.@staticmethod e @classmethod: a ordem é @staticmethod ou @classmethod primeiro, depois @abstractmethod.def metodo(self, x: int) -> bool: normalmente — o decorador não atrapalha.Um método abstrato é como um molde de bolo: você sabe a forma final, mas o sabor depende de quem prepara.
@property).Os quatro não são independentes — eles se reforçam. A abstração descreve o contrato. A herança transmite esse contrato e parte da implementação. O polimorfismo usa esse contrato para escrever código flexível. O encapsulamento protege as decisões internas de cada implementação.
Encapsulamento esconde DADOS. Abstração esconde COMPLEXIDADE. São pilares diferentes, fáceis de confundir.
Antes de criar uma ABC, responda: existem ao menos duas implementações concretas que vão compartilhar essa assinatura? Se a resposta for não, ainda não é hora.
Regra prática (de Sandi Metz): só refatore para abstração quando o mesmo padrão aparecer pela terceira vez. Antes disso, duplicação é mais barata que abstração errada.
# Ruim: ABC com apenas uma subclasse concreta
class Logger(ABC):
@abstractmethod
def log(self, msg): pass
class ConsoleLogger(Logger):
def log(self, msg): print(msg)
# Melhor (por enquanto): uma classe concreta. Promova quando aparecer a 2ª.
class Logger:
def log(self, msg):
print(msg) Programação Orientada a Objetos com Python
Tecnologia em Análise e Desenvolvimento de Sistemas
plano de voo
Por que o polimorfismo da aula 09 ainda deixa um risco aberto.
Como o módulo abc obriga subclasses a implementar métodos.
Fechamos o quarto pilar da POO — agora você conhece os quatro.
Sistema de pagamentos com contrato formal entre as classes.
de onde paramos · 9 dias atrás
calcular_salario() num gerente, num analista, num estagiário. if tipo == "gerente"... que cresce sem fim some — quem chama o método não precisa saber o tipo concreto. memória muscular
Uma só função processa qualquer funcionário — porque cada classe sabe calcular o próprio salário.
processar_folha não pergunta o tipo do funcionário — só confia que cada objeto sabe calcular o próprio salário.calcular_salario à sua maneira — polimorfismo em ação.class Funcionario:
def __init__(self, nome, base):
self.nome = nome
self.base = base
def calcular_salario(self):
return self.base
class Gerente(Funcionario):
def calcular_salario(self):
return self.base + 2000 # bônus de liderança
class Vendedor(Funcionario):
def __init__(self, nome, base, comissao):
super().__init__(nome, base)
self.comissao = comissao
def calcular_salario(self):
return self.base + self.comissao
def processar_folha(funcionarios):
for f in funcionarios:
print(f"{f.nome}: R$ {f.calcular_salario():.2f}")
processar_folha([
Funcionario("Ana", 3000),
Gerente("Bruno", 5000),
Vendedor("Carla", 2000, 1500),
]) pergunta pra turma · 2 minutos
Estagiario(Funcionario) e esquecer de escrever o método calcular_salario, o que acontece quando processar_folha for chamada com um estagiário na lista? a resposta · e por que ela importa
calcular_salario em Estagiario, não acha, sobe a hierarquia e usa o de Funcionario — que devolve só self.base. Como fazer o Python se recusar a criar uma subclasse incompleta? É pra isso que serve o módulo abc.
voltando da aula anterior
area(). return 0 em silêncio. Como transformar um método em obrigação, não em sugestão?
começando pelo problema
Hexagono ao sistema, mas esqueceu de implementar area(). Um return 0 ou pass na classe-mãe parece inofensivo, mas vira uma armadilha invisível.
o problema em código
Veja como uma subclasse incompleta passa despercebida. Não há exceção, não há aviso — só números errados.
return 0 é uma sugestão que a subclasse pode (e vai) ignorar sem que ninguém perceba.class Forma:
def area(self):
return 0 # placeholder: "alguém implementa depois"
class Circulo(Forma):
def __init__(self, raio):
self.raio = raio
def area(self):
return 3.14 * self.raio ** 2
class Hexagono(Forma):
def __init__(self, lado):
self.lado = lado
# esqueceu de implementar area()...
h = Hexagono(lado=10)
print(h.area()) # 0 — errado, mas sem erro a ideia da solução
Saímos do "talvez funcione" para o "ou implementa, ou não roda".
as peças que você vai encontrar
ABC é uma classe-mãe especial que vem com o Python, no módulo abc. Quando sua classe herda dela, é como pendurar uma plaquinha gigante dizendo "esta classe é abstrata". Sem isso, o Python não sabe que tem que reclamar. @abstractmethod é um decorador — igualzinho ao @property da aula 04. Você cola em cima de um método e ele ganha um significado especial. O @abstractmethod diz "este método é obrigatório nas subclasses". Mesmo padrão, significado diferente. Por trás disso, o Python tá só usando dois recursos da própria linguagem — uma classe e um decorador. Você vê exatamente o que ele vê.
a solução em código
abc + @abstractmethod Duas linhas mudam tudo: from abc import ABC, abstractmethod e o decorador acima do método.
ABC é a classe-mãe de toda classe abstrata. Herdar dela ativa o sistema de contratos.@abstractmethod é a etiqueta: "este método é parte do contrato — implemente ou não instancie".from abc import ABC, abstractmethod
class Forma(ABC): # agora é uma classe abstrata
@abstractmethod
def area(self): # contrato: subclasses DEVEM implementar
pass
class Circulo(Forma):
def __init__(self, raio):
self.raio = raio
def area(self): # contrato cumprido
return 3.14 * self.raio ** 2
class Hexagono(Forma):
def __init__(self, lado):
self.lado = lado
# esqueceu de implementar area()... o que muda do lado de fora
Tente criar um Hexagono sem implementar area() — o Python recusa antes mesmo do objeto nascer.
Hexagono não nasce — o Python recusa porque o contrato não foi cumprido.Forma pode ser instanciada: ela é abstrata, existe só para ser herdada.>>> from solucao_com_abc import Circulo, Hexagono, Forma
>>> c = Circulo(raio=5)
>>> print(c.area())
78.5
>>> h = Hexagono(lado=10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
h = Hexagono(lado=10)
^^^^^^^^^^^^^^^^^
TypeError: Can't instantiate abstract class Hexagono without an implementation for abstract method 'area'
>>> f = Forma()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
f = Forma()
^^^^^^^
TypeError: Can't instantiate abstract class Forma without an implementation for abstract method 'area' agora podemos dar o nome
@abstractmethod. O corpo é só pass — quem dá o corpo são as subclasses. Classe abstrata = molde. Tentar instanciar um molde não faz sentido — o que vale é o que sai dele.
antes e depois · lado a lado
return 0 herdado TypeError analogia do cotidiano
pass / return 0): a placa pede educadamente. Quem quiser ignora — e ninguém checa. @abstractmethod): a porta literalmente não abre. Você implementa ou não passa. A classe abstrata é a planta baixa de um prédio. Você não mora na planta — mora no prédio construído a partir dela.
anatomia
Toda classe abstrata em Python tem o mesmo esqueleto. Memorize esses quatro pontos.
from abc import ABC, abstractmethod — sempre os dois juntos.
ABCclass Forma(ABC): — esse é o gatilho do sistema de contratos.
@abstractmethodMarque assim os métodos que toda subclasse deve implementar.
passO método abstrato não precisa de corpo — quem dá a implementação é a subclasse.
abstrato e concreto convivem
Uma classe abstrata pode ter métodos abstratos e métodos concretos. Os concretos são herdados normalmente — sem precisar reimplementar.
descrever) são herdados sem esforço — código reutilizado.mover) marcam apenas o que varia entre subclasses.from abc import ABC, abstractmethod
class Veiculo(ABC):
def __init__(self, marca):
self.marca = marca
@abstractmethod
def mover(self): # abstrato: subclasses DEVEM implementar
pass
def descrever(self): # concreto: herdado tal qual
print(f"Veículo da marca {self.marca}")
class Carro(Veiculo):
def mover(self): # só este é obrigatório
print(f"Carro {self.marca} andando na estrada")
c = Carro("Toyota")
c.mover() # Carro Toyota andando na estrada
c.descrever() # Veículo da marca Toyota ← herdado, sem reescrever fechamento dos pilares
Encapsulamento esconde dados. Abstração esconde complexidade e define contratos.
interface = várias abstrações juntas
Em outras linguagens (Java, C#) chamam isso de interface: um contrato com vários métodos obrigatórios. Em Python, é só uma classe abstrata com vários @abstractmethod.
Reprodutor é obrigado a implementar os dois métodos — não basta um.interface como palavra-chave — usamos ABC com vários @abstractmethod.__init__, sem métodos concretos — é o que outras linguagens chamam de interface pura. Quando tem estado (como a Veiculo do slide anterior), é uma classe abstrata "completa".from abc import ABC, abstractmethod
class Reprodutor(ABC):
@abstractmethod
def play(self):
pass
@abstractmethod
def pause(self):
pass
class Spotify(Reprodutor):
def play(self):
print("Tocando música no Spotify")
def pause(self):
print("Pausando música")
class YouTube(Reprodutor):
def play(self):
print("Tocando vídeo no YouTube")
def pause(self):
print("Pausando vídeo")
s = Spotify()
s.play() # Tocando música no Spotify
s.pause() # Pausando música por que isso importa
Bugs de implementação esquecida não chegam em produção.
O contrato está no código, não em comentários ou documentação solta.
Quem implementa uma subclasse sabe exatamente o que precisa entregar.
Você pode escrever testes contra o contrato, não contra cada classe.
Reaproveita métodos concretos e força implementação só do que varia.
Novas subclasses encaixam sem alterar quem já usa a abstrata.
uso com critério
Comece concreto. Quando aparecer a segunda classe parecida, aí promove para abstrata.
glossário rápido
| item | detalhe |
|---|---|
| ABC | Abstract Base Class. Classe-base de toda classe abstrata em Python. Importada de abc. |
| @abstractmethod | Decorador que marca um método como obrigatório nas subclasses. |
| Classe abstrata | Classe que herda de ABC e tem ao menos um método abstrato. Não pode ser instanciada. |
| Método concreto | Método com corpo de verdade. Pode coexistir com métodos abstratos na mesma classe. |
| Interface | Termo de outras linguagens. Em Python: uma classe abstrata só com @abstractmethod, sem estado. |
| Contrato | Conjunto de métodos que toda subclasse promete implementar. ABC torna o contrato obrigatório. |
laboratório em sala · duplas
Em duplas. 25 minutos. Objetivo: usar ABC para formalizar o contrato do polimorfismo de pagamentos da aula 09.
Classe Pagamento(ABC) com dois métodos abstratos: processar(valor) e gerar_recibo(valor).
PagamentoCartao, PagamentoPix e PagamentoBoleto — cada uma implementa os dois métodos.
Função processar_pagamentos(lista, valor) que percorre uma lista heterogênea de pagamentos.
Crie uma classe PagamentoFalho(Pagamento) que esquece gerar_recibo e veja o TypeError em ação.
resolução · passo 1
ABC e abstractmethod do módulo abc. processar (faz o débito) e gerar_recibo (devolve a string do comprovante). A abstrata pode ter um __init__ normal — só os métodos marcados são obrigatórios.
resolução · passo 1
Cinco linhas concentram todo o contrato do sistema.
__init__ é concreto — toda subclasse já recebe cliente e data de graça.@abstractmethod formam o contrato: nenhuma subclasse passa sem implementar ambos.from abc import ABC, abstractmethod
from datetime import datetime
class Pagamento(ABC):
def __init__(self, cliente):
self.cliente = cliente
self.data = datetime.now()
@abstractmethod
def processar(self, valor):
pass
@abstractmethod
def gerar_recibo(self, valor):
pass resolução · passo 2
Cada forma de pagamento implementa o contrato do seu jeito — e o construtor da mãe é reaproveitado com super().
PagamentoCartao precisa de um atributo extra (numero), então declara seu __init__ e chama super().__init__(cliente). Pix e Boleto não precisam de nada além — herdam o __init__ da mãe sem declarar.class PagamentoCartao(Pagamento):
def __init__(self, cliente, numero_cartao):
super().__init__(cliente)
self.numero = numero_cartao
def processar(self, valor):
print(f"Cobrando R$ {valor:.2f} no cartão ****{self.numero[-4:]}")
return True
def gerar_recibo(self, valor):
return f"[CARTÃO] {self.cliente} — R$ {valor:.2f}"
class PagamentoPix(Pagamento):
# __init__ herdado de Pagamento (recebe cliente, define data)
def processar(self, valor):
print(f"Transferência Pix de R$ {valor:.2f} de {self.cliente}")
return True
def gerar_recibo(self, valor):
return f"[PIX] {self.cliente} — R$ {valor:.2f}"
class PagamentoBoleto(Pagamento):
# __init__ herdado de Pagamento (recebe cliente, define data)
def processar(self, valor):
print(f"Boleto gerado no valor de R$ {valor:.2f}")
return False # aguardando confirmação do banco
def gerar_recibo(self, valor):
return f"[BOLETO] {self.cliente} — R$ {valor:.2f} (pendente)" resolução · passos 3 e 4
Uma só função processa qualquer pagamento. E uma subclasse incompleta nem chega a nascer.
processar_pagamentos não sabe (nem precisa saber) o tipo concreto — só confia no contrato.PagamentoFalho é recusada na hora — o erro vem antes do objeto existir.def processar_pagamentos(lista, valor):
for p in lista:
ok = p.processar(valor)
status = "OK" if ok else "PENDENTE"
print(f" -> {status} | {p.gerar_recibo(valor)}")
pagamentos = [
PagamentoCartao("Ana", "1234567890124444"),
PagamentoPix("Bruno"),
PagamentoBoleto("Carla"),
]
processar_pagamentos(pagamentos, valor=150.00)
# passo 4: subclasse incompleta
class PagamentoFalho(Pagamento):
def processar(self, valor):
return True
# esqueceu gerar_recibo()...
PagamentoFalho("Diego") # 💥 TypeError nesta linha resolução · saída
Os três primeiros pagamentos rodam limpos. O quarto trava com a mensagem certa, na hora certa.
TypeError diz exatamente qual classe e qual método falhou — debug em segundos.$ python main.py
Cobrando R$ 150.00 no cartão ****4444
-> OK | [CARTÃO] Ana — R$ 150.00
Transferência Pix de R$ 150.00 de Bruno
-> OK | [PIX] Bruno — R$ 150.00
Boleto gerado no valor de R$ 150.00
-> PENDENTE | [BOLETO] Carla — R$ 150.00 (pendente)
Traceback (most recent call last):
File "main.py", line 24, in <module>
PagamentoFalho("Diego")
TypeError: Can't instantiate abstract class PagamentoFalho without an implementation for abstract method 'gerar_recibo' encerramento
ABC + @abstractmethod transformam métodos em obrigações. exercícios · visão geral
Seis exercícios em três níveis. Faça pelo menos um de cada nível antes da próxima aula.
Aplicar ABC em hierarquias já conhecidas (Forma, Animal).
Criar abstrações para sistemas reais (notificações, salário).
Sistema completo com ABC + interfaces compostas (herança múltipla).
Suba na pasta aula-10/ do seu repositório do curso.
exercícios · nível fácil
Refatore Forma da aula 09 usando ABC. Garanta que Circulo, Quadrado e Triangulo implementem area() e perimetro(). Dica: use triângulo equilátero (um único lado) pra simplificar.
Classe abstrata Animal com método som(). Crie Cachorro, Gato e Vaca. Tente instanciar Animal direto e confirme o TypeError.
exercícios · nível médio
Abstrata Notificacao com enviar(msg) e formatar(msg). Subclasses: Email, SMS e Push. Cada uma formata a mensagem do seu jeito.
Abstrata Funcionario com calcular_salario() abstrato e imprimir_holerite() concreto. Subclasses: CLT, PJ e Estagiario. Use cálculos simples (ex: CLT = base, PJ = base × 1.2, Estagiário = base × 0.5) — o foco é a estrutura ABC, não tributação.
exercícios · nível difícil
Abstrata Personagem com nome e vida no __init__, métodos abstratos atacar() e habilidade_especial(), e método concreto descansar() que aumenta vida em 10. Subclasses: Guerreiro, Mago e Arqueiro. Crie a função simular_batalha(grupo) que percorre uma lista de personagens chamando atacar() em cada.
Crie Imprimivel (com método imprimir()) e Logavel (com método log()) como abstratas separadas. Crie Pedido(Imprimivel, Logavel) que herda das duas — discuta os trade-offs em sala.
resumo final
@abstractmethod é a etiqueta. Vimos hoje o primeiro erro bom: o TypeError que evita que classes incompletas existam. Na próxima aula, aprendemos a criar, lançar e capturar os nossos próprios erros.