Aprendemos a formalizar contratos com ABC e @abstractmethod. Subclasses incompletas nem nasciam — o Python recusava com TypeError claro. Polimorfismo passou de "eu confio que cada objeto tem o método" para "o Python garante que cada objeto tem o método".
Os nomes que apareceram ao longo do curso — __init__, __str__, e agora ABC com sua metaclasse ABCMeta — todos usam o padrão __nome__. Não é estilo: é convenção rígida do Python. Cada um desses nomes é um protocolo da linguagem.
Um protocolo é um contrato que VOCÊ NÃO precisa escrever — o Python já escreveu. Você só precisa implementar.
@abstractmethod).__add__? Você cumpriu o contrato do operador +. Mesma mentalidade da aula 10, do outro lado.Na aula 10 você foi autor de contratos. Hoje você se torna implementador dos contratos que o Python definiu.
Em linguagens estáticas (Java, C#, C++), métodos têm assinatura: o nome mais a lista de tipos dos parâmetros. somar(int, int) e somar(int, int, int) são duas assinaturas diferentes, e o compilador escolhe na hora da chamada.
Em Python, métodos são apenas atributos da classe. class C: def f(...): ... equivale a C.f = função. Definir def f de novo é apenas reatribuir — o atributo só guarda uma referência.
class Calculadora:
def somar(self, a, b, c=0):
return a + b + c
c = Calculadora()
c.somar(1, 2) # 3 (c assume 0)
c.somar(1, 2, 3) # 6 Ideal quando você quer 2 ou 3 variações de chamada com nomes de argumento conhecidos. É a solução padrão — use sempre que possível.
*args — argumentos posicionais variáveisclass CalculadoraN:
def somar(self, *valores):
return sum(valores)
c = CalculadoraN()
c.somar() # 0
c.somar(1, 2) # 3
c.somar(1, 2, 3, 4) # 10 Dentro do método, valores é uma tupla. Use quando a quantidade é genuinamente variável e não faz sentido nomear cada um.
**kwargs — argumentos nomeados variáveisclass Config:
def configurar(self, **opcoes):
for chave, valor in opcoes.items():
print(f"{chave} = {valor}")
c = Config()
c.configurar(cor="azul", tamanho=10, debug=True) Dentro do método, opcoes é um dicionário. Útil em APIs flexíveis: bibliotecas como pandas e matplotlib usam **kwargs intensamente.
def metodo(self, obrigatorio, opcional=None, *extras, **config):
pass Ordem na assinatura: obrigatórios → defaults → *args → **kwargs. Essa é a única ordem que o Python aceita.
Para sobrecarga por tipo (não só quantidade), use isinstance dentro do método:
class Soma:
def somar(self, a, b):
if isinstance(a, str):
return a + b # concatena texto
return a + b # soma numérica Funciona, mas avalie antes: muito isinstance pode ser sinal de que você precisa de classes separadas (volta na aula 09 de polimorfismo).
Regra prática: 90% dos casos se resolvem com default. 9% com *args. 1% com **kwargs. Se você está combinando os três, pare e reveja o design.
Dunder = double underscore. São métodos com nomes no formato __nome__. O Python os chama automaticamente em resposta a operadores, funções built-in ou eventos do ciclo de vida do objeto.
Você nunca chama v.__add__(outro) diretamente — escreve v + outro e o Python faz a tradução. Essa é a essência do protocolo.
__init__(self, ...) — construtor, chamado em Classe(...).__del__(self) — destrutor, chamado quando o objeto vai pra coleta. Raramente útil.__str__(self) — texto pra humanos. Chamado por str(x) e print(x).__repr__(self) — texto técnico (debug). Chamado por repr(x) e no REPL.__add__ (+), __sub__ (-), __mul__ (*), __truediv__ (/).__floordiv__ (//), __mod__ (%), __pow__ (**).__neg__ (-x), __abs__ (abs(x)).__eq__ (==), __ne__ (!=).__lt__ (<), __le__ (<=), __gt__ (>), __ge__ (>=).__eq__ e __lt__ é o suficiente pra sorted() funcionar.__len__(self) — chamado por len(x).__getitem__(self, chave) — chamado por x[chave].__setitem__(self, chave, valor) — chamado por x[chave] = valor.__contains__(self, item) — chamado por item in x.__iter__(self) — chamado por for item in x.Implementar
__len__,__getitem__e__iter__faz seu objeto se comportar como uma lista do Python — sem herdar de list. Isso é o protocolo de sequência.
Quando você escreve 3 + Vetor(...), o Python primeiro tenta (3).__add__(vetor) — que falha porque int não sabe somar Vetor. Daí ele tenta o reflexo: vetor.__radd__(3). Implementar __radd__ permite que números na esquerda também funcionem.
class Vetor:
def __add__(self, outro):
return Vetor(self.x + outro.x, self.y + outro.y)
def __radd__(self, escalar):
return Vetor(self.x + escalar, self.y + escalar)
v = Vetor(1, 2)
3 + v # chama v.__radd__(3) → Vetor(4, 5) Antes de implementar __add__ (ou qualquer operador) na sua classe, responda: se um colega que nunca viu meu código ler a + b, ele consegue prever o que vai acontecer? Se sim, vá em frente. Se não, use método com nome explícito.
Cliente + Cliente? Concatenar nomes? Somar saldos? Use juntar_cadastros(outro).pedido.adicionar_item(produto) > pedido + produto.Sobrescrever __eq__ tem um efeito colateral: por padrão, o Python desabilita o __hash__ da classe, e seus objetos não podem mais ser usados em set ou como chave de dict. Se você precisar disso, implemente __hash__ junto.
class Produto:
def __init__(self, codigo, nome):
self.codigo = codigo
self.nome = nome
def __eq__(self, outro):
return self.codigo == outro.codigo
def __hash__(self):
return hash(self.codigo) # baseado no mesmo atributo do __eq__ Regra de consistência: se
a == b, entãohash(a) == hash(b). Sempre. Não cumprir essa regra causa bugs misteriosos emsetedict.
Imagine uma colega entrando no projeto amanhã. Ela abre seu arquivo e vê resultado = a + b. Se ela tiver que abrir a classe pra entender o que aconteceu — você sobrecarregou onde não devia. Se ela continuar a leitura sem hesitar — você fez certo.
Daqui pra frente vão aparecer dois tipos do Python que podem ser novidade: tupla (usada no *args) e dicionário (usado no **kwargs). Refresher rápido antes de seguir.
Parece lista, mas não pode ser alterada depois de criada. Você acessa por índice igual à lista — só não pode mudar o conteúdo.
t = (1, 2, 3)
t[0] # 1
len(t) # 3
t[1] = 99 # TypeError: 'tuple' object does not support item assignment Cada item tem um nome (a chave) e um conteúdo (o valor). Você acessa pelo nome da chave, não por posição numérica.
d = {"cor": "azul", "tamanho": 10}
d["cor"] # "azul"
d["tamanho"] # 10
for chave, valor in d.items():
print(chave, valor) Regra prática: tupla quando a quantidade varia mas todos os itens têm o mesmo papel (vários números, vários nomes). Dicionário quando cada item tem rótulo próprio (cor, tamanho, idade).
Hash é um número que o Python calcula a partir do conteúdo de um objeto — uma espécie de "impressão digital". Objetos com o mesmo conteúdo geram o mesmo número.
Os dois aparecem aqui, então vale situar. set é uma coleção sem ordem e sem repetição ({1, 2, 2} vira {1, 2}) — útil pra eliminar duplicatas. dict é a coleção de pares chave-valor ({"cor": "azul"}), onde você acessa pelo nome da chave. Os dois são primos da lista que vocês já conhecem.
⚠️ Não confunda: esse
seté um TIPO DE DADO (um conjunto). Não tem nada a ver com o setter de encapsulamento (aula 04), que é um MÉTODO pra alterar um atributo. Nomes parecidos, coisas totalmente diferentes.
Pra que serve? Estruturas como set e as chaves de um dict usam o hash pra encontrar itens quase instantaneamente, sem comparar um por um. É o que torna essas estruturas rápidas.
Existe uma regra de consistência: se dois objetos são iguais (a == b), eles PRECISAM ter o mesmo hash. Senão, o set e o dict se perdem na hora de localizar o objeto.
Quando você sobrescreve __eq__, muda o significado de "igual". O Python não tem como adivinhar o novo hash correto, então desativa o __hash__ por segurança — evitando bugs silenciosos. Cabe a você reimplementar.
Implemente __hash__ usando o MESMO atributo que você usou no __eq__. Assim a consistência é garantida.
class Produto:
def __init__(self, codigo, nome):
self.codigo = codigo
self.nome = nome
def __eq__(self, outro):
return self.codigo == outro.codigo
def __hash__(self):
return hash(self.codigo) # mesmo atributo do __eq__ Não precisa dominar hashing agora. Guarde a regra: mexeu em
__eq__, implemente__hash__com o mesmo atributo.
Programação Orientada a Objetos com Python
Tecnologia em Análise e Desenvolvimento de Sistemas
plano de voo
Por que escrever dois métodos com o mesmo nome em Python não funciona.
Como parâmetros default, *args e **kwargs simulam sobrecarga.
Como ensinar seus objetos a responder a +, -, == e companhia.
Classe Fracao com operações matemáticas — soma, subtração, multiplicação e divisão.
de onde paramos · 5 dias atrás
Antes de seguir, três coisas pra colocar de volta na ponta da língua.
ABC + @abstractmethod transformam um método de sugestão em obrigação — subclasses são forçadas a implementar. TypeError antes do bug virar produção. memória muscular
Toda subclasse de Pagamento era obrigada a implementar processar e gerar_recibo. Esquecer um? TypeError na hora.
from abc import ABC, abstractmethod
class Pagamento(ABC):
def __init__(self, cliente):
self.cliente = cliente
@abstractmethod
def processar(self, valor):
pass
@abstractmethod
def gerar_recibo(self, valor):
pass
class PagamentoPix(Pagamento):
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}" pergunta pra turma · 2 minutos
Discutam em duplas. Depois levantem a mão e respondam no quadro.
somar(int, int) e também somar(int, int, int) na mesma classe — o compilador escolhe pela quantidade de argumentos. Em Python, se eu definir def somar(self, a, b) e logo depois def somar(self, a, b, c), o que acontece? a resposta · e por que ela importa
Python não procura por assinatura. Procura por nome. E se o nome já existe, o novo valor substitui — silenciosamente.
def somar e simplesmente regrava o atributo da classe. O segundo apaga o primeiro — como se você fizesse x = 1; x = 2. c.somar(1, 2) dispara TypeError: missing 1 required positional argument: 'c'. Você perdeu uma versão sem perceber. Como conseguir o efeito de vários jeitos de chamar o mesmo método sem que Python apague tudo?
parte 1 · começando pelo problema
Linguagens com tipagem estática escolhem o método pela assinatura — nome + tipos + quantidade de parâmetros. Cada assinatura é um método diferente.
def permanece. Python é dinâmico: nomes são chaves em dicionários. Não há "assinatura" gravada em pedra — só o último def permanece. Em troca, ganhamos as ferramentas que vamos ver agora.
o problema em código
Veja o que acontece quando alguém vindo de Java tenta replicar o padrão de sobrecarga em Python.
def apaga o primeiro. Só a versão de 3 argumentos sobrevive — chamar com 2 quebra.class Calculadora:
def somar(self, a, b):
return a + b
def somar(self, a, b, c): # 💥 sobrescreve o de cima
return a + b + c
c = Calculadora()
print(c.somar(1, 2, 3)) # 6 ← só esta versão existe
print(c.somar(1, 2)) # TypeError: missing 'c' a ideia da solução
Se o Python só guarda um def somar, então esse único def precisa aceitar todas as variações de chamada que a gente quer suportar.
default, *args e **kwargs. Cada uma resolve um tipo de variação. Saímos de "várias versões do mesmo método" para "uma versão que se adapta".
as peças que você vai encontrar
Três ferramentas, cada uma com um papel claro. Memorize: default para opcionais, *args para muitos posicionais, **kwargs para muitos nomeados.
default: escrever def somar(self, a, b, c=0) diz "se não me passar c, use zero". Resolve o caso "às vezes 2, às vezes 3 argumentos". *args: empacota qualquer quantidade de argumentos posicionais em uma tupla. def somar(self, *valores) aceita somar(1, 2), somar(1, 2, 3, 4), somar() — todos. **kwargs: empacota argumentos nomeados em um dicionário. def configurar(self, **opcoes) aceita configurar(cor="azul", tamanho=10). Um * = desempacota em tupla. Dois ** = desempacota em dicionário. Os nomes args e kwargs são só convenção — você pode chamar de qualquer coisa.
ferramenta 1 · 90% dos casos
Pro caso clássico 'às vezes 2 args, às vezes 3'. Você dá um valor padrão e quem chamar decide se passa.
c=0 diz: "se não me passar c, use 0". Resolve a sobrecarga clássica (mesma operação, +1 argumento opcional).class Calculadora:
def somar(self, a, b, c=0):
return a + b + c
c = Calculadora()
print(c.somar(1, 2)) # 3 (c assume 0)
print(c.somar(1, 2, 5)) # 8 ferramenta 2 · quantidade variável
Pro caso 'quero somar 2, ou 5, ou 50 números'. O * empacota tudo numa tupla.
valores é uma tupla. sum(valores) faz o resto.args é convenção — você pode chamar de *numeros, *itens, *qualquer_coisa. O que importa é o *.class CalculadoraN:
def somar(self, *valores):
return sum(valores)
c = CalculadoraN()
print(c.somar()) # 0
print(c.somar(1, 2)) # 3
print(c.somar(1, 2, 3, 4)) # 10 ferramenta 3 · argumentos nomeados
Pro caso 'aceito várias opções e cada uma tem nome próprio'. Dois ** empacotam num dicionário.
opcoes é um dicionário. Cada chave=valor da chamada vira uma entrada.**kwargs o tempo todo — é como elas aceitam dezenas de opções sem listar cada uma na assinatura.class Config:
def configurar(self, **opcoes):
for chave, valor in opcoes.items():
print(f"{chave} = {valor}")
c = Config()
c.configurar(cor="azul", tamanho=10, debug=True)
# cor = azul
# tamanho = 10
# debug = True a solução em código
Três versões da mesma ideia — escolha a que faz sentido pro seu caso. Em projetos reais, default cobre 90% dos casos.
*valores aceita zero, dois ou cinquenta argumentos sem mudar nada na assinatura.# Versão 1: parâmetros default (2 OU 3 argumentos)
class Calculadora:
def somar(self, a, b, c=0):
return a + b + c
# Versão 2: *args (qualquer quantidade)
class CalculadoraN:
def somar(self, *valores):
return sum(valores)
# Versão 3: **kwargs (argumentos nomeados)
class CalculadoraConfig:
def configurar(self, **opcoes):
for chave, valor in opcoes.items():
print(f"{chave} = {valor}") o que muda do lado de fora
Mesmo código de chamada que travava antes — agora cada um funciona.
somar — atende vários casos. É isso que outras linguagens chamariam de sobrecarga.from calculadora_flexivel import Calculadora, CalculadoraN, CalculadoraConfig
c = Calculadora()
print(c.somar(1, 2)) # 3
print(c.somar(1, 2, 3)) # 6
cn = CalculadoraN()
print(cn.somar(1, 2)) # 3
print(cn.somar(1, 2, 3, 4, 5)) # 15
print(cn.somar()) # 0
cc = CalculadoraConfig()
cc.configurar(cor="azul", tamanho=10, debug=True)
# cor = azul
# tamanho = 10
# debug = True agora podemos dar o nome
Sobrecarga de método — no sentido clássico (Java, C++) — não existe em Python. O que fazemos é simular usando os recursos da própria linguagem.
default, *args, **kwargs) cobre os mesmos casos sem precisar de compilador escolhendo. Se você se pegar querendo escrever "dois métodos com o mesmo nome", pergunte: posso transformar isso em um método só com parâmetro opcional? Quase sempre dá.
analogia · do mundo real
Pensa no café da padaria. O atendente é um só — o que varia é o pedido.
default é o açúcar implícito ("se não me disser, ponho 1"). *args é o pedido com quantidade variável ("me traz três cafés"). **kwargs são as customizações nomeadas (leite, canela, temperatura). Um método. Vários jeitos de chamar.
antes de ensinar, observar
Antes de implementar dunders nas suas classes, repara que o Python já faz isso com os tipos nativos — e você usa todo dia sem perceber.
+, == e len() significam. É a mesma classe de mecanismo (__add__, __eq__, __len__) — mas com regras próprias de cada tipo.__add__? Acaba de ensinar seu objeto a entender +.print("abc" + "def") # abcdef
print([1, 2] + [3, 4]) # [1, 2, 3, 4]
print((1, 2) + (3, 4)) # (1, 2, 3, 4)
print("abc" == "abc") # True
print([1, 2] < [1, 3]) # True
print(len("abc")) # 3 parte 2 · a outra sobrecarga
A parte 1 resolveu chamadas do mesmo método com argumentos diferentes. Agora vamos pra outra dimensão de sobrecarga — bem mais visual. E ela já estava sob o seu nariz desde a aula 02.
__init__ (aulas 02 e 05), __str__ (aula 03), e o próprio ABC (aula 10) — todos seguem o padrão __nome__. Não é coincidência. +, ==), funções built-in (len(...), print(...)) e eventos do ciclo de vida do objeto. Como ensinar nossos objetos a se comportarem como tipos nativos — somáveis, comparáveis, imprimíveis?
o problema em código
Sem nenhuma configuração extra, tentar somar dois objetos da nossa classe trava com mensagem clara.
+ trava com TypeError — o Python avisa que não sabe somar nossas classes.== é pior: aceita silenciosamente. Compara identidade (mesmo objeto na memória?), não conteúdo. False mesmo com x e y iguais.class Vetor:
def __init__(self, x, y):
self.x = x
self.y = y
v1 = Vetor(1, 2)
v2 = Vetor(3, 4)
print(v1 + v2)
# TypeError: unsupported operand type(s) for +: 'Vetor' and 'Vetor'
print(v1 == Vetor(1, 2))
# False ← e isso é ainda pior: aceita, mas dá errado a ideia da solução
O Python não adivinha como somar objetos nossos — mas tem um sistema pronto pra você contar pra ele.
__ — que o Python chama automaticamente quando vê certos operadores. __add__ na sua classe e, daí em diante, v1 + v2 funciona — Python chama v1.__add__(v2) nos bastidores. É um protocolo: nomes acordados entre você e a linguagem. + chama __add__. == chama __eq__. print(x) chama __str__.
você já conhece dois
Toda vez que você escreveu __init__ ou __str__, você já estava sobrecarregando operador — só não tinha esse nome.
__init__ (aulas 02 e 05) é chamado quando você faz Vetor(1, 2). Os parênteses depois do nome da classe são o "operador de criação". __str__ (aula 03) é chamado quando você faz print(v) ou str(v). O print é o "operador de imprimir". Dunder = double underscore. O nome vem dos dois sublinhados que envolvem o método. Pronuncia-se "dander".
a solução em código
Quatro métodos. Cada um corresponde a um operador. Os nomes são fixos — o Python só procura por eles se forem escritos exatamente assim.
self (o objeto da esquerda) e outro (o da direita). O nome outro é convenção — escolha o que ler melhor.__add__ e __sub__ retornam um novo Vetor — não modificam os originais. Isso casa com como 1 + 2 não muda nem o 1 nem o 2.class Vetor:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, outro): # v1 + v2
return Vetor(self.x + outro.x, self.y + outro.y)
def __sub__(self, outro): # v1 - v2
return Vetor(self.x - outro.x, self.y - outro.y)
def __eq__(self, outro): # v1 == v2
return self.x == outro.x and self.y == outro.y
def __str__(self): # print(v1)
return f"Vetor({self.x}, {self.y})" o que muda do lado de fora
O mesmo código que travava antes — agora roda limpo. E o objeto se comporta como você esperaria de um número.
v1 + v2 conta a intenção sem o leitor precisar abrir o método somar().__eq__: agora Vetor(1, 2) == Vetor(1, 2) dá True — compara conteúdo, não endereço de memória.v1 = Vetor(1, 2)
v2 = Vetor(3, 4)
print(v1 + v2) # Vetor(4, 6)
print(v2 - v1) # Vetor(2, 2)
print(v1 == Vetor(1, 2)) # True
print(v1 == v2) # False
soma = v1 + v2
print(soma) # Vetor(4, 6) pergunta pra turma · 3 minutos
Em duplas. Pensando num Vetor 2D (posição, deslocamento, velocidade) — discutam:
* (multiplicar por escalar?), /, < (qual seria "menor"?), len(v) (o tamanho do vetor?), -v (o vetor oposto?). Vamos comparar as respostas com a tabela completa de dunders no próximo slide — preparem suas respostas pra ver o que o Python oferece pronto.
panorama · os dunder methods mais usados
| item | detalhe |
|---|---|
+ → __add__ | Soma. a + b chama a.__add__(b). |
- → __sub__ | Subtração. Mesma lógica do +. |
* → __mul__ | Multiplicação. |
/ → __truediv__ | Divisão de verdade (a que devolve float). |
== → __eq__ | Igualdade. Sem implementar, Python compara identidade (endereço). |
< → __lt__ | Menor que. Implementar este já permite sorted(lista) funcionar. |
<= → __le__ | Menor ou igual. Há também __gt__ e __ge__ para os contrários. |
len(x) → __len__ | Quanto seu objeto "mede". Útil em coleções customizadas. |
str(x) → __str__ | Conversão para texto legível (para humanos). Já usado desde a aula 03. |
analogia do cotidiano
Pensa numa tomada. Ela tem um padrão — três pinos, voltagem específica. Qualquer aparelho que respeitar esse padrão se conecta sem o eletricista precisar saber se é geladeira ou ventilador.
+, ==, print) é o formato da tomada. Quem chama o operador é o eletricista — não precisa abrir o aparelho. __add__ é colocar o plugue certo no seu aparelho. Daí em diante, ele encaixa em qualquer tomada do tipo +. Dunder methods são contratos do próprio Python — análogos ao @abstractmethod da aula 10, mas definidos pela linguagem em vez de por você.
anatomia
Toda sobrecarga de operador segue o mesmo esqueleto. Decora esse padrão e o resto é variação.
Quer responder a +, ==, len()? Consulte a tabela e ache o nome do dunder.
Os nomes são fixos: __add__, __eq__, __lt__. Erro de digitação = Python ignora o método.
self e outroOperadores binários recebem dois objetos: o da esquerda (self) e o da direita (outro).
Devolva um novo objeto ou um valor. Não altere self dentro de __add__ — 1 + 2 também não muda o 1.
armadilha · não faça isso
Quando você escreve 1 + 2, nem o 1 nem o 2 mudam. O mesmo vale pros seus dunders. Erro clássico: alterar self dentro de __add__.
v3 = v1 + v2 parece OK, mas v1 agora vale v1 + v2. O original sumiu silenciosamente.1 + 2 não muda o 1.# Errado: modifica self silenciosamente
class VetorRuim:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, outro):
self.x = self.x + outro.x # muda self
self.y = self.y + outro.y # muda self
return self
# Certo: devolve novo objeto
class VetorBom:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, outro):
return VetorBom(self.x + outro.x, self.y + outro.y) antes e depois · lado a lado
v1.somar(v2) — método explícito v1 + v2 trava com TypeError v1 == v2 compara endereço (quase sempre False) print(v1) mostra <Vetor object at 0x...> v1 + v2 — leitura natural __add__ automaticamente v1 == v2 compara conteúdo, como esperado print(v1) mostra Vetor(1, 2) uso com critério
Sobrecarregar operador é poderoso, mas não toda classe ganha com isso. Use quando o operador de fato tem significado natural na sua classe.
Cliente + Cliente não faz sentido — use um método com nome explícito, tipo juntar(outro_cliente). Se um colega lendo seu código tiver que parar pra pensar o que a + b faz, é sinal de que a.combinar(b) teria sido melhor escolha.
cuidado especial · __eq__
Um cuidado pra guardar pra mais tarde. O Python identifica cada objeto por um número chamado hash — pensa numa "impressão digital". Quando você cria seu próprio __eq__, o Python desativa essa impressão digital por segurança, e o objeto deixa de funcionar em algumas estruturas (como set e chave de dict). Não precisa dominar hash agora — só siga a regra de bolso abaixo.
__eq__), eles precisam ter a mesma "impressão digital" (__hash__). Por isso os dois usam o MESMO atributo — aqui, o codigo.class Produto:
def __init__(self, codigo, nome):
self.codigo = codigo
self.nome = nome
def __eq__(self, outro):
return self.codigo == outro.codigo
def __hash__(self):
return hash(self.codigo) # mesmo atributo do __eq__ glossário rápido
| item | detalhe |
|---|---|
| Sobrecarga (overloading) | Dar mais de um comportamento ao mesmo nome de método ou operador. Foi o tema de hoje — vimos as duas formas: de método e de operador. |
| Sobrescrita (overriding) | NÃO confunda com sobrecarga. Sobrescrita (vista na aula 08) é a classe-filha redefinir um método herdado da classe-mãe — assunto de herança, não de hoje. |
| Parâmetro default | Valor que o parâmetro assume quando a chamada não passa nada. Em def f(a, b=0), se ninguém informar b, ele vale 0. |
*args | Recolhe vários argumentos posicionais numa tupla. Use quando não sabe quantos vêm — de zero pra cima. |
**kwargs | Recolhe vários argumentos nomeados num dicionário. O nome vem de keyword arguments (argumentos com nome). |
| Dunder method (método mágico) | Método no formato __nome__ (dois underlines de cada lado). O Python chama sozinho em resposta a operadores e funções como print(). Ex.: __init__, __str__, __add__. |
| Sobrecarga de operador (operator overloading) | O tipo de sobrecarga que vimos a fundo: implementar o dunder certo (__add__, __eq__...) pra que +, == e cia. funcionem com objetos da sua classe. |
| Protocolo | Um grupo de dunders relacionados que dão uma capacidade ao objeto — ser somável, comparável, ter tamanho. Implementou os dunders daquele grupo? Seu objeto ganhou aquela capacidade. |
laboratório em sala · duplas
Em duplas. 30 minutos. Objetivo: construir uma Fracao que se comporta como um número — soma, subtrai, multiplica, divide, compara e imprime bonito.
__init__(numerador, denominador) + método auxiliar _simplificar() que reduz a fração usando gcd do módulo math.
Implementar __add__, __sub__, __mul__ e __truediv__. Toda operação devolve uma nova Fracao já simplificada.
__eq__ (duas frações são iguais quando simplificadas batem) e __str__ (formato 3/4 no print).
Verificar Fracao(1,2) + Fracao(1,4) == Fracao(3,4) e Fracao(2,4) == Fracao(1,2). Os dois devem dar True.
resolução · passo 1
O __init__ já simplifica na construção — assim toda Fracao na vida do programa já nasce reduzida.
gcd (do módulo math) calcula o máximo divisor comum. Dividir numerador e denominador por ele dá a fração reduzida._ em _simplificar sinaliza método interno — convenção de encapsulamento que vimos na aula 04.gcd: é uma função pronta do Python pra qualquer caso que precise simplificar fração. Você importa quando precisar — esse não é o tema da aula, é só uma ferramenta.from math import gcd
class Fracao:
def __init__(self, numerador, denominador):
self.numerador = numerador
self.denominador = denominador
# simplifica logo ao criar: toda Fracao nasce reduzida
self._simplificar()
def _simplificar(self):
# gcd = maior número que divide os dois sem deixar resto
divisor = gcd(self.numerador, self.denominador)
# //= divide e guarda de volta (divisão inteira, sem virar decimal)
self.numerador //= divisor
self.denominador //= divisor resolução · passo 2
Sete dunders, cada um devolvendo uma nova Fracao ou um booleano. A simplificação acontece de graça no __init__.
Fracao(n, d) — e a simplificação acontece automática dentro do __init__.__eq__ funciona porque ambas as frações já estão simplificadas: 2/4 e 1/2 viram a mesma representação interna. def __add__(self, outra):
# soma de frações: cruza numerador com denominador da outra
n = self.numerador * outra.denominador + outra.numerador * self.denominador
d = self.denominador * outra.denominador
# devolve uma Fracao NOVA (não altera self) — já sai simplificada
return Fracao(n, d)
def __sub__(self, outra):
# subtração: mesma lógica da soma, trocando + por -
n = self.numerador * outra.denominador - outra.numerador * self.denominador
d = self.denominador * outra.denominador
return Fracao(n, d)
def __mul__(self, outra):
# multiplicação: numerador x numerador, denominador x denominador
return Fracao(self.numerador * outra.numerador,
self.denominador * outra.denominador)
def __truediv__(self, outra):
# divisão: multiplica pela fração invertida (numerador vira denominador)
return Fracao(self.numerador * outra.denominador,
self.denominador * outra.numerador)
def __eq__(self, outra):
# iguais se numerador E denominador batem (ambas já simplificadas)
return (self.numerador == outra.numerador and
self.denominador == outra.denominador)
def __str__(self):
# define como a Fracao aparece no print: "1/2"
return f"{self.numerador}/{self.denominador}" resolução · passo 3
Mesmo código que você escreveria pra int — só que com Fracao no lugar.
metade + quarto, não metade.somar(quarto).Fracao(2, 4) == Fracao(1, 2) dá True graças à simplificação no __init__.from fracao import Fracao
metade = Fracao(1, 2)
quarto = Fracao(1, 4)
dois_tercos = Fracao(2, 3)
# graças aos dunders, usamos + - * / direto, como se fosse número
print(f"1/2 + 1/4 = {metade + quarto}")
print(f"1/2 - 1/4 = {metade - quarto}")
print(f"2/3 * 3/4 = {dois_tercos * Fracao(3, 4)}")
print(f"1/2 / 1/4 = {metade / quarto}")
# __eq__ em ação: 2/4 e 1/2 são iguais porque ambas viram 1/2 ao nascer
print(f"\n2/4 == 1/2 ? {Fracao(2, 4) == metade}")
print(f"6/8 == 3/4 ? {Fracao(6, 8) == Fracao(3, 4)}") resolução · saída
Quatro operações e duas comparações — todas com resultado já simplificado.
2/3 * 3/4 aparece como 1/2, não 6/12.$ python main.py
1/2 + 1/4 = 3/4
1/2 - 1/4 = 1/4
2/3 * 3/4 = 1/2
1/2 / 1/4 = 2/1
2/4 == 1/2 ? True
6/8 == 3/4 ? True encerramento
default, *args e **kwargs simulam o efeito. __add__, __eq__, __str__ e companhia. + chama __add__, == chama __eq__. Fracao que opera como número nativo — soma, subtrai, multiplica, divide, compara e imprime. exercícios · visão geral
Sete exercícios em quatro níveis. Faça pelo menos um de cada nível antes da próxima aula.
2 exercícios — Saudação flexível e Estatísticas com *args. Foco: simular sobrecarga de método.
2 exercícios — Vetor 3D e Produto. Foco: sobrecarregar operadores aritméticos e de igualdade.
2 exercícios — Dinheiro e Tempo. Foco: combinar dunders, comparação e validação.
1 exercício — Vetor N-dimensional. Foco: juntar as duas metades da aula (*args + operadores).
exercícios · nível fácil
Classe Pessoa com o método cumprimentar(nome="amigo", periodo="dia"). Sem argumentos, imprime "Bom dia, amigo!". Com cumprimentar("Ana", "noite"), imprime "Boa noite, Ana!". Foco: parâmetros com valor default.
Classe Analise com o método resumir(*numeros) que devolve a média, o menor e o maior valor. Deve funcionar com qualquer quantidade: resumir(10, 20) ou resumir(1, 2, 3, 4, 5). Foco: *args coletando vários valores numa tupla.
exercícios · nível médio
Classe Vetor3D com x, y, z. Implemente __add__, __sub__, __eq__ e __str__ — igual ao Vetor da aula, só com uma dimensão a mais. Foco: repetir o padrão de dunders aritméticos que vimos.
Classe Produto com codigo_barras, nome e preco. Dois produtos são iguais quando o código de barras é o mesmo (nome e preço podem diferir). Implemente __eq__ e teste se um produto está numa lista usando produto in lista. Foco: definir o que torna dois objetos "iguais".
exercícios · nível difícil
Classe Dinheiro com valor e moeda (ex.: "BRL"). Implemente __add__ e __sub__ entre dois objetos Dinheiro. A regra: se as moedas forem iguais, faz a conta normalmente; se forem diferentes, devolve None e imprime um aviso. Implemente também __str__ no formato "R$ 10.50 (BRL)". Foco: validar antes de operar — é a ponte pra aula 14, onde trocamos o aviso por raise.
Classe Tempo(horas, minutos, segundos). Implemente __add__ e __sub__ com normalização (60s viram 1min, 60min viram 1h), além de __eq__ e __str__ no formato 02:30:45. Teste: Tempo(0, 59, 50) + Tempo(0, 0, 20) deve dar 01:00:10.
exercícios · nível avançado
Um exercício só, mas que usa as duas metades da aula ao mesmo tempo.
Classe Vetor que aceita qualquer número de componentes: Vetor(1, 2) ou Vetor(1, 2, 3, 4) — use *componentes no __init__ (sobrecarga de método, parte 1!). Implemente __add__ e __sub__ somando componente a componente; se os vetores têm dimensões diferentes, devolva None e avise. Implemente __eq__ e __str__ no formato (1, 2, 3). Foco: *args + operadores na mesma classe.
resumo final
default, *args e **kwargs cobrem 99% das necessidades de sobrecarga. + chama __add__, == chama __eq__. __str__. Hoje vimos o TypeError que o Python lança quando não sabe somar nossos objetos. Na próxima aula, aprendemos a capturar esses erros, criar os nossos próprios — e construir programas que não morrem no primeiro tropeço.