28 个回答
除非你特别去做一些事情,否则大多数类的输出结果都不会很有用:
>>> 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
的输入这个难以实现的目标!
我的经验法则是:__repr__
是给开发者用的,而 __str__
是给用户用的。
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__
。