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.

Você vai aprender
  • O que são os métodos especiais (os "dunder") e quem chama cada um.
  • __str__: o que o print mostra.
  • __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 o sorted, o min e o max funcionarem.
  • __len__ e __add__: o len() 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.

Por que tantos sublinhados?

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ê escreveO 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 é o print, com o texto que ele devolveu.
print(cafe)
O print chama cafe.__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 !r dentro da f-string pede o repr do valor, em vez do str. Para um texto, isso põe as aspas: 'Café'. Assim o resultado fica igual ao código Produto('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 quemPara o usuário do programaPara quem programa (você, procurando um defeito)
Quem usaprint(x), str(x), f"{x}"repr(x), listas e dicionários, >>> x, f"{x!r}"
CostumeUm texto bonito: Café: R$ 18.90Parecido com o código: Produto('Café', 18.9)
Se faltarO 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 ==: em a == b, o self é a e o outro é b.
isinstance(outro, Produto)
"O outro lado é um Produto?" Se alguém comparar um produto com um texto, não faz sentido olhar outro.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 False no ==. 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 True ou False.
$ python3 com_eq.py
True
False
False
True
False
True
  • a == b agora é True: mesmo nome, mesmo preço.
  • a is b continua False: são iguais, mas não são o mesmo objeto. O is não usa o __eq__.
  • a == c é False (preços diferentes), e o != deu True sem você escrever nada para ele: o Python usa o contrário do __eq__.
  • a == "Café" deu False, sem erro, graças ao NotImplemented.
  • O in também usa o ==: b está na lista porque é igual ao a que 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, o self é o leite e o outro é 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.)

E o <=?

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 valer False no if quando 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 None no 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: o self é o carrinho da esquerda, o outro é 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 nomesorted(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

Um 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 o print de dentro do __str__, que rodou quando o print(cafe) chamou o método.
  • File "/home/ana/str_print.py", line 11: o erro foi na linha 11, o print(cafe), marcado pelos ^^^^^^.
  • TypeError: __str__ returned non-string (type NoneType): "o __str__ devolveu algo que não é texto (do tipo NoneType)". Como o método não tem return, ele devolve None (aula O Valor None), e o print de 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.

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

Um sublinhado só: nenhum erro, nenhum efeito
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.

Ordenar sem __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'
Escreveu __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.

Somar um carrinho com outra coisa
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étodoChamado porDeve 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, maxTrue/False ou NotImplemented
__le__, __gt__, __ge__<=, >, >=True/False
__len__(self)len(x), if x:um inteiro, 0 ou mais
__add__(self, outro)x + yum objeto novo ou NotImplemented
__hash__(self)conjuntos e chaves de dicionárioum inteiro: hash((campo1, campo2))
SintomaCausa provável
<__main__.Produto object at 0x…> no printFalta __str__/__repr__, ou o nome está com um sublinhado só
Endereços dentro de uma lista, mas o print do item sai bonitoTem __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:

def (self):
    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:

def (self):
    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 print chama o __str__, o len chama 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; um print no lugar dá __str__ returned non-string.
  • __eq__ decide o == (e, com ele, o != e o in). Com __eq__, escreva também __hash__ se o objeto for para um conjunto ou virar chave.
  • __lt__ basta para sorted, min e max. Para ordenar de vários jeitos, prefira key=.
  • Quando o outro lado não é do tipo esperado, devolva NotImplemented (um valor, não um erro).