Blog

Python 中的 __repr__ 和 __str__

没有 __repr__ 的类,从写出来那天起就在浪费你的时间。repr 给你自己看,str 给读者看,而两者中只有一个有像样的回退。

这篇排在第三,放在属性和继承之前,因为在回溯信息(traceback)里看不懂的类,只要还留在代码里,每天都在耗你的时间。

默认输出毫无用处

class Order:
    def __init__(self, id_, total):
        self.id, self.total = id_, total

o = Order(17, 249.5)
print(o)
print([o, o])

输出类似这样:

<__main__.Order object at 0x7daca41360f0>
[<__main__.Order object at 0x7daca41360f0>, <__main__.Order object at 0x7daca41360f0>]

在你的机器上地址会不一样,而问题恰恰在这里:Python 唯一能告诉你的,是对象在内存里的位置,而这从来不是你想知道的。

一个 __repr__ 处处生效

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])

输出:

Order2(id=17, total=249.5)
[Order2(id=17, total=249.5), Order2(id=17, total=249.5)]

注意第二行。容器对里面的元素用的是 repr,所以一个装着没有 __repr__ 的对象的列表,打印出来就是一堆地址。

约定是返回一段能粘回 Python 里重建这个对象的文本Order2(id=17, total=249.5) 就符合。里面的值要加 !r,否则字符串字段会丢掉引号,结果就不再是合法的代码。

str 给读者看,repr 给你看

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}")

输出:

Order #17 — 249.50
Order3(id=17, total=249.5)
Order #17 — 249.50
Order3(id=17, total=249.5)

print() 和 f-string 用 str。REPL、容器和 !rrepr

先写 __repr__,需要时再写 __str__

回退只有一个方向。只定义 __repr__str() 会用它:

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))

输出:

Minimal(1) | Minimal(1)

只定义 __str__ 的话,repr() 给你的还是地址。所以 __repr__ 永远值得写;__str__ 只在对象要展示给人看时才值得加。

真正见效的地方

错误信息和回溯:

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] + '...')

输出:

cannot charge Order3(id=17, total=249.5)
cannot charge <__main__.Order object at 0x7dac...

第一行告诉你是哪个订单出了问题。第二行只告诉你某个对象出了问题,你还得回头去复现。一年下来,这点差别就是好几个小时。

要点

  • 永远写 __repr__。这是成本最低的调试改进。
  • 让它看起来像能重建对象的那次调用,字段上用 !r
  • __str__ 用于给人读的输出。它是可选的,没定义时会回退到 __repr__
  • 容器和 !rreprprint 和 f-string 用 str

数据类(dataclass)会自动生成 __repr__,这是选用它的好理由之一。第 8 篇会讲怎么选。

这篇文章对你有帮助吗?

点一颗爱心来评分!

平均评分 0 / 5. 投票总数: 0

还没有人投票。来做第一个评分的人吧。