Este é o pizzaria.py inteiro, exatamente como foi rodado pra gerar a saída do slide. Copie e cole no VS Code para rodar. Ele cria um pedidos.txt ao lado do próprio arquivo.
Sugestão de estudo: rode como está; depois troque o modo "w" por "a" no salvar e rode duas vezes — veja as linhas duplicando. Depois tire o encoding="utf-8" e observe o que acontece com os acentos.
"""Cadastro de pizzaria — POO + coleções + arquivos (aulas 02–05, 17, 18)."""
from pathlib import Path
ARQUIVO = Path(__file__).parent / "pedidos.txt" # ancorado ao lado do script (aula 17)
class Pedido:
"""Um pedido: cliente, sabor e valor. Encapsula o valor (aulas 02–04)."""
def __init__(self, cliente, sabor, valor):
self.cliente = cliente # atributos simples (aula 02)
self.sabor = sabor
self.valor = valor # passa pelo setter -> valida
@property
def valor(self): # getter (aula 04)
return self._valor
@valor.setter
def valor(self, novo): # setter valida e guarda (aula 04)
if novo <= 0:
raise ValueError("valor deve ser positivo")
self._valor = novo
def __str__(self): # representação amigável (aula 03)
return f"{self.cliente} — {self.sabor} (R$ {self._valor:.2f})"
class Cadastro:
"""Compõe uma lista de Pedidos e persiste em arquivo (aulas 05, 17, 18)."""
def __init__(self):
self._pedidos = [] # composição (aula 05)
def adicionar(self, pedido):
self._pedidos.append(pedido)
def salvar(self, caminho=ARQUIVO):
# reescreve o arquivo inteiro em "w" (aula 17)
with open(caminho, "w", encoding="utf-8") as f:
for p in self._pedidos:
f.write(f"{p.cliente};{p.sabor};{p.valor}\n")
@classmethod
def carregar_todos(cls, caminho=ARQUIVO):
# factory que reconstrói os objetos a partir do texto (aula 17)
cadastro = cls()
try:
with open(caminho, "r", encoding="utf-8") as f:
for linha in f:
cliente, sabor, valor = linha.strip().split(";")
cadastro.adicionar(Pedido(cliente, sabor, float(valor)))
except FileNotFoundError:
pass # 1ª execução: arquivo ainda não existe
return cadastro
def buscar_por_cliente(self, alvo):
# list comprehension filtrando (aula 18)
return [p for p in self._pedidos if p.cliente == alvo]
def por_valor(self):
# ordena com sorted(key=lambda) — devolve lista nova (aula 18)
return sorted(self._pedidos, key=lambda p: p.valor)
# ── Demonstração ──────────────────────────────────────────────────────
cadastro = Cadastro()
cadastro.adicionar(Pedido("Ana", "Calabresa", 39.90))
cadastro.adicionar(Pedido("Bruno", "Marguerita", 42.00))
cadastro.adicionar(Pedido("Ana", "Portuguesa", 45.50))
# valor inválido é barrado na origem (aula 04/14)
try:
Pedido("Carla", "Frango", -5)
except ValueError as erro:
print(f"[recusado] {erro}")
cadastro.salvar() # grava em pedidos.txt
# recarrega do zero, provando a persistência (aula 17)
recarregado = Cadastro.carregar_todos()
print("Pedidos da Ana:")
for p in recarregado.buscar_por_cliente("Ana"):
print(f" - {p}")
print("Todos por valor (crescente):")
for p in recarregado.por_valor():
print(f" - {p}")
__init__ e __str__ (representação amigável).@property valor + @valor.setter validando (raise ValueError).Cadastro cria e possui a lista _pedidos.with open(..., encoding="utf-8"), modos "w"/"r", FileNotFoundError, caminho com Path(__file__).parent, e o @classmethod carregar_todos como factory.sorted(key=lambda) na ordenação.Dois arquivos que trabalham a mesma ideia: o programa 2 é o repositório SQLite puro (roda no terminal); o programa 4 é a janela Tkinter que consome um repositório igual. Rode o programa 4 com o Python do python.org (Tk 8.6, igual ao Windows).
"""Repositório SQLite — CRUD de produtos (aulas 24, 04, 14)."""
import sqlite3
from pathlib import Path
BANCO = Path(__file__).parent / "loja.db" # ancorado ao lado do script (aula 24)
class ProdutoInvalidoError(Exception):
"""Erro de domínio: o produto fere uma regra da tabela (aula 14)."""
def __init__(self, motivo):
self.motivo = motivo # guarda o dado do erro
super().__init__(f"Produto inválido: {motivo}")
class Produto:
"""Objeto de domínio; sabe virar/voltar de uma linha (ORM manual, aula 24)."""
def __init__(self, nome, preco, id=None):
self.id = id
self.nome = nome
self.preco = preco
@classmethod
def from_row(cls, linha): # tupla do banco -> objeto (aula 24)
id, nome, preco = linha
return cls(nome, preco, id)
def to_params(self): # objeto -> tupla p/ os ? (SEM o id)
return (self.nome, self.preco)
def __str__(self):
return f"#{self.id} {self.nome} — R$ {self.preco:.2f}"
class RepositorioProduto:
"""Esconde todo o SQL atrás de métodos do domínio (Repositório, aulas 04/24)."""
def __init__(self, caminho=BANCO):
self.caminho = caminho
self.criar_tabela()
def criar_tabela(self):
with sqlite3.connect(self.caminho) as con:
con.execute(
"CREATE TABLE IF NOT EXISTS produtos ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"nome TEXT NOT NULL, "
"preco REAL NOT NULL)"
)
def salvar(self, produto):
# INSERT com placeholder ? (nunca f-string!) — aula 24
try:
with sqlite3.connect(self.caminho) as con:
cursor = con.execute(
"INSERT INTO produtos (nome, preco) VALUES (?, ?)",
produto.to_params(),
)
produto.id = cursor.lastrowid # id gerado pelo banco
return produto
except sqlite3.IntegrityError: # feriu uma regra da tabela (ex.: NOT NULL) — aula 14
raise ProdutoInvalidoError("nome e preço são obrigatórios")
except sqlite3.Error as erro: # rede de segurança geral
print(f"[erro no banco] {erro}")
return None
def listar(self):
with sqlite3.connect(self.caminho) as con:
cursor = con.execute("SELECT id, nome, preco FROM produtos ORDER BY id")
return [Produto.from_row(l) for l in cursor.fetchall()]
def buscar_por_nome(self, termo):
with sqlite3.connect(self.caminho) as con:
cursor = con.execute(
"SELECT id, nome, preco FROM produtos WHERE nome LIKE ?",
(f"%{termo}%",), # busca parcial com LIKE + % (aula 24)
)
return [Produto.from_row(l) for l in cursor.fetchall()]
def atualizar(self, produto):
with sqlite3.connect(self.caminho) as con:
con.execute(
"UPDATE produtos SET nome = ?, preco = ? WHERE id = ?",
(produto.nome, produto.preco, produto.id),
)
def remover(self, id):
with sqlite3.connect(self.caminho) as con:
con.execute("DELETE FROM produtos WHERE id = ?", (id,))
# ── Demonstração (rode num loja.db novo pra bater a saída) ─────────────
repo = RepositorioProduto()
repo.salvar(Produto("Cafe", 18.0))
repo.salvar(Produto("Leite", 6.5))
repo.salvar(Produto("Pao", 8.0))
# produto sem nome: fere o NOT NULL; o Repositório traduz o IntegrityError (aula 14)
try:
repo.salvar(Produto(None, 20.0))
except ProdutoInvalidoError as erro:
print(f"[recusado] {erro}") # print(erro) usa a mensagem do super().__init__
print("Todos os produtos:")
for p in repo.listar():
print(f" {p}")
cafe = repo.buscar_por_nome("Cafe")[0] # atualiza o preço do café
cafe.preco = 21.0
repo.atualizar(cafe)
leite = repo.buscar_por_nome("Leite")[0] # remove o leite
repo.remover(leite.id)
print("Depois de atualizar Cafe e remover Leite:")
for p in repo.listar():
print(f" {p}")
print("Busca por 'a':")
for p in repo.buscar_por_nome("a"):
print(f" {p}")
# Saída no terminal:
# [recusado] Produto inválido: nome e preço são obrigatórios
# Todos os produtos:
# #1 Cafe — R$ 18.00
# #2 Leite — R$ 6.50
# #3 Pao — R$ 8.00
# Depois de atualizar Cafe e remover Leite:
# #1 Cafe — R$ 21.00
# #3 Pao — R$ 8.00
# Busca por 'a':
# #1 Cafe — R$ 21.00
# #3 Pao — R$ 8.00 Sugestão de estudo: troque o ? do INSERT por uma f-string e tente salvar um nome com aspa simples — veja o SQL quebrar. Depois tente salvar um Produto com preco=None e veja o NOT NULL barrar (o IntegrityError vira ProdutoInvalidoError).
"""App Tkinter que consome o repositório SQLite (aulas 22, 23, 24)."""
import sqlite3
import tkinter as tk
from tkinter import messagebox
from pathlib import Path
BANCO = Path(__file__).parent / "loja_gui.db" # ancorado ao lado do script (aula 24)
class RepositorioProduto:
"""CRUD mínimo em SQLite (aula 24). A GUI usa isto sem ver SQL."""
def __init__(self, caminho=BANCO):
self.caminho = caminho
with sqlite3.connect(self.caminho) as con:
con.execute(
"CREATE TABLE IF NOT EXISTS produtos ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, nome TEXT NOT NULL)"
)
def salvar(self, nome):
with sqlite3.connect(self.caminho) as con:
con.execute("INSERT INTO produtos (nome) VALUES (?)", (nome,))
def listar(self):
with sqlite3.connect(self.caminho) as con:
cursor = con.execute("SELECT id, nome FROM produtos ORDER BY id")
return cursor.fetchall() # lista de tuplas (id, nome)
def remover(self, id):
with sqlite3.connect(self.caminho) as con:
con.execute("DELETE FROM produtos WHERE id = ?", (id,))
class App(tk.Tk):
"""A janela É um objeto: herda de tk.Tk (aulas 07/23)."""
def __init__(self, repo):
super().__init__() # PRIMEIRA linha do __init__ (aula 23)
self.repo = repo
self.title("Estoque")
self.geometry("320x300")
# só .pack neste container — um gerente de geometria por container (aula 22)
self.campo = tk.Entry(self, width=30)
self.campo.pack(padx=10, pady=(10, 4))
tk.Button(self, text="Adicionar", command=self.adicionar).pack(pady=2)
self.lista = tk.Listbox(self, width=40, height=8)
self.lista.pack(padx=10, pady=4)
tk.Button(self, text="Excluir", command=self.excluir).pack(pady=2)
self._ids = [] # espelha os ids na ordem do Listbox (aula 23)
self.preencher_lista()
def preencher_lista(self):
# o banco é a fonte da verdade; o Listbox é só o espelho (aula 23)
self.lista.delete(0, tk.END) # limpa ANTES de reinserir
self._ids = []
for id, nome in self.repo.listar():
self._ids.append(id)
self.lista.insert(tk.END, nome)
def adicionar(self):
nome = self.campo.get().strip() # Entry.get() sempre string (aula 22)
if not nome:
messagebox.showerror("Ops", "Digite um nome.")
return
self.repo.salvar(nome) # grava no banco (aula 24)
self.campo.delete(0, tk.END)
self.preencher_lista()
print(f"[+] adicionado: {nome}")
def excluir(self):
selecao = self.lista.curselection() # tupla, vazia se nada (aula 23)
if not selecao:
messagebox.showerror("Ops", "Selecione um item.")
return
indice = selecao[0]
id = self._ids[indice] # índice do Listbox -> id do banco
nome = self.lista.get(indice)
if messagebox.askyesno("Excluir", f"Remover '{nome}'?"): # confirma (aula 23)
self.repo.remover(id)
self.preencher_lista()
print(f"[-] removido: {nome}")
if __name__ == "__main__":
repo = RepositorioProduto()
app = App(repo)
app.mainloop() # o aluno roda de verdade
?; lastrowid; LIKE; ORM manual (from_row/to_params); Padrão Repositório.ProdutoInvalidoError — o salvar traduz o sqlite3.IntegrityError nela (super().__init__); específico antes do geral sqlite3.Error.App(tk.Tk) com super().__init__(), Entry/Button/Listbox, command=, curselection(), messagebox.askyesno, e um gerente de geometria por container..db.Material de estudo em casa
Copie, rode e mexa de propósito
como aproveitar
São quatro programas pequenos, completos e propositalmente didáticos. A ideia é rodar e mexer, não só ler. Cada um roda sozinho.
Cada bloco de código tem observações curtas apontando o conceito e a aula. Foque no que está sublinhado.
Os códigos completos estão nos guias de estudo (botão 📚). Copie e cole no VS Code. O programa 4 usa Tkinter — rode com o Python do python.org.
Cada demo traz a saída exata do terminal como comentário. Se a sua diverge, é porque mexeu em algo — ótimo, está aprendendo.
Tire o encoding="utf-8", troque o ? por f-string, remova o super().__init__(): veja o que quebra. Quebrar de propósito ensina.
programa 1 · cenário
Um Pedido encapsula o valor, um Cadastro compõe uma lista de pedidos e grava/lê tudo de um .txt. Cobre a base POO (02–05) mais arquivos (17) e coleções (18).
@property/@setter validam o valor (levanta ValueError), e __str__ dá a representação amigável (aula 03). Cadastro compõe a lista _pedidos e persiste em arquivo com salvar/carregar_todos — sempre encoding="utf-8", caminho ancorado com Path(__file__).parent. sorted(key=lambda ...) — a mesma key= que é Strategy (aula 21). Clique no botão 📚 deste slide para copiar o pizzaria.py inteiro e rodar.
programa 1 · parte 1
O par @property/@setter com o mesmo nome do atributo: o getter lê self._valor, o setter valida e guarda. Valor inválido é barrado na origem com raise ValueError.
self.valor = valor no __init__ já passa pelo setter — a validação vale até na criação do objeto.__str__ devolve o texto que aparece no print(pedido) e na f-string. Nunca use print dentro do __str__ — ele deve retornar."""Cadastro de pizzaria — POO + coleções + arquivos (aulas 02–05, 17, 18)."""
from pathlib import Path
ARQUIVO = Path(__file__).parent / "pedidos.txt" # ancorado ao lado do script (aula 17)
class Pedido:
"""Um pedido: cliente, sabor e valor. Encapsula o valor (aulas 02–04)."""
def __init__(self, cliente, sabor, valor):
self.cliente = cliente # atributos simples (aula 02)
self.sabor = sabor
self.valor = valor # passa pelo setter -> valida
@property
def valor(self): # getter (aula 04)
return self._valor
@valor.setter
def valor(self, novo): # setter valida e guarda (aula 04)
if novo <= 0:
raise ValueError("valor deve ser positivo")
self._valor = novo
def __str__(self): # representação amigável (aula 03)
return f"{self.cliente} — {self.sabor} (R$ {self._valor:.2f})" programa 1 · parte 2
A coleção vira uma classe: Cadastro cria e possui a lista _pedidos (composição). salvar reescreve o arquivo em "w"; carregar_todos é um factory (@classmethod) que reconstrói os objetos do texto.
with open(..., encoding="utf-8") fecha sozinho (o finally automático). Ao carregar, tudo vem como texto — por isso float(valor).[p for p in ... if ...] filtra, e sorted(key=lambda p: p.valor) devolve uma lista nova ordenada — sem alterar a original.class Cadastro:
"""Compõe uma lista de Pedidos e persiste em arquivo (aulas 05, 17, 18)."""
def __init__(self):
self._pedidos = [] # composição (aula 05)
def adicionar(self, pedido):
self._pedidos.append(pedido)
def salvar(self, caminho=ARQUIVO):
# reescreve o arquivo inteiro em "w" (aula 17)
with open(caminho, "w", encoding="utf-8") as f:
for p in self._pedidos:
f.write(f"{p.cliente};{p.sabor};{p.valor}\n")
@classmethod
def carregar_todos(cls, caminho=ARQUIVO):
# factory que reconstrói os objetos a partir do texto (aula 17)
cadastro = cls()
try:
with open(caminho, "r", encoding="utf-8") as f:
for linha in f:
cliente, sabor, valor = linha.strip().split(";")
cadastro.adicionar(Pedido(cliente, sabor, float(valor)))
except FileNotFoundError:
pass # 1ª execução: arquivo ainda não existe
return cadastro
def buscar_por_cliente(self, alvo):
# list comprehension filtrando (aula 18)
return [p for p in self._pedidos if p.cliente == alvo]
def por_valor(self):
# ordena com sorted(key=lambda) — devolve lista nova (aula 18)
return sorted(self._pedidos, key=lambda p: p.valor) programa 1 · rodando
Monta três pedidos, tenta um valor negativo (recusado), salva, recarrega do arquivo e mostra a busca e a ordenação. A saída real está comentada no fim.
salvar().Pedido("Carla", "Frango", -5) nem chega a ser criado — o setter levanta ValueError no __init__.# ── Demonstração ──────────────────────────────────────────────────────
cadastro = Cadastro()
cadastro.adicionar(Pedido("Ana", "Calabresa", 39.90))
cadastro.adicionar(Pedido("Bruno", "Marguerita", 42.00))
cadastro.adicionar(Pedido("Ana", "Portuguesa", 45.50))
# valor inválido é barrado na origem (aula 04/14)
try:
Pedido("Carla", "Frango", -5)
except ValueError as erro:
print(f"[recusado] {erro}")
cadastro.salvar() # grava em pedidos.txt
# recarrega do zero, provando a persistência (aula 17)
recarregado = Cadastro.carregar_todos()
print("Pedidos da Ana:")
for p in recarregado.buscar_por_cliente("Ana"):
print(f" - {p}")
print("Todos por valor (crescente):")
for p in recarregado.por_valor():
print(f" - {p}")
# Saída no terminal:
# [recusado] valor deve ser positivo
# Pedidos da Ana:
# - Ana — Calabresa (R$ 39.90)
# - Ana — Portuguesa (R$ 45.50)
# Todos por valor (crescente):
# - Ana — Calabresa (R$ 39.90)
# - Bruno — Marguerita (R$ 42.00)
# - Ana — Portuguesa (R$ 45.50) programa 2 · cenário
A persistência sobe um degrau: sai do arquivo de texto e entra no sqlite3. Cada objeto é uma linha, cada atributo uma coluna. O RepositorioProduto esconde todo o SQL atrás de métodos do domínio.
?: CREATE, INSERT, SELECT, UPDATE, DELETE, sempre com placeholder ? — nunca f-string (SQL injection). .db ancorado com Path(__file__).parent. from_row (@classmethod, tupla → objeto) e to_params (objeto → tupla dos ?) — parentes do from_dict/to_dict da aula 17. salvar traduz o sqlite3.IntegrityError (uma regra da tabela, ex.: NOT NULL) numa exceção de domínio: raise ProdutoInvalidoError. O botão 📚 deste slide traz o repositorio_sqlite.py e o app_estoque.py (programa 4) inteiros.
programa 2 · parte 1
A classe de domínio sabe ir e voltar do banco. from_row transforma a tupla lida em objeto; to_params devolve a tupla que preenche os ? do INSERT — sem o id, que o banco gera sozinho.
@classmethod) — o mesmo padrão do carregar_todos do programa 1 e do from_dict da aula 17.id depois do INSERT, via lastrowid."""Repositório SQLite — CRUD de produtos (aulas 24, 04, 14)."""
import sqlite3
from pathlib import Path
BANCO = Path(__file__).parent / "loja.db" # ancorado ao lado do script (aula 24)
class ProdutoInvalidoError(Exception):
"""Erro de domínio: o produto fere uma regra da tabela (aula 14)."""
def __init__(self, motivo):
self.motivo = motivo # guarda o dado do erro
super().__init__(f"Produto inválido: {motivo}")
class Produto:
"""Objeto de domínio; sabe virar/voltar de uma linha (ORM manual, aula 24)."""
def __init__(self, nome, preco, id=None):
self.id = id
self.nome = nome
self.preco = preco
@classmethod
def from_row(cls, linha): # tupla do banco -> objeto (aula 24)
id, nome, preco = linha
return cls(nome, preco, id)
def to_params(self): # objeto -> tupla p/ os ? (SEM o id)
return (self.nome, self.preco)
def __str__(self):
return f"#{self.id} {self.nome} — R$ {self.preco:.2f}" programa 2 · parte 2
Cada método abre a conexão com with, roda o SQL com ? e devolve objetos (não tuplas). salvar captura IntegrityError quando um campo obrigatório fica vazio (NOT NULL); buscar_por_nome usa LIKE.
repo.salvar(produto) — nunca vê INSERT. O SQL fica trancado aqui dentro (encapsulamento).except sqlite3.IntegrityError (específico, antes do geral sqlite3.Error) traduz o erro técnico do banco numa exceção de domínio: raise ProdutoInvalidoError(...).class RepositorioProduto:
"""Esconde todo o SQL atrás de métodos do domínio (Repositório, aulas 04/24)."""
def __init__(self, caminho=BANCO):
self.caminho = caminho
self.criar_tabela()
def criar_tabela(self):
with sqlite3.connect(self.caminho) as con:
con.execute(
"CREATE TABLE IF NOT EXISTS produtos ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"nome TEXT NOT NULL, "
"preco REAL NOT NULL)"
)
def salvar(self, produto):
# INSERT com placeholder ? (nunca f-string!) — aula 24
try:
with sqlite3.connect(self.caminho) as con:
cursor = con.execute(
"INSERT INTO produtos (nome, preco) VALUES (?, ?)",
produto.to_params(),
)
produto.id = cursor.lastrowid # id gerado pelo banco
return produto
except sqlite3.IntegrityError: # feriu uma regra da tabela (ex.: NOT NULL) — aula 14
raise ProdutoInvalidoError("nome e preço são obrigatórios")
except sqlite3.Error as erro: # rede de segurança geral
print(f"[erro no banco] {erro}")
return None
def listar(self):
with sqlite3.connect(self.caminho) as con:
cursor = con.execute("SELECT id, nome, preco FROM produtos ORDER BY id")
return [Produto.from_row(l) for l in cursor.fetchall()]
def buscar_por_nome(self, termo):
with sqlite3.connect(self.caminho) as con:
cursor = con.execute(
"SELECT id, nome, preco FROM produtos WHERE nome LIKE ?",
(f"%{termo}%",), # busca parcial com LIKE + % (aula 24)
)
return [Produto.from_row(l) for l in cursor.fetchall()]
def atualizar(self, produto):
with sqlite3.connect(self.caminho) as con:
con.execute(
"UPDATE produtos SET nome = ?, preco = ? WHERE id = ?",
(produto.nome, produto.preco, produto.id),
)
def remover(self, id):
with sqlite3.connect(self.caminho) as con:
con.execute("DELETE FROM produtos WHERE id = ?", (id,)) programa 2 · rodando
Grava três produtos, tenta salvar um sem nome (recusado pelo NOT NULL), lista, atualiza o preço do café, remove o leite e faz uma busca parcial. A saída real está comentada no fim.
with do sqlite3 commita ao sair (rollback se der erro). Cuidado: diferente do with de arquivo, ele NÃO fecha a conexão — aqui abrimos uma por operação.LIKE: traz Cafe e Pao. O % nas pontas (%a%) casa o termo em qualquer posição do nome.# ── Demonstração (rode num loja.db novo pra bater a saída) ─────────────
repo = RepositorioProduto()
repo.salvar(Produto("Cafe", 18.0))
repo.salvar(Produto("Leite", 6.5))
repo.salvar(Produto("Pao", 8.0))
# produto sem nome: fere o NOT NULL; o Repositório traduz o IntegrityError (aula 14)
try:
repo.salvar(Produto(None, 20.0))
except ProdutoInvalidoError as erro:
print(f"[recusado] {erro}") # print(erro) usa a mensagem do super().__init__
print("Todos os produtos:")
for p in repo.listar():
print(f" {p}")
cafe = repo.buscar_por_nome("Cafe")[0] # atualiza o preço do café
cafe.preco = 21.0
repo.atualizar(cafe)
leite = repo.buscar_por_nome("Leite")[0] # remove o leite
repo.remover(leite.id)
print("Depois de atualizar Cafe e remover Leite:")
for p in repo.listar():
print(f" {p}")
print("Busca por 'a':")
for p in repo.buscar_por_nome("a"):
print(f" {p}")
# Saída no terminal:
# [recusado] Produto inválido: nome e preço são obrigatórios
# Todos os produtos:
# #1 Cafe — R$ 18.00
# #2 Leite — R$ 6.50
# #3 Pao — R$ 8.00
# Depois de atualizar Cafe e remover Leite:
# #1 Cafe — R$ 21.00
# #3 Pao — R$ 8.00
# Busca por 'a':
# #1 Cafe — R$ 21.00
# #3 Pao — R$ 8.00 programa 3 · cenário
Os dois padrões comportamentais da aula 21, lado a lado. Observer: um Subject avisa vários inscritos. Strategy: um Contexto delega a uma estratégia trocável em runtime. Ambos vivem de ABC + polimorfismo + composição.
Canal é o Subject — mantém _inscritos, e notificar() percorre a lista chamando atualizar() em cada um (um-para-muitos). Carrinho é o Contexto — guarda uma estratégia de desconto e delega a ela. Trocar carrinho.desconto muda o cálculo sem tocar em quem chama. command= de um botão Tkinter (aula 22) é Observer; o sorted(key=...) da aula 18 é Strategy. Aqui eles aparecem explícitos. programa 3 · parte 1
Inscrito é o contrato (ABC). Canal é o Subject: guarda os inscritos e, ao publicar, chama o mesmo atualizar() em cada um — e cada um responde do seu jeito (polimorfismo).
notificar não usa isinstance — chama atualizar() e confia que cada observer implementa o seu.botao.config(command=funcao) faz — inscrever um observer que roda quando o evento acontece."""Padrões aplicados — Observer e Strategy (aula 21)."""
from abc import ABC, abstractmethod
# ── Observer: um Canal avisa vários inscritos (aula 21) ────────────────
class Inscrito(ABC):
@abstractmethod
def atualizar(self, video): # contrato do observer
pass
class Pessoa(Inscrito):
def __init__(self, nome):
self.nome = nome
def atualizar(self, video): # cada observer reage do seu jeito
print(f" {self.nome} recebeu: '{video}'")
class Canal:
"""Subject: mantém a lista e notifica todos (um-para-muitos)."""
def __init__(self, nome):
self.nome = nome
self._inscritos = [] # composição (aula 05)
def inscrever(self, inscrito):
self._inscritos.append(inscrito)
def notificar(self, video):
print(f"[{self.nome}] publicou '{video}' — avisando {len(self._inscritos)}:")
for inscrito in self._inscritos: # polimorfismo: mesma chamada (aula 09)
inscrito.atualizar(video) programa 3 · parte 2 + rodando
Cada regra de desconto é uma classe com o mesmo método calcular. O Carrinho guarda a estratégia ativa e delega. Trocar a estratégia (carrinho.desconto = ...) muda o resultado sem alterar o Carrinho.
SemDesconto devolve 200,00; ao trocar para DescontoBlackFriday, a mesma compra vira 140,00 — 30% off.# ── Strategy: o Carrinho troca a regra de desconto em runtime (aula 21) ─
class Desconto(ABC):
@abstractmethod
def calcular(self, valor): # contrato da estratégia
pass
class SemDesconto(Desconto):
def calcular(self, valor):
return valor
class DescontoBlackFriday(Desconto):
def calcular(self, valor):
return valor * 0.70 # 30% off
class Carrinho:
"""Contexto: guarda a estratégia ativa e DELEGA a ela."""
def __init__(self, total, desconto):
self.total = total
self.desconto = desconto # composição (aula 05)
def total_final(self):
return self.desconto.calcular(self.total)
# ── Demonstração ──────────────────────────────────────────────────────
canal = Canal("SENAI Play")
canal.inscrever(Pessoa("Ana"))
canal.inscrever(Pessoa("Bruno"))
canal.notificar("POO com Python - Aula 24")
carrinho = Carrinho(200.0, SemDesconto()) # mesma compra, estratégias diferentes
print(f"Sem desconto: R$ {carrinho.total_final():.2f}")
carrinho.desconto = DescontoBlackFriday() # troca em runtime (aula 21)
print(f"Black Friday: R$ {carrinho.total_final():.2f}")
# Saída no terminal:
# [SENAI Play] publicou 'POO com Python - Aula 24' — avisando 2:
# Ana recebeu: 'POO com Python - Aula 24'
# Bruno recebeu: 'POO com Python - Aula 24'
# Sem desconto: R$ 200.00
# Black Friday: R$ 140.00 programa 4 · cenário
O fecho da 2ª VA: uma janela CRUD que grava e lê do SQLite. App herda de tk.Tk, tem Entry + Button + Listbox, confirma exclusão com messagebox.askyesno e conversa com o banco pelo RepositorioProduto do programa 2.
App(tk.Tk) — a janela É um objeto (herança + super().__init__() primeiro). command= nos botões e o Listbox como espelho da lista. repo.salvar, repo.listar e repo.remover. O banco é a fonte da verdade; o Listbox só reflete. .pack. Misturar pack e grid no mesmo pai levanta TclError. programa 4 · parte 1
Versão enxuta do repositório (só id e nome) para a GUI consumir. Mesmo desenho: cada método abre a conexão com with, roda SQL com ? e devolve dados prontos. A tela nunca vê SQL.
salvar, listar e remover são a interface do domínio. Trocar .txt por SQLite não muda quem chama — só esta classe.?: o nome digitado pelo usuário entra por (nome,) — nunca colado na string. É a defesa contra SQL injection da aula 24."""App Tkinter que consome o repositório SQLite (aulas 22, 23, 24)."""
import sqlite3
import tkinter as tk
from tkinter import messagebox
from pathlib import Path
BANCO = Path(__file__).parent / "loja_gui.db" # ancorado ao lado do script (aula 24)
class RepositorioProduto:
"""CRUD mínimo em SQLite (aula 24). A GUI usa isto sem ver SQL."""
def __init__(self, caminho=BANCO):
self.caminho = caminho
with sqlite3.connect(self.caminho) as con:
con.execute(
"CREATE TABLE IF NOT EXISTS produtos ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, nome TEXT NOT NULL)"
)
def salvar(self, nome):
with sqlite3.connect(self.caminho) as con:
con.execute("INSERT INTO produtos (nome) VALUES (?)", (nome,))
def listar(self):
with sqlite3.connect(self.caminho) as con:
cursor = con.execute("SELECT id, nome FROM produtos ORDER BY id")
return cursor.fetchall() # lista de tuplas (id, nome)
def remover(self, id):
with sqlite3.connect(self.caminho) as con:
con.execute("DELETE FROM produtos WHERE id = ?", (id,)) programa 4 · parte 2
A app inteira herda de tk.Tk: self é a janela, o estado vira atributo, os callbacks viram métodos. preencher_lista limpa e reflete o banco; excluir confirma com askyesno antes de remover.
super().__init__() é a PRIMEIRA linha. Sem ela, a janela nem existe quando você tenta criar os widgets._ids guarda o id do banco na mesma ordem do Listbox — o índice selecionado vira o id certo para remover. Todo change chama preencher_lista().class App(tk.Tk):
"""A janela É um objeto: herda de tk.Tk (aulas 07/23)."""
def __init__(self, repo):
super().__init__() # PRIMEIRA linha do __init__ (aula 23)
self.repo = repo
self.title("Estoque")
self.geometry("320x300")
# só .pack neste container — um gerente de geometria por container (aula 22)
self.campo = tk.Entry(self, width=30)
self.campo.pack(padx=10, pady=(10, 4))
tk.Button(self, text="Adicionar", command=self.adicionar).pack(pady=2)
self.lista = tk.Listbox(self, width=40, height=8)
self.lista.pack(padx=10, pady=4)
tk.Button(self, text="Excluir", command=self.excluir).pack(pady=2)
self._ids = [] # espelha os ids na ordem do Listbox (aula 23)
self.preencher_lista()
def preencher_lista(self):
# o banco é a fonte da verdade; o Listbox é só o espelho (aula 23)
self.lista.delete(0, tk.END) # limpa ANTES de reinserir
self._ids = []
for id, nome in self.repo.listar():
self._ids.append(id)
self.lista.insert(tk.END, nome)
def adicionar(self):
nome = self.campo.get().strip() # Entry.get() sempre string (aula 22)
if not nome:
messagebox.showerror("Ops", "Digite um nome.")
return
self.repo.salvar(nome) # grava no banco (aula 24)
self.campo.delete(0, tk.END)
self.preencher_lista()
print(f"[+] adicionado: {nome}")
def excluir(self):
selecao = self.lista.curselection() # tupla, vazia se nada (aula 23)
if not selecao:
messagebox.showerror("Ops", "Selecione um item.")
return
indice = selecao[0]
id = self._ids[indice] # índice do Listbox -> id do banco
nome = self.lista.get(indice)
if messagebox.askyesno("Excluir", f"Remover '{nome}'?"): # confirma (aula 23)
self.repo.remover(id)
self.preencher_lista()
print(f"[-] removido: {nome}") programa 4 · rodando
O aluno roda com o Python do python.org (Tk 8.6, igual ao Windows). Ao adicionar dois itens e excluir o primeiro (confirmando no diálogo), os print dos callbacks aparecem no terminal — a saída real está comentada abaixo.
self.adicionar/self.excluir é o observer. Vai SEM parênteses — command=self.adicionar, não self.adicionar()..db via repositório. Feche e reabra o app: os itens continuam lá — persistência real (aula 24).if __name__ == "__main__":
repo = RepositorioProduto()
app = App(repo)
app.mainloop() # o aluno roda de verdade
# Saída no terminal:
# [+] adicionado: Teclado
# [+] adicionado: Mouse
# [-] removido: Teclado autoavaliação
@property/@setter com o mesmo nome, validando no setter e levantando ValueError — e lembrar do __str__. (aulas 04, 03) self._itens = []) e persistir em .txt com encoding="utf-8" e caminho ancorado. (aulas 05, 17) sorted(key=lambda ...). (aula 18) ? — CREATE, INSERT, SELECT, UPDATE, DELETE — e explicar por que nunca usar f-string no SQL. (aula 24) from_row (@classmethod) e to_params, e devolver objetos do listar(). (aula 24) class ProdutoInvalidoError(Exception)) e traduzir o sqlite3.IntegrityError nela, com super().__init__. (aulas 04, 14, 24) inscrever/notificar, observers com atualizar) e um Strategy (Contexto que delega, estratégia trocável em runtime). (aula 21) command= é Observer e sorted(key=) é Strategy — os padrões que você já usava sem saber o nome. (aulas 21, 22, 18) App(tk.Tk) com super().__init__() primeiro, Entry + Button + Listbox, e um só gerente de geometria por container. (aulas 22, 23) askyesno confirma a exclusão. (aulas 23, 24)