__str__和__repr__有什么区别?

3696 投票
28 回答
985419 浏览
提问于 2025-04-15 14:23

在Python中,__str____repr__这两个东西有什么区别呢?

28 个回答

536

除非你特别去做一些事情,否则大多数类的输出结果都不会很有用:

>>> class Sic(object): pass
... 
>>> print(str(Sic()))
<__main__.Sic object at 0x8b7d0>
>>> print(repr(Sic()))
<__main__.Sic object at 0x8b7d0>

如你所见——没有区别,除了类和对象的 id 之外没有其他信息。如果你只重写其中一个:

>>> class Sic(object): 
...   def __repr__(self): return 'foo'
... 
>>> print(str(Sic()))
foo
>>> print(repr(Sic()))
foo
>>> class Sic(object):
...   def __str__(self): return 'foo'
... 
>>> print(str(Sic()))
foo
>>> print(repr(Sic()))
<__main__.Sic object at 0x2617f0>

如你所见,如果你重写了 __repr__,那么 __str__ 也会使用这个重写的内容,但反过来就不行。

还有一些重要的小知识:在一个内置容器上,__str__ 会使用其中的 __repr__,而不是 __str__,来表示它包含的项目。而且,尽管在常见文档中有相关的描述,几乎没有人会去做 __repr__ 的输出是一个可以被 eval 用来构建相同对象的字符串(这实在是太难了,而且不知道相关模块是怎么导入的,实际上让这变得几乎不可能)。

所以,我的建议是:专注于让 __str__ 变得比较容易被人理解,而 __repr__ 尽量做到明确清晰,即使这可能会妨碍你想要让 __repr__ 的返回值可以作为 eval 的输入这个难以实现的目标!

800

我的经验法则是:__repr__ 是给开发者用的,而 __str__ 是给用户用的。

3486

Alex Martelli总结得很好,但令人惊讶的是,他的总结有点简短。

首先,让我重申一下Alex帖子中的主要观点:

  • 默认实现是没用的(很难想象有什么默认实现是有用的,但确实如此)
  • __repr__的目标是要明确无误
  • __str__的目标是要易于阅读
  • 容器的__str__使用的是其包含对象的__repr__

默认实现是没用的

这让人有点意外,因为Python的默认设置通常都挺有用的。不过,在这种情况下,__repr__的默认实现如果像这样:

return "%s(%r)" % (self.__class__, self.__dict__)

那就太危险了(例如,如果对象互相引用,就很容易陷入无限递归)。所以Python选择了不提供默认实现。需要注意的是,有一个默认情况是成立的:如果定义了__repr__,而没有定义__str__,那么对象会表现得像是__str__=__repr__

简单来说:几乎每个你实现的对象都应该有一个能帮助理解对象的__repr__。实现__str__是可选的:如果你需要一个“美观打印”的功能(比如用于生成报告),可以实现它。

__repr__的目标是要明确无误

我直接说吧——我不相信调试工具。我不知道怎么用任何调试工具,也从来没有认真用过。此外,我认为调试工具的一个大缺陷就是它们的基本特性——我调试的大多数错误发生在很久很久以前,远在一个遥远的星系。这意味着我非常相信日志记录。日志记录是任何不错的“发射后忘记”的服务器系统的命脉。Python让日志记录变得简单:可能需要一些项目特定的包装,但你只需要一个

log(INFO, "I am in the weird function and a is", a, "and b is", b, "but I got a null C — using default", default_c)

但是你必须做最后一步——确保你实现的每个对象都有一个有用的repr,这样像上面的代码才能正常工作。这就是为什么“eval”这个概念会出现:如果你有足够的信息使得eval(repr(c))==c,那就意味着你知道关于c的一切。如果这很简单,至少在模糊的意义上,去做。如果不简单,确保你对c有足够的信息。我通常使用类似eval的格式:"MyClass(this=%r,that=%r)" % (self.this,self.that)。这并不意味着你真的可以构造MyClass,或者这些是正确的构造参数——但它是一个有用的形式来表达“这是你需要知道的关于这个实例的所有信息”。

注意:我上面用的是%r,而不是%s。你总是想在__repr__的实现中使用repr()(或者等价的%r格式化字符),否则你就违背了repr的目标。你想能够区分MyClass(3)MyClass("3")

__str__的目标是要易于阅读

具体来说,它并不是为了要明确无误——注意str(3)==str("3")。同样,如果你实现了一个IP抽象,让它的字符串看起来像192.168.1.1是完全可以的。当实现日期/时间抽象时,字符串可以是"2010/4/12 15:35:22"等等。目标是以用户而不是程序员想要阅读的方式来表示它。去掉无用的数字,假装成其他类——只要支持可读性,这就是一种改进。

容器的__str__使用包含对象的__repr__

这似乎有点令人惊讶,不是吗?确实有点,但如果使用它们的__str__,可读性会如何呢?

[moshe is, 3, hello
world, this is a list, oh I don't know, containing just 4 elements]

可读性就差多了。具体来说,容器中的字符串会很容易干扰它的字符串表示。在面对模糊性时,记住,Python抵制猜测的诱惑。如果你想在打印列表时获得上述行为,只需

print("[" + ", ".join(lst) + "]")

(你可能也能想出如何处理字典)。

总结

为你实现的任何类实现__repr__。这应该是自然而然的。如果你认为有一个字符串版本是有用的,并且更侧重于可读性,那么就实现__str__

撰写回答