Unidade 10 · a ficha
A ficha de verdade
Nome e via cresceram juntos: agora são um tipo. class, objeto, __init__, self. No fim do turno, o que o programa ganha ao crescer — conjunto, dica de tipo, argparse, um pão por vez.
Passo a passo, e depois o molde
Linguagens têm jeitos de organizar o pensamento. Até agora o código desta trilha é sobretudo procedural: funções, de cima para baixo, um passo depois do outro. O caderno (unidade 5) já passou uma função para sorted(..., key=...) — um cheiro de programação funcional. Aqui entra outro jeito: o molde. Quando o arquivo cresce, juntar dado e ação no mesmo tipo deixa o desenho menos frágil.
Nome e via, na mão
ficha.py. Duas perguntas: nome na fila, via (mesa ou balcão). Um print confirma.
nome = input("nome? ")
via = input("via? ")
print(f"{nome} · {via}")
Correto, curto. Unidade 4: fatiar em funções. main chama get_nome e get_via. Cada uma só devolve o input. No fim, if __name__ == "__main__": main().
O que você quer, de verdade, é uma ficha — os dois juntos. get_ficha pede os dois. Como devolver os dois?
Uma vírgula, um valor
return nome, via parece dois retornos. É um: uma tupla com dois itens. Desempacote: nome, via = get_ficha(). Ou guarde num só nome: ficha = get_ficha() e use ficha[0], ficha[1].
Tupla parece lista, mas é imutável. Não dá para fazer ficha[1] = "mesa" depois. TypeError: 'tuple' object does not support item assignment. Se o caixa digitou via errada para a Bia e você quer corrigir no código, a tupla recusa. Isso é defesa: quem não deve mudar, não muda.
Parênteses deixam a tupla óbvia: return (nome, via). Colchetes são lista. Os dois usam [0] para ler.
ficha = ("Bia", "balcão"). O que acontece com ficha[1] = "mesa"?
Mutável, e com nomes
return [nome, via] — lista. A correção ficha[1] = "mesa" passa. Ainda assim: 0 é nome, 1 é via. Com dez campos, ninguém lembra.
Dicionário: chaves "nome" e "via". Semântica. Na f-string com aspas duplas por fora, as chaves por dentro usam aspas simples: f"{ficha['nome']} · {ficha['via']}". Dá para montar o dict vazio e ir enchendo, ou devolver tudo numa linha: return {"nome": nome, "via": via}.
Dict também é mutável: if ficha["nome"] == "Bia": ficha["via"] = "mesa".
O tipo que o Python não te deu
Os autores da linguagem não iam inventar um tipo Ficha. Deram ferramentas gerais — e class: o molde. Você nomeia o tipo. Convenção: primeira letra maiúscula. class Ficha: e ... no corpo já basta para existir.
class Ficha:
...
def get_ficha():
ficha = Ficha()
ficha.nome = input("nome? ")
ficha.via = input("via? ")
return ficha
print(f"{ficha.nome} · {ficha.via}")
Ponto: atributo (depois: variável de instância). Ficha() — com parênteses — instancia um objeto daquela classe. Classe = planta da casa; objeto = a casa pintada. Vários Ficha() são várias instâncias; ana.nome não mistura com bia.nome.
Uma class com __init__.
class Ficha:
def __init__(self, nome):
self.nome = nomeConstrutor: passar nome e via na porta
Encher atributo na mão é frouxo. Melhor: return Ficha(nome, via). Isso chama um método especial __init__ (dunder init). Python cria o objeto vazio e entrega self — este objeto — para você preencher.
class Ficha:
def __init__(self, nome, via):
self.nome = nome
self.via = via
Na chamada você passa dois argumentos; na definição são três: self vem sozinho. self é convenção (pode ser outro nome; não faça isso). Instalar valores no objeto: self.nome = nome. Sem o self, o nome some quando a função acaba.
Há um __new__ que de fato aloca memória; você quase nunca o escreve. O que importa é o init.
Em pedido.total(), o que é self dentro de total?
Não deixe ficha sem nome
Nome em branco, via que não é mesa nem balcão: não grave isso. Validar em get_ficha até funciona, mas a regra pertence ao tipo Ficha. Encapsular: o que é da ficha fica na classe.
print("faltou o nome") e seguir cria o objeto mesmo assim. sys.exit mata o programa inteiro — grosso. return None é tarde: o objeto já existe. Unidade 6: raise ValueError("faltou o nome"). Quem chamou pode try/except. Via inválida: if via not in ["mesa", "balcão"]: raise ValueError("via inválida").
Num dict, qualquer string entra na chave. Na classe, o init decide o que entra.
Nome vazio no __init__. O que fazer em vez de sys.exit?
print(ficha) sem o endereço feio
print(ficha) sem ajuda: <__main__.Ficha object at 0x...> — lugar na memória. Defina __str__(self). O print (e quem quiser string) chama isso sozinho. repr é o irmão mais técnico, para quem programa; str é o recado para quem usa.
def __str__(self):
return f"{self.nome} · {self.via}"
Qual método o print consulta para mostrar o objeto?
Um método def __str__(self) com return de texto.
def __str__(self):
return f"{self.nome} · {self.via}"Ação da ficha, não só dado
Dict guarda chave e valor. Classe também guarda função — método. Sem dunder: você escolhe o nome. Primeiro parâmetro continua self.
def etiqueta(self):
match self.via:
case "mesa":
return "leva na mesa"
case "balcão":
return "chama no balcão"
case _:
return "?"
print(ficha.etiqueta()) — não passe self. Devolver string (unidade 4) em vez de só imprimir.
O ponto com porteiro
O init valida. Depois, ficha.via = "rua" fura a regra: atributo de instância é acessível. Propriedade: getter e setter. Quem lê ou grava .via passa por função — aí entra o mesmo raise.
@property
def via(self):
return self._via
@via.setter
def via(self, via):
if via not in ["mesa", "balcão"]:
raise ValueError("via inválida")
self._via = via
O atributo real se chama _via para não colidir com o método via. No init, escreva self.via = via sem underscore — assim o setter também valida na criação. Um underscore na frente é honra: “não mexa”. ficha._via = "rua" ainda passa. Java trava; Python confia na convenção. Dois underscores gritam mais alto; ainda dá para contornar.
Getter: só self. Setter: self e o valor novo. @property é um decorador — função que altera outra função.
Você já usava isso
int("50") constrói um objeto da classe int. "café".strip() é método da classe str. lista.append, dict — tudo classe. Os tipos embutidos vão em minúscula; os seus, com maiúscula no começo. Curiosidade: print(type(50)) mostra <class 'int'>.
Um caixa só, não um molde de caixas
Às vezes a ação não é de um objeto, é do tipo. Na padaria há um caixa que sorteia via (random.choice, unidade 7). Não faz sentido instanciar três caixas. @classmethod: o primeiro parâmetro é cls (não dá para chamar de class — palavra reservada). Variável de classe: vias = ["mesa", "balcão"] colada na classe, sem self — uma cópia para todos.
class Caixa:
vias = ["mesa", "balcão"]
@classmethod
def sortear(cls, nome):
print(f"{nome} · {random.choice(cls.vias)}")
Caixa.sortear("Ana")
Sem Caixa(). Método de instância (com self) é o padrão, sem decorador. Dá para fazer o mesmo com função solta em caixa.py; a classe é o jeito de empacotar quando o mundo do programa tem vários tipos (ficha, caixa, bandeja).
O get_ficha solto no arquivo também cheira mal: lógica da ficha fora da classe. @classmethod def get(cls): pede os inputs e return cls(nome, via) — evita o ovo e a galinha (não precisa de uma Ficha para criar outra).
ficha = Ficha.get()
print(ficha)
Por que Caixa.sortear("Ana") em vez de criar três objetos Caixa()?
Pessoa, cliente, barista
Cliente tem nome e via. Barista tem nome e turno. Os dois têm nome — copiar if not nome: raise é bandeira vermelha. Classe Pessoa com o nome. class Cliente(Pessoa): e class Barista(Pessoa):.
class Pessoa:
def __init__(self, nome):
if not nome:
raise ValueError("faltou o nome")
self.nome = nome
class Cliente(Pessoa):
def __init__(self, nome, via):
super().__init__(nome)
self.via = via
class Barista(Pessoa):
def __init__(self, nome, turno):
super().__init__(nome)
self.turno = turno
super() é a classe-mãe. O init do Cliente ainda precisa chamar o da Pessoa — senão o nome não é instalado. Hierarquia de exceções (unidade 6) já era isso: ValueError desce de Exception. Dá para criar a sua, herdando de uma existente.
No __init__ de Cliente(Pessoa), para que serve super().__init__(nome)?
Bandeja + bandeja
+ em string junta texto; em lista, concatena. Sobrecarga de operador: você ensina o que + significa no seu tipo. __add__(self, other) — esquerda e direita do mais.
class Bandeja:
def __init__(self, pao=0, cafe=0, suco=0):
self.pao = pao
self.cafe = cafe
self.suco = suco
def __str__(self):
return f"{self.pao} pães, {self.cafe} cafés, {self.suco} sucos"
def __add__(self, other):
return Bandeja(
self.pao + other.pao,
self.cafe + other.cafe,
self.suco + other.suco,
)
mesa = Bandeja(2, 1, 0)
balcao = Bandeja(1, 2, 1)
print(mesa + balcao)
Há uma lista longa de dunders (__sub__, comparações…). Não invente símbolo novo no teclado. Tupla, lista e dict continuam válidos. Classe entra quando o programa ganha entidades com dado e comportamento — e você quer o código desenhado, não só certo.
Oficina · class Ficha
__init__ (ficha: nome e via; ou produto: nome e preço). Opcional: __str__ ou um método que devolve texto. Arquivo: ficha.py ou produto.py.
Cole a classe com __init__.
class Ficha:
def __init__(self, nome, via):
self.nome = nome
self.via = viaOpcional · o turno fecha
Quando o caixa, o caderno e o molde convivem no mesmo arquivo, aparecem ferramentas que a ficha sozinha não pedia.
Conjunto: via uma vez só
Na matemática, conjunto não repete. Em Python, set é um tipo — outra classe — que descarta duplicata sozinho. Lista de fichas: Ana mesa, Bia mesa, Leo balcão. Quais vias aparecem, sem contar duas vezes a mesa?
Na mão, com lista: se a via ainda não está em vias, append. No fim, sorted(vias) e print. Funciona. Você reinventou o conjunto.
vias = set()
for ficha in fichas:
vias.add(ficha["via"])
for via in sorted(vias):
print(via)
Não é append: é add. Sem o if not in. "mesa" in vias e not in valem como na lista. Set vazio é set() — {} é dict. Chaves {1, 2, 3} montam um conjunto com números.
Grafia diferente conta como valor diferente: "Balcão" e "balcão" são dois. Se veio de humano, .lower() antes de adicionar. União e interseção (|, &) existem; o mínimo é criar, adicionar, perguntar se já viu.
Qual estrutura descarta a segunda ocorrência do mesmo valor?
Crie um conjunto: set(...) ou {1, 2, 3}.
set([1, 1, 2]) ou {1, 2}Ler de cima, gravar com aviso
caixa.py. Variável no topo do arquivo — global do módulo. main pode ler saldo e imprimir 0. Depositar 100 e sacar 50: as funções tentam saldo += n. UnboundLocalError: local variable 'saldo' referenced before assignment. Mentira útil: o Python viu uma atribuição dentro da função e tratou o nome como local. Ler passa; gravar, não.
Mover saldo para dentro de main não resolve: depósito e saque não enxergam o local de outra função. Passar o inteiro como argumento também não: o += muda a cópia.
Palavra global saldo no começo de quem altera. Aí 0, depois 50. Se você declara o mesmo nome no topo e de novo na função, o de dentro sombreia o de fora — não mexa no de cima. Não faça os dois.
Em script curto, global resolve. Em geral, evite: o dado some de vista. Uma classe Conta, self._saldo, métodos depositar e sacar. @property só getter: conta.saldo lê; sem setter, conta.saldo = 1000 não cola. O self é o acesso compartilhado, sem global.
saldo = 0 no topo. Dentro de depositar, saldo += n quebra. Por quê?
MAIÚSCULAS no honra
Três print("próximo") no loop. O 3 no meio de um arquivo grande some. Suba: VEZES = 3 e range(VEZES). Um lugar para mudar. Python não trava: VEZES = 4 mais abaixo roda. Outras linguagens têm const; aqui o aviso é o nome em maiúsculas. Convenção, não cadeado.
Na classe, variável de classe no mesmo espírito: class Forno: e VEZES = 3 fora dos métodos. O método usa Forno.VEZES. Continua honra.
Como o código sinaliza “não mexa neste número”?
Dica de tipo, não conversão
Python é dinamicamente tipado: você não declara int x. C e Java obrigam; o compilador pega troca de tipo cedo. Aqui o interpretador não exige. Você pode anotar: def recado(n: int) -> None:. Dois pontos e o nome do tipo. Não cria um int. Não converte o input.
n = input("quantos? ") devolve str. range(n) explode: TypeError: 'str' object cannot be interpreted as an integer. A anotação sozinha não impede isso. mypy recado.py (pip install mypy) lê as dicas e grita antes do usuário: argumento str, esperava int. Anote também a variável: n: int = input(...) — o mypy pega já na atribuição. O conserto continua sendo int(input(...)). A palavra int no hint não é a chamada int().
Função que só imprime devolve None implícito. Se você faz texto = recado(3) e printa, aparece próximo três vezes e um None. Anote -> None: o mypy avisa que não há valor. Melhor, unidade 4: devolver string. -> str e return "próximo\n" * n — multiplicar string (nesta unidade, sobrecarga). print(texto, end="") para não duplicar a quebra.
Times grandes usam hint porque acha bug cedo. O Python em si não vai passar a recusar. Opinião de projeto, não lei da linguagem.
def recado(n: int) -> str: e n = input("?"). O que a anotação faz?
Três aspas, documentação
PEP 257: a documentação da função fica dentro dela, não num # em cima. Três aspas duplas (ou simples) no começo e no fim. Ferramentas extraem isso e montam página ou PDF. Convenção extra (reStructuredText) descreve parâmetro, tipo, exceção, retorno — não é sintaxe do Python; é texto padronizado para o gerador de docs. Dá para pôr exemplo de entrada e saída e um outro programa conferir. Biblioteca de terceiro que você já usou veio com página porque alguém documentou.
def recado(n: int) -> str:
"""Devolve n linhas de 'próximo'.
:param n: quantas vezes
:type n: int
:raises TypeError: se n não for int
:return: o texto
:rtype: str
"""
return "próximo\n" * n
-n 3, sem floresta de if
Unidade 7: sys.argv. Sem argumento, um próximo. Com três tokens — nome do arquivo, -n, número — o loop. Convenção: um traço e uma letra (-n); duas e uma palavra (--vezes). Mensagem uso: recado.py se o humano errar. Quando entram -a, -b, -c em qualquer ordem, o emaranhado explode.
argparse vem com o Python. Construtor ArgumentParser, método add_argument("-n"), parse_args() olha o argv por você. O objeto args tem args.n. -h / --help sai de graça. description="chama a próxima da fila" e help="quantas vezes" deixam o texto humano. default=1 e type=int: sem flag, um recado; sem int() na mão. -n bolo vira erro de uso gerado pela biblioteca, não o seu TypeError.
import argparse
parser = argparse.ArgumentParser(description="chama a próxima da fila")
parser.add_argument("-n", default=1, type=int, help="quantas vezes")
args = parser.parse_args()
for _ in range(args.n):
print("próximo")
Um número solto no argv ainda cabe no sys.argv[1]. Flag, ordem livre, ajuda: argparse.
Por que argparse em vez de uma torre de if no sys.argv?
Desempacotar: lista, dict, args
primeiro, _ = input("nome? ").split(" ") — Ana Silva vira dois pedaços; o underscore é o sobrenome que você não usa. Isso já era unpacking.
def total(pao, cafe, suco): devolve pao * 4 + cafe * 6 + suco * 5 (preço). Pedido numa lista [2, 1, 0]: dá para passar itens[0], itens[1], itens[2]. Ou total(*itens) — um asterisco explode a sequência em argumentos posicionais. Tupla vale; set não (ordem não é contrato). Quatro números para três parâmetros: TypeError. Dict: {"pao": 2, "cafe": 1, "suco": 0} e total(**pedido) — dois asteriscos viram pao=2, cafe=1, suco=0. Chave a mais ("agua"): keyword inesperado.
Na definição, o mesmo desenho: def f(*args, **kwargs). Variádica: quantidade variável. args é tupla dos posicionais; kwargs é dict dos nomeados. Convenção de nomes; poderia ser *palavras. É assim que print aceita zero, um ou muitos objetos — na documentação, *objects, depois sep e end. Você pode reencaminhar g(*args, **kwargs) para embrulhar outra função.
fila = ["Ana", "Bia", "Leo"]
print(*fila, sep=", ")
Uma linha com unpacking (* / **) ou uma anotação de tipo (nome: int).
print(*["a", "b"])Gritar, filtrar, compreender
Terceiro jeito de pensar (além de procedimento e molde): um cheiro de programação funcional — funções sem efeito colateral, passadas como valor. O caderno (unidade 5) já deu lambda no sorted.
gritar(*palavras): lista vazia, loop, append(p.upper()), depois print(*maiusculas). map(str.upper, palavras) aplica a função a cada item e devolve a sequência nova — você passa str.upper sem parênteses, para o map chamar. List comprehension, mais pythonica: maiusculas = [p.upper() for p in palavras]. Dá para pôr if no fim.
Fichas com via. Só mesa: [f["nome"] for f in fichas if f["via"] == "mesa"], depois sorted. Ou filter: função que devolve True/False, filter(na_mesa, fichas). A função pode ser lambda: lambda f: f["via"] == "mesa". sorted(..., key=lambda f: f["nome"]) como no caderno.
Comprehension de dicionário: chaves por fora. {f["nome"]: f["via"] for f in fichas} — um dict só, nome → via. Ou lista de dicts: [{"nome": n, "via": "mesa"} for n in nomes] em vez de append no for.
Índice e valor juntos
for i in range(len(fila)) e print(i + 1, fila[i]) numera a fila a partir de 1. enumerate(fila) entrega o par: for i, nome in enumerate(fila): print(i + 1, nome). Sem len, sem colchete.
Um pão por vez, não o forno inteiro
Função que monta uma lista enorme e return no fim: em n grande a máquina mata o processo — RAM. Boas práticas pediam o helper testável, não tudo no main. return no meio do for entrega o primeiro e para.
yield devolve um valor e pausa a função. O for de fora pede o próximo; o gerador lembra onde parou. Iterator: você percorre sem ter a lista toda na memória.
def fornada(n):
for i in range(n):
yield "pão " * i
for linha in fornada(5):
print(linha)
Três linhas ainda cabem numa lista. Um milhão, o yield continua imprimindo na hora; o return da lista inteira trava. Ctrl+C interrompe, como no loop infinito.
Por que yield "pão" * i no loop, e não return?
O turno fecha
Você começou falando com a máquina. Guardou o crachá, escolheu a via, girou a fila, escreveu a receita, passou a prova, abriu o caderno, levantou o tombo, pegou ferramenta na prateleira, filtrou o que a ficha aceita, e agora o tipo Ficha. Nada da segunda metade desta página é obrigatório para o próximo recado. São opções quando o problema cresce.
O instinto que importa: ler a documentação, perguntar, aplicar num projeto que você queira — não só no exercício da unidade. A linguagem você agora consegue ensinar a si mesmo.
Oficina · o programa cresce
*, type hint, enumerate ou yield e imprima o efeito. Arquivo: turno.py.
Cole um trecho com set, *, anotação, enumerate ou yield, e um print.
nomes = ["Ana", "Ana", "Bia"]
print(set(nomes))