Guia de estudo

20/05/2026 · Aula 11

Sobrecarga: Métodos e Operadores

Quando o mesmo nome faz coisas diferentes.

Programação Orientada a Objetos com Python
Tecnologia em Análise e Desenvolvimento de Sistemas

Parâmetros opcionais *args e **kwargs Dunder methods Operator overloading

plano de voo

O que vamos aprender hoje

01

O Buraco

Por que escrever dois métodos com o mesmo nome em Python não funciona.

02

A Saída

Como parâmetros default, *args e **kwargs simulam sobrecarga.

03

Os Dunder

Como ensinar seus objetos a responder a +, -, == e companhia.

04

Na Prática

Classe Fracao com operações matemáticas — soma, subtração, multiplicação e divisão.

de onde paramos · 5 dias atrás

Classes abstratas — o que vimos na aula 10

  • 🏛️ ABC + @abstractmethod transformam um método de sugestão em obrigação — subclasses são forçadas a implementar.
  • 🛑 O ganho: classes incompletas nem chegam a nascer — TypeError antes do bug virar produção.
  • 🏗️ Convivência: métodos abstratos (o que varia) e métodos concretos (o que se repete) moram na mesma classe-mãe.

memória muscular

O contrato em ação — aula 10

Toda subclasse de Pagamento era obrigada a implementar processar e gerar_recibo. Esquecer um? TypeError na hora.

📜
O contrato ficava no código — não no comentário. ABC + @abstractmethod garantiam que ninguém saísse sem implementar.
🎭
Polimorfismo formalizado: uma só função processava qualquer tipo de pagamento, confiando no contrato.
pagamentos_aula_10.py
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

Pra esquentar a cabeça

  • Em Java ou C#, posso escrever 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

A segunda definição apaga a primeira

  • 🪦 O Python vê dois def somar e simplesmente regrava o atributo da classe. O segundo apaga o primeiro — como se você fizesse x = 1; x = 2.
  • 🚩 Resultado: chamar c.somar(1, 2) dispara TypeError: missing 1 required positional argument: 'c'. Você perdeu uma versão sem perceber.

É exatamente o problema de hoje (parte 1)

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

Em outras linguagens, isso é normal

  • 🧰 Lá: o compilador olha a chamada, casa com a assinatura correta, executa. Várias versões coexistem na mesma classe porque cada uma é única — Java, C#, C++ funcionam assim.
  • 🐍 Em Python: método é só um nome no dicionário da classe. Sem assinatura gravada, sem compilador escolhendo — só o último def permanece.

Por que essa diferença existe

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

O Python sobrescreve em silêncio

Veja o que acontece quando alguém vindo de Java tenta replicar o padrão de sobrecarga em Python.

🪦
O segundo def apaga o primeiro. Só a versão de 3 argumentos sobrevive — chamar com 2 quebra.
🤫
Nenhum aviso do Python na hora de definir. O erro só aparece quando você chama com argumentos a menos.
tentativa_sobrecarga.py
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

Em vez de várias versões, uma versão flexível

  • 🎚️ A ideia: escrever um método com parâmetros flexíveis — alguns obrigatórios, outros opcionais, e uma rota de escape pra "quantos vierem".
  • 🧰 Três ferramentas: valores default, *args e **kwargs. Cada uma resolve um tipo de variação.

Mudança de mentalidade

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

Antes do código, conhece quem entra em cena

  • 🎯 Parâmetro 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).

Os asteriscos têm significado

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

default — quando alguns argumentos são opcionais

Pro caso clássico 'às vezes 2 args, às vezes 3'. Você dá um valor padrão e quem chamar decide se passa.

🎯
O c=0 diz: "se não me passar c, use 0". Resolve a sobrecarga clássica (mesma operação, +1 argumento opcional).
É a solução padrão. Use sempre que possível — simples, explícita e cobre 90% dos casos.
default.py
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

*args — quando a quantidade de argumentos varia

Pro caso 'quero somar 2, ou 5, ou 50 números'. O * empacota tudo numa tupla.

📦
Dentro do método, valores é uma tupla. sum(valores) faz o resto.
💡
O nome args é convenção — você pode chamar de *numeros, *itens, *qualquer_coisa. O que importa é o *.
args.py
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

**kwargs — quando os argumentos têm nome

Pro caso 'aceito várias opções e cada uma tem nome próprio'. Dois ** empacotam num dicionário.

🏷️
Dentro do método, opcoes é um dicionário. Cada chave=valor da chamada vira uma entrada.
🌍
Bibliotecas como pandas e matplotlib usam **kwargs o tempo todo — é como elas aceitam dezenas de opções sem listar cada uma na assinatura.
kwargs.py
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

Uma calculadora que aceita 2, 3 ou N argumentos

Três versões da mesma ideia — escolha a que faz sentido pro seu caso. Em projetos reais, default cobre 90% dos casos.

🎯
Versão 1 resolve o caso original (somar com 2 OU 3 argumentos) com a menor mudança possível.
📦
Versão 2 generaliza: *valores aceita zero, dois ou cinquenta argumentos sem mudar nada na assinatura.
calculadora_flexivel.py
# 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

Os três jeitos em uso

Mesmo código de chamada que travava antes — agora cada um funciona.

O mesmo nome de métodosomar — atende vários casos. É isso que outras linguagens chamariam de sobrecarga.
💡
Em Python, é uma abordagem única com parâmetros flexíveis — não várias definições escolhidas pelo compilador.
uso_calculadora.py
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

Isso se chama simular sobrecarga

  • 🪞 Sobrecarga clássica: várias versões do mesmo método, escolhidas em compilação. Linguagens estáticas, com assinaturas rígidas.
  • 🐍 Em Python: uma versão com parâmetros flexíveis (default, *args, **kwargs) cobre os mesmos casos sem precisar de compilador escolhendo.

Regra de ouro

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

Pedido no balcão

  • "Um café, por favor": chamada simples, com o mínimo. O atendente sabe entregar usando o padrão da casa (sem açúcar, copo médio, leite normal).
  • 🧋 "Um café com dois açúcares, leite vegetal e canela": mesma função do atendente, mais informações no pedido. Ele se adapta sem precisar virar outra pessoa.

A ponte com Python

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

Você já viu isso funcionar

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.

🧩
Cada tipo nativo decide o que +, == e len() significam. É a mesma classe de mecanismo (__add__, __eq__, __len__) — mas com regras próprias de cada tipo.
🎯
Pista pro que vem aí: o Python permite que VOCÊ faça igual com suas classes. Implementou __add__? Acaba de ensinar seu objeto a entender +.
tipos_nativos.py
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

E quando a gente quer somar objetos?

  • 🔍 Lembra dos nomes com dois sublinhados? __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.
  • 🧩 São contratos da linguagem: nomes que o Python procura automaticamente em resposta a operadores (+, ==), funções built-in (len(...), print(...)) e eventos do ciclo de vida do objeto.

A pergunta de hoje (parte 2)

Como ensinar nossos objetos a se comportarem como tipos nativos — somáveis, comparáveis, imprimíveis?

o problema em código

Vetor + Vetor: o que o Python responde

Sem nenhuma configuração extra, tentar somar dois objetos da nossa classe trava com mensagem clara.

🛑
O + trava com TypeError — o Python avisa que não sabe somar nossas classes.
🤫
O == é pior: aceita silenciosamente. Compara identidade (mesmo objeto na memória?), não conteúdo. False mesmo com x e y iguais.
vetor_sem_operadores.py
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

E se a gente ensinasse o Python?

  • 📖 A ideia: existem métodos com nomes especiais — começam e terminam com __ — que o Python chama automaticamente quando vê certos operadores.
  • O efeito: implementa __add__ na sua classe e, daí em diante, v1 + v2 funciona — Python chama v1.__add__(v2) nos bastidores.

Não tem mágica

É um protocolo: nomes acordados entre você e a linguagem. + chama __add__. == chama __eq__. print(x) chama __str__.

você já conhece dois

Dunder methods não são novidade

  • 🏗️ __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".

Etimologia rápida

Dunder = double underscore. O nome vem dos dois sublinhados que envolvem o método. Pronuncia-se "dander".

a solução em código

Vetor que entende +, -, == e print

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.

🧬
Cada dunder recebe 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.
vetor_com_operadores.py
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

Agora o Vetor se comporta como tipo nativo

O mesmo código que travava antes — agora roda limpo. E o objeto se comporta como você esperaria de um número.

A leitura fica natural: v1 + v2 conta a intenção sem o leitor precisar abrir o método somar().
🎁
Bônus do __eq__: agora Vetor(1, 2) == Vetor(1, 2)True — compara conteúdo, não endereço de memória.
uso_vetor.py
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

E que outros operadores fariam sentido?

  • 🤔 Quais operadores fariam sentido natural? Pensem em * (multiplicar por escalar?), /, < (qual seria "menor"?), len(v) (o tamanho do vetor?), -v (o vetor oposto?).
  • E quais NÃO fariam sentido? Pensem em casos onde a operação seria ambígua, estranha pro leitor, ou simplesmente sem significado matemático.

Depois discutimos no quadro

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

O mapa: operador → método

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

Tomada universal

  • 🔌 O padrão (+, ==, print) é o formato da tomada. Quem chama o operador é o eletricista — não precisa abrir o aparelho.
  • 📱 Implementar __add__ é colocar o plugue certo no seu aparelho. Daí em diante, ele encaixa em qualquer tomada do tipo +.

Outra forma de pensar

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

Como sobrecarregar um operador, passo a passo

Toda sobrecarga de operador segue o mesmo esqueleto. Decora esse padrão e o resto é variação.

1

Identificar o operador

Quer responder a +, ==, len()? Consulte a tabela e ache o nome do dunder.

2

Definir o método com o nome exato

Os nomes são fixos: __add__, __eq__, __lt__. Erro de digitação = Python ignora o método.

3

Receber self e outro

Operadores binários recebem dois objetos: o da esquerda (self) e o da direita (outro).

4

Retornar — não modificar

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

Operador não modifica — devolve

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__.

🚨
Com a versão errada: v3 = v1 + v2 parece OK, mas v1 agora vale v1 + v2. O original sumiu silenciosamente.
🧪
Princípio: operador é função pura. Recebe, calcula, devolve um novo objeto. Não altera o estado dos operandos — exatamente como 1 + 2 não muda o 1.
anti_padrao.py
# 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

A diferença que os dunders fazem

❌ Sem operadores

  • 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...>

✅ Com dunders

  • v1 + v2 — leitura natural
  • O operador chama __add__ automaticamente
  • v1 == v2 compara conteúdo, como esperado
  • print(v1) mostra Vetor(1, 2)

uso com critério

Quando usar (e quando não usar)

  • Use quando: a operação tem sentido matemático ou intuitivo. Vetor, Fração, Dinheiro, Tempo, Ponto — somam, subtraem, comparam.
  • Evite quando: a operação não é óbvia. Cliente + Cliente não faz sentido — use um método com nome explícito, tipo juntar(outro_cliente).

Princípio prático

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__

Mexeu em __eq__? Mexa em __hash__ também

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.

⚖️
A regra: se dois objetos são iguais (__eq__), eles precisam ter a mesma "impressão digital" (__hash__). Por isso os dois usam o MESMO atributo — aqui, o codigo.
🧭
Não trave nisso agora: hash é um mecanismo interno que vocês vão entender melhor mais pra frente. Quando precisar, copie esse padrãozinho. O porquê completo está no guia de estudo.
produto_eq_hash.py
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

Os termos de hoje, em uma frase cada

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

Exercício ao Vivo: Classe Fração

Em duplas. 30 minutos. Objetivo: construir uma Fracao que se comporta como um número — soma, subtrai, multiplica, divide, compara e imprime bonito.

1

Estrutura básica

__init__(numerador, denominador) + método auxiliar _simplificar() que reduz a fração usando gcd do módulo math.

2

As quatro operações

Implementar __add__, __sub__, __mul__ e __truediv__. Toda operação devolve uma nova Fracao já simplificada.

3

Comparação e representação

__eq__ (duas frações são iguais quando simplificadas batem) e __str__ (formato 3/4 no print).

4

Testar

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

Estrutura e simplificação

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.
🔒
O _ em _simplificar sinaliza método interno — convenção de encapsulamento que vimos na aula 04.
📚
Não precisa decorar 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.
fracao.py
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

As quatro operações + igualdade + print

Sete dunders, cada um devolvendo uma nova Fracao ou um booleano. A simplificação acontece de graça no __init__.

🎯
Cada operação faz a aritmética crua, devolve Fracao(n, d) — e a simplificação acontece automática dentro do __init__.
🪞
O __eq__ funciona porque ambas as frações já estão simplificadas: 2/4 e 1/2 viram a mesma representação interna.
fracao.py (continuação)
    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

Usando a Fração

Mesmo código que você escreveria pra int — só que com Fracao no lugar.

🧬
A leitura do código fica idêntica à da matemática — metade + quarto, não metade.somar(quarto).
🎁
Ganho extra: Fracao(2, 4) == Fracao(1, 2)True graças à simplificação no __init__.
main.py
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

Saída esperada no terminal

Quatro operações e duas comparações — todas com resultado já simplificado.

As operações devolvem frações já em forma reduzida: 2/3 * 3/4 aparece como 1/2, não 6/12.
🎯
As comparações funcionam mesmo com numeradores/denominadores diferentes — porque ambas as frações são simplificadas antes.
terminal
$ 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

O que dominamos hoje

  • Entendemos por que o Python não tem sobrecarga clássica — e que isso não é limitação, é só um jeito diferente de resolver.
  • A solução parte 1: parâmetros default, *args e **kwargs simulam o efeito.
  • A regra: uma versão flexível em vez de várias versões fixas.
  • A solução parte 2: dunder methods — __add__, __eq__, __str__ e companhia.
  • A ponte com a linguagem: o Python chama o dunder certo pra cada operador. + chama __add__, == chama __eq__.
  • Na prática: classe Fracao que opera como número nativo — soma, subtrai, multiplica, divide, compara e imprime.

exercícios · visão geral

Para praticar em casa

Sete exercícios em quatro níveis. Faça pelo menos um de cada nível antes da próxima aula.

01

Fáceis

2 exercícios — Saudação flexível e Estatísticas com *args. Foco: simular sobrecarga de método.

02

Médios

2 exercícios — Vetor 3D e Produto. Foco: sobrecarregar operadores aritméticos e de igualdade.

03

Difíceis

2 exercícios — Dinheiro e Tempo. Foco: combinar dunders, comparação e validação.

04

Avançados

1 exercício — Vetor N-dimensional. Foco: juntar as duas metades da aula (*args + operadores).

exercícios · nível fácil

Para fixar simulação de sobrecarga

01

Saudação flexível

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.

02

Estatísticas com *args

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

Sobrecarregar operadores em classes reais

03

Vetor 3D

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.

04

Produto igual por código

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

Para ir além

05

Classe Dinheiro

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.

06

Classe Tempo

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

O desafio que junta tudo

Um exercício só, mas que usa as duas metades da aula ao mesmo tempo.

07

Vetor N-dimensional

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

Em uma linha cada

  • Sobrecarga de método em Python é simulada — uma versão flexível em vez de várias.
  • default, *args e **kwargs cobrem 99% das necessidades de sobrecarga.
  • Operadores chamam dunders+ chama __add__, == chama __eq__.
  • Você já vinha sobrecarregando operador desde a aula 03 com __str__.
  • Use operator overloading quando o operador faz sentido óbvio na sua classe — não force a barra.
próxima aula

Aula 12 · 22/05

Tratamento de Exceções

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.

try / except / finally Exceções customizadas Hierarquia de erros Boas práticas de erro