Esta parte vem em terceiro, antes de properties e herança, porque uma classe que você não consegue ler num traceback custa tempo todo santo dia em que ela existir.
O padrão é inútil
class Order:
def __init__(self, id_, total):
self.id, self.total = id_, total
o = Order(17, 249.5)
print(o)
print([o, o])
Ele imprime algo como:
<__main__.Order object at 0x7daca41360f0>
[<__main__.Order object at 0x7daca41360f0>, <__main__.Order object at 0x7daca41360f0>]
O endereço vai ser diferente na sua máquina, e esse é justamente o problema: a única coisa que o Python consegue te dizer é onde o objeto mora na memória, que nunca é o que você queria saber.
__repr__ resolve em todo lugar de uma vez
class Order2:
def __init__(self, id_, total):
self.id, self.total = id_, total
def __repr__(self):
return f"Order2(id={self.id!r}, total={self.total!r})"
o2 = Order2(17, 249.5)
print(o2)
print([o2, o2])
Ele imprime:
Order2(id=17, total=249.5)
[Order2(id=17, total=249.5), Order2(id=17, total=249.5)]
Repare na segunda linha. Contêineres usam repr no conteúdo deles, e é por isso que uma lista de objetos sem __repr__ é um paredão de endereços.
A convenção é devolver algo que você poderia colar de volta no Python para reconstruir o objeto. Order2(id=17, total=249.5) atende isso. Use !r nos valores dentro dele, ou um campo de texto perde as aspas e deixa de ser válido.
str é para quem lê, repr é para você
class Order3:
def __init__(self, id_, total):
self.id, self.total = id_, total
def __repr__(self):
return f"Order3(id={self.id!r}, total={self.total!r})"
def __str__(self):
return f"Order #{self.id} — {self.total:,.2f}"
o3 = Order3(17, 249.5)
print(str(o3))
print(repr(o3))
print(f"{o3}")
print(f"{o3!r}")
Ele imprime:
Order #17 — 249.50
Order3(id=17, total=249.5)
Order #17 — 249.50
Order3(id=17, total=249.5)
print() e f-strings usam str. O REPL, os contêineres e o !r usam repr.
Defina __repr__ primeiro, __str__ só se precisar
O fallback funciona num sentido só. Defina apenas __repr__ e o str() usa ele:
class Minimal:
def __init__(self, v): self.v = v
def __repr__(self): return f"Minimal({self.v!r})"
m = Minimal(1)
print(str(m), '|', repr(m))
Ele imprime:
Minimal(1) | Minimal(1)
Defina só __str__ e o repr() continua te dando o endereço. Então o __repr__ é o que sempre ganha o lugar dele; o __str__ vale a pena quando o objeto é mostrado para uma pessoa.
Onde isso realmente compensa
Mensagens de erro e tracebacks:
def charge(order):
raise ValueError(f"cannot charge {order!r}")
try:
charge(o3)
except ValueError as err:
print(err)
try:
charge(o)
except ValueError as err:
print(str(err)[:46] + '...')
Ele imprime:
cannot charge Order3(id=17, total=249.5)
cannot charge <__main__.Order object at 0x7dac...
O primeiro te diz qual pedido falhou. O segundo te diz que um objeto falhou e te manda de volta para reproduzir. Ao longo de um ano, essa diferença são horas.
O que lembrar
-
Escreva
__repr__sempre. É a melhoria de depuração mais barata que existe. -
Faça ele parecer a chamada que reconstruiria o objeto, e use
!rnos campos. -
__str__é para saída que uma pessoa lê. É opcional, e cai para__repr__. -
Contêineres e
!rusamrepr;printe f-strings usamstr.
Dataclasses geram __repr__ por você, o que é um dos melhores motivos para usar uma. A parte 8 cobre essa escolha.