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
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
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
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
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
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
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
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
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
__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
__ — 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
__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
* (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
+, ==, 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
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 12, 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.