Blog

__repr__ e __str__ em Python

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 !r nos campos.

  • __str__ é para saída que uma pessoa lê. É opcional, e cai para __repr__.

  • Contêineres e !r usam repr; print e f-strings usam str.

Dataclasses geram __repr__ por você, o que é um dos melhores motivos para usar uma. A parte 8 cobre essa escolha.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.