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.

Nesta unidade Tupla, lista e dicionário já juntam nome e via. Classe dá um tipo chamado Ficha — validação, texto do print, métodos. Depois, as ferramentas que aparecem quando o caixa, o caderno e o molde convivem no mesmo arquivo.

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.

10.A · tupla

ficha = ("Bia", "balcão"). O que acontece com ficha[1] = "mesa"?

Imutável = não muda depois de criada.
Lista usa colchetes na criação e aceita atribuição.
Solução: TypeError. Use lista (ou dict, ou classe) se precisa alterar.

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.

10.B · classe

Uma class com __init__.

class Ficha: (ou Pedido, Produto)
def __init__(self, nome): self.nome = nome
Modelo:
class Ficha:
    def __init__(self, nome):
        self.nome = nome

Construtor: 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.

10.C · self

Em pedido.total(), o que é self dentro de total?

O ponto escolhe o objeto.
self.preco é o preço daquele pedido.
Solução: a instância à esquerda do ponto.

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.

10.D · raise

Nome vazio no __init__. O que fazer em vez de sys.exit?

Unidade 6: você também levanta exceção.
exit é para o programa inteiro (unidade 7).
Solução: raise ValueError.

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}"
10.E · print

Qual método o print consulta para mostrar o objeto?

Dois underlines, str, dois underlines.
__init__ monta; não formata a tela.
Solução: __str__.
10.F · escrever __str__

Um método def __str__(self) com return de texto.

def __str__(self):
return self.nome (ou uma f-string)
Modelo:
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)
10.G · classmethod

Por que Caixa.sortear("Ana") em vez de criar três objetos Caixa()?

Molde serve quando há muitos objetos iguais.
Um só no mundo → método da classe.
Solução: a ação é do tipo, não de cada cópia.

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.

10.H · super

No __init__ de Cliente(Pessoa), para que serve super().__init__(nome)?

Cliente acrescenta via; o nome já estava na mãe.
Sem super, o nome da Pessoa não roda.
Solução: chama o construtor da classe-mãe.

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

Problema da unidade Uma classe com __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.
Oficina 10 · Ficha

Cole a classe com __init__.

class Ficha:
def __init__(self, nome, via): self.nome = nome; self.via = via
Modelo:
class Ficha:
    def __init__(self, nome, via):
        self.nome = nome
        self.via = via

Opcional · o turno fecha

Depois da oficina da Ficha, isto é extra A unidade fecha no molde. O que segue não é obrigatório para o próximo recado: conjunto, global, dica de tipo, argparse, desempacotar, um pão por vez. Leia quando o arquivo da loja crescer; pule se a Ficha já bastou hoje.

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.

10.I · set

Qual estrutura descarta a segunda ocorrência do mesmo valor?

Lista aceita Ana duas vezes.
Conjunto matemático: cada elemento uma vez.
Solução: set.
10.J · criar set

Crie um conjunto: set(...) ou {1, 2, 3}.

set([1, 1, 2]) descarta a duplicata.
{} vazio é dicionário, não conjunto.
Exemplo: 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.

10.K · UnboundLocal

saldo = 0 no topo. Dentro de depositar, saldo += n quebra. Por quê?

Print do saldo no main, sem +=, funciona.
O erro cita variável local.
Solução: global saldo (ou uma classe Conta).

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.

10.L · TAXA

Como o código sinaliza “não mexa neste número”?

Python não tem const.
O aviso é para quem lê o código.
Solução: maiúsculas por convenção.

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.

10.M · anotação

def recado(n: int) -> str: e n = input("?"). O que a anotação faz?

input sempre devolve str.
Python não é Java.
Solução: dica para ferramenta e para humano; int() é outro passo.

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.

10.N · argparse

Por que argparse em vez de uma torre de if no sys.argv?

-h quase sempre existe em programa de terminal.
type=int ainda reclama se vier a palavra bolo — mas com mensagem de uso.
Solução: o parser cuida do commodity; você cuida do recado.

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=", ")
10.O · * ou hint

Uma linha com unpacking (* / **) ou uma anotação de tipo (nome: int).

print(*itens) se itens for uma lista.
Ou: def area(largura: int, altura: int) -> int:
Exemplo: 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.

10.P · yield

Por que yield "pão" * i no loop, e não return?

return no meio do for não volta para o i seguinte.
A lista de um milhão de linhas é o que estoura a memória.
Solução: gerador. Um valor por vez.

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

Problema da unidade Use set, *, type hint, enumerate ou yield e imprima o efeito. Arquivo: turno.py.
Oficina 10 · cresce

Cole um trecho com set, *, anotação, enumerate ou yield, e um print.

print(set(["Ana", "Ana", "Bia"]))
Ou print(*fila) · ou for i, n in enumerate(fila)
Modelo:
nomes = ["Ana", "Ana", "Bia"]
print(set(nomes))