Este é o cafeteria.py inteiro, exatamente como foi rodado pra gerar a saída do slide do terminal. Copie o código e cole no VS Code ou no Google Colab para rodar.
Sugestão de estudo: rode primeiro como está; depois remova o super().__init__(nome, preco) de Bebida e veja o AttributeError aparecer. Depois remova o @abstractmethod de Item.descricao e veja que Item("x", 5) deixa de estourar — entendeu na pele a função do contrato.
"""Cardápio de uma cafeteria — exercita POO de ponta a ponta.
Cobre as aulas 03 a 11: __str__, encapsulamento, composição, herança,
super(), sobrescrita, polimorfismo, classes abstratas e sobrecarga de
operador (__len__).
"""
from abc import ABC, abstractmethod
# ----------------------------------------------------------------------
# Item — classe abstrata (aula 10)
# ----------------------------------------------------------------------
class Item(ABC):
"""Contrato comum a tudo que pode entrar no cardápio."""
def __init__(self, nome, preco):
if preco < 0:
raise ValueError("preco nao pode ser negativo")
self._nome = nome
self._preco = preco
@property
def nome(self):
# somente leitura — encapsulamento (aula 04)
return self._nome
@property
def preco(self):
return self._preco
@abstractmethod
def descricao(self):
"""Cada subclasse descreve o item do seu jeito."""
pass
def __str__(self):
# representacao amigavel (aula 03)
return f"{self._nome} — R$ {self._preco:.2f}"
# ----------------------------------------------------------------------
# Bebida — herda de Item (aula 07) e sobrescreve descricao (aula 08)
# ----------------------------------------------------------------------
class Bebida(Item):
def __init__(self, nome, preco, tamanho):
super().__init__(nome, preco) # delega ao pai
self._tamanho = tamanho # "P", "M" ou "G"
def descricao(self):
return (
f"Bebida {self._nome} ({self._tamanho}) — R$ {self._preco:.2f}"
)
# ----------------------------------------------------------------------
# Comida — outra filha de Item
# ----------------------------------------------------------------------
class Comida(Item):
def __init__(self, nome, preco, vegetariana=False):
super().__init__(nome, preco)
self._vegetariana = vegetariana
def descricao(self):
marca = "vegetariana" if self._vegetariana else "tradicional"
return f"Comida {self._nome} ({marca}) — R$ {self._preco:.2f}"
# ----------------------------------------------------------------------
# Cardapio — composicao (aula 05) + __len__ (aula 11)
# ----------------------------------------------------------------------
class Cardapio:
"""Guarda itens e calcula o total. Compoe Itens por dentro."""
def __init__(self):
self._itens = []
def adicionar(self, item):
self._itens.append(item)
def total(self):
# generator expression somando precos
return sum(item.preco for item in self._itens)
def __len__(self):
# protocolo Sized — agora len(cardapio) funciona
return len(self._itens)
# ----------------------------------------------------------------------
# Programa principal
# ----------------------------------------------------------------------
# tentar instanciar a classe abstrata estoura TypeError (aula 10)
try:
Item("Generico", 5.0)
except TypeError as erro:
print(f"[abstrata] {erro}")
# monta o cardapio
cardapio = Cardapio()
cardapio.adicionar(Bebida("Cafe", 6.50, "M"))
cardapio.adicionar(Bebida("Suco de laranja", 9.00, "G"))
cardapio.adicionar(Comida("Pao de queijo", 4.00, vegetariana=True))
cardapio.adicionar(Comida("Coxinha", 7.50, vegetariana=False))
# polimorfismo (aula 09): mesmo metodo, comportamento diferente
print("Itens do cardapio:")
for item in cardapio._itens:
print(f" - {item.descricao()}")
print(f"Total de itens: {len(cardapio)}")
print(f"Total a pagar: R$ {cardapio.total():.2f}") __str__ em Item formata o objeto pra print e f-string._nome, _preco protegidos; @property nome e @property preco sem setter (somente leitura).Cardapio cria e possui sua lista _itens internamente. Validação no __init__ de Item (preço < 0).Bebida(Item) e Comida(Item); super().__init__(nome, preco) na primeira linha das filhas.descricao() tem uma versão em cada filha; cada uma é uma substituição total do método abstrato.for item in cardapio._itens: item.descricao() chama versões diferentes sem nenhum if isinstance.ABC + @abstractmethod obrigam descricao() nas filhas; Item("x", 5) estoura TypeError.__len__ em Cardapio faz len(cardapio) funcionar.Este é o fechar_conta.py inteiro. Copie o código e cole no VS Code ou no Google Colab para rodar. Ele roda sozinho, sem depender de outros arquivos.
Sugestão de estudo: rode como está; depois troque o raise SaldoInsuficienteError(...) por return None e veja como o programa silencia o erro (volta o problema da aula 11). Depois remova o finally e perceba que o saldo final só aparece no caminho feliz.
"""Fechar a conta usando uma Carteira — exercita a aula 12 inteira.
Programa autossuficiente: excecao customizada com dados, validacao com
raise e o quarteto try/except/else/finally. Trabalha com valores em reais
(numericos), sem depender de nenhum outro arquivo.
"""
# ----------------------------------------------------------------------
# Excecao customizada (aula 12)
# ----------------------------------------------------------------------
class SaldoInsuficienteError(Exception):
"""Levantada quando a carteira nao cobre o total da conta."""
def __init__(self, saldo, total):
self.saldo = saldo
self.total = total
self.falta = total - saldo
super().__init__(
f"Faltam R$ {self.falta:.2f} para pagar R$ {total:.2f}"
)
# ----------------------------------------------------------------------
# Carteira — encapsulamento + validacao via raise
# ----------------------------------------------------------------------
class Carteira:
def __init__(self, saldo_inicial=0.0):
if saldo_inicial < 0:
raise ValueError("saldo inicial nao pode ser negativo")
self._saldo = saldo_inicial
@property
def saldo(self):
return self._saldo
def depositar(self, valor):
if valor <= 0:
raise ValueError("valor deve ser positivo")
self._saldo += valor
return self._saldo
def pagar(self, total):
# detecta na origem, deixa quem chamou tratar
if total > self._saldo:
raise SaldoInsuficienteError(self._saldo, total)
self._saldo -= total
return self._saldo
# ----------------------------------------------------------------------
# Programa principal
# ----------------------------------------------------------------------
carteira = Carteira(20.00)
total_caro = 27.00 # conta que estoura o saldo
print(f"Saldo inicial: R$ {carteira.saldo:.2f}")
print(f"Total da conta cara: R$ {total_caro:.2f}")
try:
carteira.pagar(total_caro)
except SaldoInsuficienteError as erro:
print(f"❌ Pagamento recusado: {erro}")
print(f" Deposite mais R$ {erro.falta:.2f} para fechar a conta.")
except ValueError as erro:
print(f"❌ Valor invalido: {erro}")
else:
print("Pagamento concluido (caminho feliz).")
finally:
print(f"Saldo apos tentativa: R$ {carteira.saldo:.2f}")
try:
carteira.depositar(-10)
except ValueError as erro:
print(f"❌ Deposito recusado: {erro}")
total_barato = 10.50
try:
carteira.pagar(total_barato)
except SaldoInsuficienteError as erro:
print(f"❌ {erro}")
else:
print(f"✅ Pagamento OK. Sobrou R$ {carteira.saldo:.2f}.")
finally:
print(f"Saldo final: R$ {carteira.saldo:.2f}") _saldo + @property saldo; validação acontece dentro dos métodos públicos (depositar e pagar).SaldoInsuficienteError(Exception) mostra que herança vale também pra exceções.__init__ guarda saldo, total, falta; super().__init__(mensagem) entrega texto pro print(erro).pagar e depositar disparam exceções em vez de devolver None — quem chamou é obrigado a tratar.except específico (SaldoInsuficienteError) antes do genérico (ValueError).raise acontece antes de mexer em _saldo, então o saldo continua íntegro mesmo após exceção.Material de estudo em casa
Cafeteria + Carteira que fecha a conta
como aproveitar
São dois programas pequenos, completos e propositalmente didáticos. A ideia é você rodar e mexer, não só ler.
Cada bloco de código tem 2 observações curtas que apontam o conceito e a aula correspondente. Foque no que está sublinhado.
Os dois códigos completos estão nos guias de estudo (botão 📚). Copie cada um e cole no VS Code ou no Google Colab para rodar. Cada um roda sozinho.
Cada programa tem um slide com a saída exata do terminal. Se a sua saída diverge, é porque mexeu em algo — ótimo, está aprendendo.
Mude um preço, remova o super(), troque o raise por um print: veja o que quebra. Quebrar de propósito é a melhor forma de aprender.
código 1 · cenário
Primeiro programa: um cardápio onde cada item (bebida ou comida) tem nome, preço e sabe se descrever. A classe Cardapio agrupa os itens e calcula o total. Cobre nove aulas — tudo menos exceções.
__str__ para representação amigável, @property para encapsular preço e nome, e Cardapio que compõe uma lista de itens. Item é abstrata (ABC + @abstractmethod); Bebida e Comida herdam, sobrescrevem descricao() e respondem polimorficamente. Cardapio implementa __len__ — agora len(cardapio) funciona como em qualquer coleção nativa do Python. Clique no botão 📚 deste slide para copiar o cafeteria.py inteiro e rodar.
código 1 · mapa das classes
Antes de ler o código, fixe a estrutura na cabeça. Quatro classes, duas relações.
Item (ABC) — molde abstrato. Tem _nome, _preco, propriedades de leitura, __str__ concreto e descricao() abstrato. Bebida (Item) — herda tudo de Item e adiciona tamanho (P/M/G). Implementa descricao() do seu jeito. Comida (Item) — herda de Item e adiciona vegetariana. Implementa descricao() do seu jeito. Cardapio — compõe uma lista de Itens. Tem adicionar(), total() e __len__ para virar uma coleção pythônica. Bebida e Comida são Item (herança — aula 07). Cardapio tem uma lista de Item (composição — aula 05).
código 1 · parte 1
Começa pelo contrato: todo item do cardápio precisa ter nome, preço, propriedades de leitura e um método descricao() que cada subclasse implementa do seu jeito.
ABC + @abstractmethod tornam Item uma classe que não pode ser instanciada direto. Subclasses são obrigadas a implementar descricao()._nome e _preco são protegidos; @property dá leitura sem permitir escrita. Validação acontece no __init__.from abc import ABC, abstractmethod
class Item(ABC):
"""Contrato comum a tudo que pode entrar no cardápio."""
def __init__(self, nome, preco):
if preco < 0:
raise ValueError("preco nao pode ser negativo")
self._nome = nome
self._preco = preco
@property
def nome(self):
return self._nome # somente leitura
@property
def preco(self):
return self._preco
@abstractmethod
def descricao(self):
"""Cada subclasse descreve o item do seu jeito."""
pass
def __str__(self):
return f"{self._nome} — R$ {self._preco:.2f}" código 1 · parte 2
Duas filhas de Item. Cada uma adiciona um atributo próprio e dá a sua versão de descricao(). O super().__init__ garante que nome e preço são inicializados pelo pai.
Bebida e Comida herdam de Item e chamam super().__init__ na primeira linha do construtor — a Regra de Ouro do super().descricao(). Essa diferença é o que torna o polimorfismo possível mais à frente.class Bebida(Item):
def __init__(self, nome, preco, tamanho):
super().__init__(nome, preco) # delega ao pai
self._tamanho = tamanho # "P", "M" ou "G"
def descricao(self):
return (
f"Bebida {self._nome} ({self._tamanho}) — R$ {self._preco:.2f}"
)
class Comida(Item):
def __init__(self, nome, preco, vegetariana=False):
super().__init__(nome, preco)
self._vegetariana = vegetariana
def descricao(self):
marca = "vegetariana" if self._vegetariana else "tradicional"
return f"Comida {self._nome} ({marca}) — R$ {self._preco:.2f}" código 1 · parte 3
O Cardapio cria e guarda sua própria lista de itens (composição). Implementar __len__ faz len(cardapio) funcionar — sem herdar nada, só plugando no protocolo nativo.
self._itens = [] nasce dentro do cardápio. Se o cardápio some, a lista some junto — ciclo de vida compartilhado.__len__ é um dunder do protocolo Sized. Implementou? Ganhou len(cardapio) de graça, sem herdar de ninguém.class Cardapio:
"""Guarda itens e calcula o total. Compoe Itens por dentro."""
def __init__(self):
self._itens = []
def adicionar(self, item):
self._itens.append(item)
def total(self):
# generator expression somando precos
return sum(item.preco for item in self._itens)
def __len__(self):
# protocolo Sized — agora len(cardapio) funciona
return len(self._itens) código 1 · parte 4
Aqui o programa roda. Montamos o cardápio, percorremos os itens chamando o mesmo descricao() em cada um — e cada classe responde do seu jeito. Também demonstramos que Item abstrata barra instanciação.
item.descricao() dispara comportamentos diferentes em Bebida e Comida. Late binding na prática.Item("...", 5) levanta TypeError. Pegamos com try/except só para mostrar a mensagem sem matar o programa.# Programa principal
# tentar instanciar a classe abstrata estoura TypeError (aula 10)
try:
Item("Generico", 5.0)
except TypeError as erro:
print(f"[abstrata] {erro}")
cardapio = Cardapio()
cardapio.adicionar(Bebida("Cafe", 6.50, "M"))
cardapio.adicionar(Bebida("Suco de laranja", 9.00, "G"))
cardapio.adicionar(Comida("Pao de queijo", 4.00, vegetariana=True))
cardapio.adicionar(Comida("Coxinha", 7.50, vegetariana=False))
print("Itens do cardapio:")
for item in cardapio._itens:
print(f" - {item.descricao()}")
print(f"Total de itens: {len(cardapio)}")
print(f"Total a pagar: R$ {cardapio.total():.2f}") código 1 · saída
Rode o programa e veja exatamente isso no terminal. A primeira linha vem do try/except ao redor da abstrata; o resto é o cardápio sendo descrito polimorficamente.
TypeError que o Python gera (em inglês — tracebacks não são traduzidos). A redação exata varia com a versão: no Python 3.12+ ela diz without an implementation for abstract method. O importante é reconhecer que é um TypeError de instanciação de classe abstrata.[abstrata] Can't instantiate abstract class Item without an implementation for abstract method 'descricao'
Itens do cardapio:
- Bebida Cafe (M) — R$ 6.50
- Bebida Suco de laranja (G) — R$ 9.00
- Comida Pao de queijo (vegetariana) — R$ 4.00
- Comida Coxinha (tradicional) — R$ 7.50
Total de itens: 4
Total a pagar: R$ 27.00 código 2 · cenário
Segundo programa, independente do primeiro: uma Carteira que recusa pagar quando o saldo não cobre o valor da conta. Cobre a aula 12 inteira — raise, try/except/else/finally e exceções customizadas.
fechar_conta.py traz as próprias classes e trabalha com valores em reais. Não depende de nenhum outro arquivo. SaldoInsuficienteError carrega saldo, total e falta, e formata uma mensagem precisa. Quem captura sabe exatamente o que ofereceu. try envolve o pagamento, except mostra o erro, else celebra o caminho feliz e finally garante que o saldo final aparece — com erro ou sem erro. Clique no botão 📚 deste slide para copiar o fechar_conta.py inteiro e rodar.
código 2 · parte 1
Herdar de Exception já bastaria, mas a exceção fica muito mais útil quando carrega os dados do erro. saldo, total e falta ficam disponíveis pra quem captura.
Exception e adiciona campos do domínio. super().__init__(mensagem) entrega o texto ao print(erro).saldo, total e falta ficam guardados na exceção; quem captura usa erro.falta direto.class SaldoInsuficienteError(Exception):
"""Levantada quando a carteira nao cobre o total da conta."""
def __init__(self, saldo, total):
self.saldo = saldo
self.total = total
self.falta = total - saldo
super().__init__(
f"Faltam R$ {self.falta:.2f} para pagar R$ {total:.2f}"
) código 2 · parte 2
A Carteira protege o saldo (_saldo + @property) e usa raise para barrar operações inválidas na origem. Quem chamou decide o que fazer com o erro.
_saldo é interno; @property saldo dá leitura sem permitir escrita direta. Validação acontece nos métodos públicos.pagar detecta o problema e dispara SaldoInsuficienteError — não devolve None, não imprime aviso. Quem chamou é obrigado a tratar.class Carteira:
def __init__(self, saldo_inicial=0.0):
if saldo_inicial < 0:
raise ValueError("saldo inicial nao pode ser negativo")
self._saldo = saldo_inicial
@property
def saldo(self):
return self._saldo
def depositar(self, valor):
if valor <= 0:
raise ValueError("valor deve ser positivo")
self._saldo += valor
return self._saldo
def pagar(self, total):
if total > self._saldo:
raise SaldoInsuficienteError(self._saldo, total)
self._saldo -= total
return self._saldo código 2 · parte 3
Aqui aparece o quarteto completo. Uma tentativa que falha (conta cara), uma que tem deposito inválido e uma que dá certo (conta barata). O finally garante saldo final sempre.
try envolve só a linha arriscada; except mais específico primeiro; else só roda se nada estourar; finally roda sempre.# Programa principal
carteira = Carteira(20.00)
total_caro = 27.00 # conta que estoura o saldo
print(f"Saldo inicial: R$ {carteira.saldo:.2f}")
print(f"Total da conta cara: R$ {total_caro:.2f}")
try:
carteira.pagar(total_caro)
except SaldoInsuficienteError as erro:
print(f"❌ Pagamento recusado: {erro}")
print(f" Deposite mais R$ {erro.falta:.2f} para fechar a conta.")
except ValueError as erro:
print(f"❌ Valor invalido: {erro}")
else:
print("Pagamento concluido (caminho feliz).")
finally:
print(f"Saldo apos tentativa: R$ {carteira.saldo:.2f}")
try:
carteira.depositar(-10)
except ValueError as erro:
print(f"❌ Deposito recusado: {erro}")
total_barato = 10.50
try:
carteira.pagar(total_barato)
except SaldoInsuficienteError as erro:
print(f"❌ {erro}")
else:
print(f"✅ Pagamento OK. Sobrou R$ {carteira.saldo:.2f}.")
finally:
print(f"Saldo final: R$ {carteira.saldo:.2f}") código 2 · saída
Saída do programa rodado em sequência: pagamento caro recusado (saldo intacto), depósito inválido recusado, pagamento barato aprovado. finally imprime o saldo final.
pagar() levantou a exceção antes de mexer em _saldo. Estado consistente.try. Por isso o saldo aparece após cada tentativa, mesmo quando houve exceção.Saldo inicial: R$ 20.00
Total da conta cara: R$ 27.00
❌ Pagamento recusado: Faltam R$ 7.00 para pagar R$ 27.00
Deposite mais R$ 7.00 para fechar a conta.
Saldo apos tentativa: R$ 20.00
❌ Deposito recusado: valor deve ser positivo
✅ Pagamento OK. Sobrou R$ 9.50.
Saldo final: R$ 9.50 autoavaliação
ABC + @abstractmethod e explicar por que Item() direto estoura TypeError. (aula 10) _atributo + @property e dizer por que preco aqui é somente leitura. (aula 04) super().__init__ e sobrescreve um método — sem esquecer a Regra de Ouro do super(). (aulas 07 e 08) for item in cardapio._itens e explicar por que item.descricao() chama versões diferentes. (aula 09) Bebida é Item; Cardapio tem uma lista de Item. (aulas 05 e 07) __len__ e explicar que isso é sobrecarga de operador / protocolo, não herança. (aula 11) Exception e carrega dados — e dizer por que super().__init__(mensagem) está lá. (aula 12) raise na origem em vez de devolver None ou imprimir aviso — e justificar a diferença. (aula 12) try/except/else/finally e explicar o papel de cada um, na ordem correta dos except. (aula 12)