Python Métodos Especiais
Você escreve print(5) e aparece 5. Escreve len("pão") e aparece 3. Escreve [1, 2] + [3] e aparece [1, 2, 3]. Mas experimente dar print num objeto da sua classe: sai um <__main__.Produto object at 0x7f…> que não diz nada. Os números, os textos e as listas sabem se mostrar, se comparar e se somar porque têm métodos especiais: métodos com nomes como __str__ e __eq__, que o Python chama sozinho na hora certa. Nesta aula, você vai dar esses poderes às suas classes.
- O que são os métodos especiais (os "dunder") e quem chama cada um.
__str__: o que oprintmostra.__repr__: o que aparece dentro de uma lista e no modo interativo, e a diferença para o__str__.__eq__: quando dois objetos são "iguais" para o==.__lt__: "menor que", para osorted, omine omaxfuncionarem.__len__e__add__: olen()e o+nos seus objetos.- Os erros reais:
__str__ returned non-string,'<' not supported between instances,unhashable type,unsupported operand type(s) for +.
A ideia: o Python pergunta ao objeto
Pense numa tomada. Qualquer aparelho que tenha o plugue certo funciona nela: a geladeira, o liquidificador, o carregador. A tomada não sabe nada de liquidificadores; ela só exige o plugue.
O print, o len, o == e o + são tomadas. Quando você escreve len(x), o Python não sabe o que é x; ele procura em x o "plugue" chamado __len__ e o chama. Se o objeto tem o plugue, funciona. Se não tem, dá erro (ou, em alguns casos, o Python usa um comportamento padrão, sem graça).
Dá para ver o plugue das coisas que você já conhece:
texto = "lojinha" print(len(texto)) print(texto.__len__()) print(3 + 4) print((3).__add__(4)) print([1, 2] + [3]) print([1, 2].__add__([3]))
$ python3 por_dentro.py 7 7 7 7 [1, 2, 3] [1, 2, 3]
len(texto) e texto.__len__() dão o mesmo resultado, porque o primeiro chama o segundo. O mesmo vale para 3 + 4 e (3).__add__(4). Você nunca vai escrever texto.__len__() no dia a dia (escreve len(texto), que é mais claro), mas agora sabe o que acontece por baixo.
São dois sublinhados antes e dois depois: __len__. Em inglês, "double underscore" virou o apelido dunder; por isso muita gente fala "métodos dunder" (ou "métodos mágicos"). Os sublinhados avisam: "este nome é especial, o Python chama sozinho". Você já conhece um: o __init__, que o Python chama quando o objeto nasce (aula __init__ e self).
| Você escreve | O Python chama |
|---|---|
Produto("Café", 18.9) | __init__ |
print(cafe), str(cafe), f"{cafe}" | __str__ |
repr(cafe), print([cafe]), >>> cafe | __repr__ |
a == b | __eq__ |
a < b, sorted(lista), min, max | __lt__ |
len(carrinho), if carrinho: | __len__ |
a + b | __add__ |
1. O problema: o print de um objeto
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
cafe = Produto("Café", 18.9)
print(cafe)
$ python3 sem_str.py <__main__.Produto object at 0x7fb4e1594590>
Sem ninguém ensinar, o Python mostra o comportamento padrão: "um objeto da classe Produto, no endereço de memória 0x7fb4e1594590" (a aula Classes e Objetos lê essa linha pedaço por pedaço). O número do endereço muda a cada execução; no seu computador e no editor do guia vai aparecer outro. Para a Ana, que quer ver o nome e o preço, isso não serve.
2. __str__: o texto para as pessoas
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __str__(self):
return f"{self.nome}: R$ {self.preco:.2f}"
cafe = Produto("Café", 18.9)
print(cafe)
print(str(cafe))
print(f"Hoje tem {cafe} na promoção")
print("Mais vendido: " + str(cafe))
- def __str__(self):
- O método especial do texto. Recebe só o
self(o objeto) e não recebe mais nada. - return f"…"
- Ele tem de devolver (
return) um texto. Não é ele que mostra na tela; quem mostra é oprint, com o texto que ele devolveu. - print(cafe)
- O
printchamacafe.__str__()e mostra o resultado. - str(cafe)
- Converte o objeto em texto, também pelo
__str__. Útil para juntar com+. - f"Hoje tem {cafe}…"
- A f-string também usa o
__str__para encaixar o objeto no texto.
$ python3 com_str.py Café: R$ 18.90 Café: R$ 18.90 Hoje tem Café: R$ 18.90 na promoção Mais vendido: Café: R$ 18.90
Um método só, e o objeto ficou apresentável em todos os lugares onde o Python precisa dele como texto.
Missão: rode e veja o <__main__.Produto object at …>. Depois, acrescente à classe um __str__ para que o print mostre Leite: R$ 5.49.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
leite = Produto("Leite", 5.49)
print(leite)
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __str__(self):
return f"{self.nome}: R$ {self.preco:.2f}"
leite = Produto("Leite", 5.49)
print(leite)
3. __repr__: o texto para quem programa
Com o __str__ pronto, a Ana pôs dois produtos numa lista e mandou mostrar a lista:
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __str__(self):
return f"{self.nome}: R$ {self.preco:.2f}"
cesta = [Produto("Café", 18.9), Produto("Leite", 5.49)]
print(cesta)
print(cesta[0])
$ python3 lista_str.py [<__main__.Produto object at 0x7fdd82990590>, <__main__.Produto object at 0x7fdd82978690>] Café: R$ 18.90
O produto sozinho saiu bonito, mas dentro da lista voltou o endereço. É que uma lista, para se mostrar, não usa o __str__ de cada item: usa o __repr__ ("representação"). É o mesmo motivo pelo qual print(["Café"]) mostra ['Café'], com as aspas: dentro da lista, você vê cada item do jeito que ele seria escrito no código.
O costume é fazer o __repr__ devolver algo parecido com o código que cria o objeto:
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __str__(self):
return f"{self.nome}: R$ {self.preco:.2f}"
def __repr__(self):
return f"Produto({self.nome!r}, {self.preco!r})"
cafe = Produto("Café", 18.9)
cesta = [cafe, Produto("Leite", 5.49)]
print(cafe)
print(repr(cafe))
print(cesta)
print(f"{cafe!r}")
- def __repr__(self):
- O método especial da representação. Também devolve um texto.
- {self.nome!r}
- O
!rdentro da f-string pede oreprdo valor, em vez dostr. Para um texto, isso põe as aspas:'Café'. Assim o resultado fica igual ao códigoProduto('Café', 18.9). - repr(cafe)
- Chama o
__repr__diretamente. - f"{cafe!r}"
- O mesmo
!r, agora aplicado ao produto.
$ python3 com_repr.py
Café: R$ 18.90
Produto('Café', 18.9)
[Produto('Café', 18.9), Produto('Leite', 5.49)]
Produto('Café', 18.9)
No modo interativo (aula O Modo Interativo) a diferença aparece de novo. Com a classe guardada no arquivo produto.py:
$ python3 -q
>>> from produto import Produto
>>> cafe = Produto("Café", 18.9)
>>> cafe
Produto('Café', 18.9)
>>> print(cafe)
Café: R$ 18.90
>>> [cafe]
[Produto('Café', 18.9)]
>>> "Café"
'Café'
>>> print("Café")
Café
>>> exit()
Digitar só o nome (>>> cafe) mostra o __repr__; o print mostra o __str__. Com o texto "Café" acontece a mesma coisa: com aspas no repr, sem aspas no print.
E se eu escrever só um dos dois?
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __repr__(self):
return f"Produto({self.nome!r}, {self.preco!r})"
cafe = Produto("Café", 18.9)
print(cafe)
print([cafe])
$ python3 so_repr.py
Produto('Café', 18.9)
[Produto('Café', 18.9)]
Sem __str__, o print usa o __repr__ no lugar. O contrário não acontece: com só o __str__, a lista mostra o endereço (você viu isso em lista_str.py). Por isso, se for escrever um só, escreva o __repr__.
__str__ | __repr__ | |
|---|---|---|
| Para quem | Para o usuário do programa | Para quem programa (você, procurando um defeito) |
| Quem usa | print(x), str(x), f"{x}" | repr(x), listas e dicionários, >>> x, f"{x!r}" |
| Costume | Um texto bonito: Café: R$ 18.90 | Parecido com o código: Produto('Café', 18.9) |
| Se faltar | O Python usa o __repr__ | O Python mostra <… object at 0x…> |
4. __eq__: quando dois objetos são iguais
A Ana cadastrou o café duas vezes, com o mesmo nome e o mesmo preço. Para ela, é o mesmo produto. Para o Python:
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
a = Produto("Café", 18.9)
b = Produto("Café", 18.9)
print(a == b)
print(a == a)
$ python3 sem_eq.py False True
Sem __eq__, o == só responde True quando os dois lados são o mesmo objeto, igual ao is (aula is e in). O Python não sabe que, para a lojinha, o que conta é o nome e o preço. Você ensina com o __eq__ (de equal, "igual"):
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __eq__(self, outro):
if not isinstance(outro, Produto):
return NotImplemented
return self.nome == outro.nome and self.preco == outro.preco
a = Produto("Café", 18.9)
b = Produto("Café", 18.9)
c = Produto("Café", 21.0)
print(a == b)
print(a is b)
print(a == c)
print(a != c)
print(a == "Café")
print(b in [c, a])
- def __eq__(self, outro):
- Recebe os dois lados do
==: ema == b, oselféae ooutroéb. - isinstance(outro, Produto)
- "O outro lado é um
Produto?" Se alguém comparar um produto com um texto, não faz sentido olharoutro.nome. - return NotImplemented
- Um valor especial que quer dizer "não sei comparar com isso". O Python então tenta o outro lado e, se ninguém souber, responde
Falseno==. Repare: éreturn NotImplemented, um valor, e não um erro. - return self.nome == outro.nome and …
- A regra da lojinha: mesmo nome e mesmo preço. Devolve
TrueouFalse.
$ python3 com_eq.py True False False True False True
a == bagora éTrue: mesmo nome, mesmo preço.a is bcontinuaFalse: são iguais, mas não são o mesmo objeto. Oisnão usa o__eq__.a == céFalse(preços diferentes), e o!=deuTruesem você escrever nada para ele: o Python usa o contrário do__eq__.a == "Café"deuFalse, sem erro, graças aoNotImplemented.- O
intambém usa o==:bestá na lista porque é igual aoaque está lá.
5. __lt__: "menor que", para ordenar
A Ana quer a cesta ordenada do produto mais barato para o mais caro. Tenta o sorted (aula Ordenando Listas):
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
cesta = [Produto("Café", 18.9), Produto("Leite", 5.49), Produto("Arroz", 27.5)]
print(sorted(cesta))
$ python3 sem_lt.py
Traceback (most recent call last):
File "/home/ana/sem_lt.py", line 8, in <module>
print(sorted(cesta))
~~~~~~^^^^^^^
TypeError: '<' not supported between instances of 'Produto' and 'Produto'
"O < não é suportado entre objetos Produto e Produto." Para ordenar, o sorted fica perguntando "este é menor que aquele?", com o <. E o Python não sabe o que é um produto ser "menor" que outro: mais barato? Nome antes no alfabeto? Quem decide é você, com o __lt__ (de less than, "menor que"):
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __repr__(self):
return f"Produto({self.nome!r}, {self.preco!r})"
def __lt__(self, outro):
return self.preco < outro.preco
cafe = Produto("Café", 18.9)
leite = Produto("Leite", 5.49)
arroz = Produto("Arroz", 27.5)
cesta = [cafe, leite, arroz]
print(leite < cafe)
print(cafe > leite)
print(sorted(cesta))
print(sorted(cesta, reverse=True))
print(min(cesta))
print(max(cesta))
- def __lt__(self, outro):
- Em
leite < cafe, oselfé o leite e ooutroé o café. - return self.preco < outro.preco
- A regra: um produto é "menor" que outro quando é mais barato.
$ python3 com_lt.py
True
True
[Produto('Leite', 5.49), Produto('Café', 18.9), Produto('Arroz', 27.5)]
[Produto('Arroz', 27.5), Produto('Café', 18.9), Produto('Leite', 5.49)]
Produto('Leite', 5.49)
Produto('Arroz', 27.5)
Com um método só, funcionaram o <, o sorted (nos dois sentidos), o min e o max. E até o cafe > leite: o Python percebe que "café maior que leite" é o mesmo que "leite menor que café" e usa o __lt__ do leite. (O __repr__ está ali só para a lista sair legível.)
<=?
Esse não vem de graça: leite <= cafe dá o mesmo TypeError, agora com '<='. Cada comparação tem o seu método: __le__ (<=), __gt__ (>), __ge__ (>=). Para ordenar, o __lt__ basta. Se você precisar de todas, a próxima aula, Dataclasses, mostra o order=True, que escreve todas sozinho.
Missão: o programa quer mostrar os produtos do mais barato para o mais caro, mas o sorted dá TypeError. Acrescente um __lt__ que compare os preços.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
cesta = [Produto("Café", 18.9), Produto("Leite", 5.49), Produto("Arroz", 27.5)]
for produto in sorted(cesta):
print(produto.nome)
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __lt__(self, outro):
return self.preco < outro.preco
cesta = [Produto("Café", 18.9), Produto("Leite", 5.49), Produto("Arroz", 27.5)]
for produto in sorted(cesta):
print(produto.nome)
6. __len__: o tamanho do objeto
A Ana fez uma classe Carrinho, que guarda os itens de uma compra numa lista. Faz sentido perguntar len(carrinho):
class Carrinho:
def __init__(self):
self.itens = []
def colocar(self, item):
self.itens.append(item)
def __len__(self):
return len(self.itens)
carrinho = Carrinho()
print(len(carrinho))
if not carrinho:
print("O carrinho está vazio.")
carrinho.colocar("café")
carrinho.colocar("leite")
print(len(carrinho))
if carrinho:
print(f"O carrinho tem {len(carrinho)} itens.")
- def __len__(self):
- Chamado pelo
len(carrinho). Tem de devolver um número inteiro, 0 ou maior. - return len(self.itens)
- O tamanho do carrinho é o tamanho da lista de itens dele.
- if not carrinho:
- Brinde: com
__len__, o objeto passa a valerFalsenoifquando o tamanho é 0, como uma lista vazia (aula Booleanos).
$ python3 len.py 0 O carrinho está vazio. 2 O carrinho tem 2 itens.
7. __add__: o + entre objetos
A Ana e a Bia vão juntar as compras num carrinho só. Com o __add__, isso vira um +:
class Carrinho:
def __init__(self, itens=None):
self.itens = list(itens) if itens else []
def __len__(self):
return len(self.itens)
def __str__(self):
return "Carrinho: " + ", ".join(self.itens)
def __add__(self, outro):
if not isinstance(outro, Carrinho):
return NotImplemented
return Carrinho(self.itens + outro.itens)
ana = Carrinho(["café", "leite"])
bia = Carrinho(["pão", "manteiga", "queijo"])
juntos = ana + bia
print(juntos)
print(len(juntos))
print(ana)
- itens=None
- O carrinho pode nascer vazio ou já com uma lista. (O
Noneno lugar de[]é de propósito; o porquê está na aula Valores Padrão.) - ", ".join(self.itens)
- Junta os itens num texto, separados por vírgula e espaço.
- def __add__(self, outro):
- Chamado por
ana + bia: oselfé o carrinho da esquerda, ooutroé o da direita. - return NotImplemented
- Se o lado direito não for um carrinho, avisa "não sei somar isso", e o Python dá o erro certo (veja em Quando dá errado).
- return Carrinho(self.itens + outro.itens)
- Devolve um carrinho novo, com os itens dos dois. Os carrinhos originais não mudam, como em
3 + 4, que não muda nem o 3 nem o 4.
$ python3 add.py Carrinho: café, leite, pão, manteiga, queijo 5 Carrinho: café, leite
O carrinho juntado tem os 5 itens, e o da Ana continua com os dois dela.
Formas diferentes de fazer a mesma coisa
Para ordenar, existe outro caminho que não mexe na classe: o key= do sorted, que diz por qual valor ordenar (aula lambda):
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __repr__(self):
return f"Produto({self.nome!r}, {self.preco!r})"
cesta = [Produto("Café", 18.9), Produto("Leite", 5.49), Produto("Arroz", 27.5)]
print(sorted(cesta, key=lambda p: p.preco))
print(sorted(cesta, key=lambda p: p.nome))
$ python3 key.py
[Produto('Leite', 5.49), Produto('Café', 18.9), Produto('Arroz', 27.5)]
[Produto('Arroz', 27.5), Produto('Café', 18.9), Produto('Leite', 5.49)]
Sem __lt__ nenhum, deu para ordenar por preço e por nome.
| Você quer… | Use |
|---|---|
| Um jeito "natural" e único de ordenar (o mais barato vem antes, sempre) | __lt__ na classe |
| Ordenar ora por preço, ora por nome | sorted(lista, key=…), sem mexer na classe |
| Mostrar o objeto para o usuário | __str__ |
| Ver o objeto ao procurar defeitos, ou dentro de listas | __repr__ (se for escrever um só, escreva este) |
| Uma classe que é "só dados" (nome, preço…) com tudo isso pronto | @dataclass (aula Dataclasses) |
Quando dá errado
print dentro do __str__, em vez de return
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __str__(self):
print(f"{self.nome}: R$ {self.preco:.2f}")
cafe = Produto("Café", 18.9)
print(cafe)
$ python3 str_print.py
Café: R$ 18.90
Traceback (most recent call last):
File "/home/ana/str_print.py", line 11, in <module>
print(cafe)
~~~~~^^^^^^
TypeError: __str__ returned non-string (type NoneType)
Confuso à primeira vista: o texto apareceu e deu erro. Linha por linha:
Café: R$ 18.90: foi oprintde dentro do__str__, que rodou quando oprint(cafe)chamou o método.File "/home/ana/str_print.py", line 11: o erro foi na linha 11, oprint(cafe), marcado pelos^^^^^^.TypeError: __str__ returned non-string (type NoneType): "o__str__devolveu algo que não é texto (do tipoNoneType)". Como o método não temreturn, ele devolveNone(aula O Valor None), e oprintde fora não tem o que mostrar.
Como consertar: o __str__ devolve o texto: return f"{self.nome}: R$ {self.preco:.2f}". Quem mostra é o print.
__str__ devolvendo um número
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __str__(self):
return self.preco
cafe = Produto("Café", 18.9)
print(cafe)
$ python3 str_numero.py
Traceback (most recent call last):
File "/home/ana/str_numero.py", line 11, in <module>
print(cafe)
~~~~~^^^^^^
TypeError: __str__ returned non-string (type float)
O mesmo erro, agora com type float: o método devolveu o número 18.9, não um texto. O __str__ (e o __repr__) têm de devolver texto. Como consertar: return str(self.preco) ou uma f-string.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def _str_(self):
return f"{self.nome}: R$ {self.preco:.2f}"
cafe = Produto("Café", 18.9)
print(cafe)
$ python3 um_sublinhado.py <__main__.Produto object at 0x7f2da2d50590>
Nenhum erro, mas o print ignorou o método. _str_, com um sublinhado de cada lado, é só um método comum com nome esquisito; o Python procura exatamente __str__, com dois. O mesmo vale para __init__, __eq__ e todos os outros. Como consertar: conte os sublinhados: dois antes, dois depois.
__lt__
Você já viu na seção 5: sorted(cesta) com produtos sem __lt__ dá TypeError: '<' not supported between instances of 'Produto' and 'Produto'. Como consertar: escreva o __lt__ ou use sorted(cesta, key=lambda p: p.preco). E se o erro falar de '<=', é porque o código usou <=, que precisa do __le__:
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __lt__(self, outro):
return self.preco < outro.preco
cafe = Produto("Café", 18.9)
leite = Produto("Leite", 5.49)
print(leite <= cafe)
$ python3 menor_igual.py
Traceback (most recent call last):
File "/home/ana/menor_igual.py", line 12, in <module>
print(leite <= cafe)
^^^^^^^^^^^^^
TypeError: '<=' not supported between instances of 'Produto' and 'Produto'
__eq__ e o objeto não entra mais num conjunto
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __eq__(self, outro):
return self.nome == outro.nome and self.preco == outro.preco
cafe = Produto("Café", 18.9)
vistos = {cafe}
$ python3 unhashable.py
Traceback (most recent call last):
File "/home/ana/unhashable.py", line 11, in <module>
vistos = {cafe}
^^^^^^
TypeError: cannot use 'Produto' as a set element (unhashable type: 'Produto')
"Não dá para usar Produto como elemento de um conjunto (tipo não hashable)." Conjuntos (aula Conjuntos) e chaves de dicionário precisam de um hash: um número de "etiqueta" que o Python calcula para achar o objeto depressa. Objetos iguais têm de ter a mesma etiqueta. Quando você escreve o seu __eq__, o Python não tem como saber qual etiqueta combina com a sua regra de igualdade e, por segurança, tira a etiqueta da classe.
Como consertar: se você precisa pôr os objetos num conjunto ou usá-los como chave, escreva também um __hash__ que use os mesmos dados do __eq__:
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def __eq__(self, outro):
return self.nome == outro.nome and self.preco == outro.preco
def __hash__(self):
return hash((self.nome, self.preco))
a = Produto("Café", 18.9)
b = Produto("Café", 18.9)
print(len({a, b}))
$ python3 hash_ok.py 1
Agora os dois cafés iguais viram um item só no conjunto. Cuidado: depois disso, não mude o nome nem o preço de um objeto que já está dentro de um conjunto (ele ficaria com a etiqueta velha). A aula Dataclasses mostra o frozen=True, que resolve isso proibindo a mudança.
class Carrinho:
def __init__(self, itens=None):
self.itens = list(itens) if itens else []
def __add__(self, outro):
if not isinstance(outro, Carrinho):
return NotImplemented
return Carrinho(self.itens + outro.itens)
ana = Carrinho(["café", "leite"])
print(ana + "pão")
$ python3 add_erro.py
Traceback (most recent call last):
File "/home/ana/add_erro.py", line 12, in <module>
print(ana + "pão")
~~~~^~~~~~~
TypeError: unsupported operand type(s) for +: 'Carrinho' and 'str'
"Tipos não suportados para o +: Carrinho e str." Foi o NotImplemented trabalhando: o __add__ avisou que não sabe somar um carrinho com um texto, o texto também não sabia, e o Python deu um erro claro, com o ^ embaixo do +. Sem o isinstance, o erro seria outro, mais confuso, lá de dentro do método ('str' object has no attribute 'itens'). Como consertar: some carrinho com carrinho (ana + Carrinho(["pão"])), ou crie um método próprio para pôr um item.
Referência rápida
| Método | Chamado por | Deve devolver |
|---|---|---|
__init__(self, …) | Classe(…) | nada (None) |
__str__(self) | print(x), str(x), f"{x}" | um texto |
__repr__(self) | repr(x), listas, >>> x, f"{x!r}" | um texto (de preferência, parecido com o código) |
__eq__(self, outro) | x == y (e !=, in) | True/False ou NotImplemented |
__lt__(self, outro) | x < y, sorted, min, max | True/False ou NotImplemented |
__le__, __gt__, __ge__ | <=, >, >= | True/False |
__len__(self) | len(x), if x: | um inteiro, 0 ou mais |
__add__(self, outro) | x + y | um objeto novo ou NotImplemented |
__hash__(self) | conjuntos e chaves de dicionário | um inteiro: hash((campo1, campo2)) |
| Sintoma | Causa provável |
|---|---|
<__main__.Produto object at 0x…> no print | Falta __str__/__repr__, ou o nome está com um sublinhado só |
Endereços dentro de uma lista, mas o print do item sai bonito | Tem __str__, falta __repr__ |
__str__ returned non-string (type NoneType) | print no lugar do return dentro do __str__ |
__str__ returned non-string (type float) | O __str__ devolveu um número |
'<' not supported between instances of … | sorted/min/max sem __lt__ |
unhashable type: 'Produto' | Tem __eq__ e falta __hash__ |
unsupported operand type(s) for + | Sem __add__, ou somando com um tipo que o __add__ recusa |
Exercícios
1. O texto do print
Complete o nome do método que o print usa:
return f"{self.nome}: R$ {self.preco:.2f}"
2. O tamanho da turma
A classe Turma guarda os alunos na lista self.alunos. Complete para que len(turma) funcione:
return
3. Uma classe tem só o __str__. O que aparece em print([cafe, leite])?
4. Sem __eq__, Produto("Café", 18.9) == Produto("Café", 18.9) dá:
5. Qual método especial o sorted(lista_de_produtos) precisa encontrar nos produtos?
6. O que o __add__ deve devolver quando não sabe somar com o tipo recebido?
Missão final: os carrinhos da Ana e da Bia
Missão: o programa quer juntar os carrinhos com + e contar os itens com len, mas a classe ainda não sabe fazer isso. Escreva o __len__ (o número de itens) e o __add__ (um carrinho novo com os itens dos dois).
class Carrinho:
def __init__(self, itens):
self.itens = list(itens)
def __str__(self):
return "Carrinho: " + ", ".join(self.itens)
# escreva aqui o __len__
# escreva aqui o __add__
ana = Carrinho(["café", "leite"])
bia = Carrinho(["pão"])
juntos = ana + bia
print(juntos)
print(len(juntos), "itens")
class Carrinho:
def __init__(self, itens):
self.itens = list(itens)
def __str__(self):
return "Carrinho: " + ", ".join(self.itens)
def __len__(self):
return len(self.itens)
def __add__(self, outro):
if not isinstance(outro, Carrinho):
return NotImplemented
return Carrinho(self.itens + outro.itens)
ana = Carrinho(["café", "leite"])
bia = Carrinho(["pão"])
juntos = ana + bia
print(juntos)
print(len(juntos), "itens")
Resumo
- Métodos especiais ("dunder") têm dois sublinhados de cada lado e são chamados pelo Python: o
printchama o__str__, olenchama o__len__, o+chama o__add__. __str__é o texto para as pessoas (print, f-string);__repr__é o texto para quem programa (listas, modo interativo). Sem__str__, o Python usa o__repr__; se for escrever um só, escreva o__repr__.- Os dois devolvem texto com
return; umprintno lugar dá__str__ returned non-string. __eq__decide o==(e, com ele, o!=e oin). Com__eq__, escreva também__hash__se o objeto for para um conjunto ou virar chave.__lt__basta parasorted,minemax. Para ordenar de vários jeitos, prefirakey=.- Quando o outro lado não é do tipo esperado, devolva
NotImplemented(um valor, não um erro).