在Python中,__str__
和__repr__
的区别是什么?
Alex Martelli对Python中的__str__
和__repr__
进行了总结,但令人惊讶的是,他的回答过于简洁。
首先,让我重申一下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抽象,让它的str看起来像是192.168.1.1是完全可以的。当你实现一个日期/时间抽象时,str可以是"2010/4/12 15:35:22",等等。目标是以一种用户(而不是程序员)愿意阅读的方式来表示它。删除无用的数字,假装成其他类——只要它支持可读性,就是改进。
容器的__str__
使用包含的对象的__repr__
这似乎令人惊讶,不是吗?它有点,但是如果它使用它们的__str__
会是什么样子呢?
不太多。具体来说,容器中的字符串会很容易干扰其字符串表示形式。面对歧义时,请记住,Python抵制猜测的诱惑。如果您想在打印列表时获得上述行为,只需使用以下代码:
print("[" + ", ".join(lst) + "]")
(您可能还可以想出如何处理字典)。
总结
对于您实现的任何类,请实现__repr__
方法。这应该是自然而然的事情。如果您认为拥有一个倾向于可读性的字符串版本是有用的,请实现__str__
方法。