__str__和__repr__有什么区别?

3667
28个回答

3464

Alex Martelli总结得很好, 但令人惊讶的是,他太简洁了。

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

  • 默认实现是无用的(很难想象有哪个实现不是这样的,但是是的)
  • __repr__的目标是无歧义的
  • __str__的目标是可读性强
  • 容器的__str__使用包含对象的__repr__

默认实现是无用的

这主要是因为Python的默认值通常相当有用。然而,在这种情况下,如果__repr__的默认值像这样运行:

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

这样做可能会太危险了(例如,如果对象相互引用,则很容易进入无限递归)。因此,Python选择了一种妥协方式。请注意,有一个默认值为True:如果定义了__repr__,而没有定义__str__,则对象将表现得好像__str__=__repr__

简单来说,这意味着:几乎每个你实现的对象都应该有一个可用于理解该对象的功能性__repr__。实现__str__是可选的:如果需要“漂亮的打印”功能(例如,由报告生成器使用),可以这样做。

__repr__的目标是不含歧义的

让我直截了当地说吧——我不相信调试器。我真的不知道如何使用任何调试器,也从未认真使用过。此外,我认为调试器的主要问题在于它们的基本性质——我调试的大多数故障都发生在很久很久以前,在一个遥远的星系中。这意味着,我确实像宗教狂热者一样相信日志记录。日志记录是任何体面的“fire-and-forget”服务器系统的命脉。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__,那么它会有多可读呢?

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

不是很好。具体来说,容器中的字符串会发现它太容易干扰其字符串表示形式了。面对模棱两可的情况,请记住,Python抵制猜测的诱惑。如果您想在打印列表时获得上述行为,只需

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

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

概述

对于您实现的任何类,请实现__repr__。这应该是第二天性的事情。如果您认为具有以可读性为导向的字符串版本会很有用,则实现__str__


364
我非常不同意你的观点,认为调试不是解决问题的好方法。在开发阶段应该使用调试器和/或日志记录,在生产环境中则应该使用日志记录。使用调试器可以看到问题发生时的所有情况,获得全面的视角。除非将所有事情都记录下来,否则无法达到这个效果。而且,如果将所有事情都记录下来,你就必须在一堆数据中寻找自己想要的内容。 - Samuel
41
好的回答(除了关于不使用调试器的那一点)。我只想在这里添加一个链接,它是关于 Python 3 中 strunicode 的另一个问题的问答,对于已经切换到 Python 3 的人来说可能会有所帮助。链接地址为:https://dev59.com/2HM_5IYBdhLWcg3wmkau。 - ThatAintWorking
30
关于使用调试器和不使用调试器:不要形成这样的固定观念。在某些应用程序中,调试可能并不现实,通常是因为涉及到实时性,或者当您的代码仅在几乎无法访问或没有控制台的平台上远程执行时。在大多数其他情况下,停在异常处进行调查或设置断点会更快,因为您不必浏览成千上万行的日志记录(这会使磁盘混乱并拖慢应用程序)。最后,在某些情况下无法记录日志,例如在嵌入式设备上,则调试器也是您的朋友。 - RedGlyph
6
关于调试和日志记录,它们都很有用。如果一个 bug 可以重现,调试会更简单。如果 bug 是随机的,日志记录是必不可少的。 - Marco Sulla
7
Python 3的最新版本有一个小更新:您可以使用f-strings,并且仍然可以调用嵌入数据的__repr__,只需添加!r即可:您可以将"MyClass(this=%r,that=%r)" % (self.this,self.that)替换为f"MyClass(this={self.this!r},that={self.that!r})"。否则,感谢这篇优秀的文章! - joanis
显示剩余4条评论

795
我的经验法则是:__repr__ 面向开发者,__str__ 面向客户。

25
这是真的,因为对于 obj = uuid.uuid1(),obj.str() 是"2d7fc7f0-7706-11e9-94ae-0242ac110002",obj.repr() 是"UUID('2d7fc7f0-7706-11e9-94ae-0242ac110002')"。开发人员需要(value + origin),而客户只需要一个数值,他们不在乎它是如何得到的! - Naren Yellavula
14
在这里,“_customer_”并不一定指最终用户,而是指客户或对象的用户。因此,如果它是一个SDK,那么SDK开发人员将使用__str__来使普通开发人员能够读取对象。另一方面,__repr__是为SDK开发人员自己准备的。 - Shiplu Mokaddim
@NarenYellavula 如果你向客户公开UUID,那么你可能在做一些错误的事情。 - Mark Ransom
@AbdessabourMtk 它们过于复杂,而且没有防止打错的保护措施。也许在某些情况下,比如作为QR码的一部分,它们还可以接受。 - Mark Ransom
1
@Mark 客户也可能是技术人员,例如 GParted 会为分区暴露 UUID(如 截图 所示)。如果您需要一个适用于非技术客户的示例:d = datetime.date.today() str: 2023-05-20 repr: datetime.date(2023, 5, 20) - wjandrea

531

除非你特别采取行动来确保不同,否则大多数类都没有有用的结果:

>>> 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的输入的模糊不可达目标!


53
在我的单元测试中,我总是检查eval(repr(foo))是否会被评估为等于foo的对象。你说得对,在我的测试案例之外它可能不起作用,因为我不知道该模块是如何被引入的,但至少这可以确保它在某些可预测的上下文中运行正常。我认为这是一种评估__repr__结果是否足够明确的好方法。在单元测试中执行此操作还有助于确保__repr__遵循类的更改。 - Steven T. Snyder
7
我一直尝试确保在正确的上下文中,eval(repr(spam)) == spam 要么成立,要么 eval(repr(spam)) 抛出 SyntaxError 以避免混淆。(这个规则对于内置函数和大多数标准库都 几乎 适用,除了例如递归列表,在这种情况下 a=[]; a.append(a); print(eval(repr(a))) 将会返回 [[Ellipses]]…)当然,我并不打算真正地使用 eval(repr(spam)),除非是在单元测试时检查代码是否正常……但有时我确实会将 repr(spam) 复制粘贴到交互式会话中。 - abarnert
为什么容器(列表、元组)不使用 __str__ 来代替 __repr__ 来处理每个元素?这对我来说似乎是错误的,因为我在我的对象中实现了可读的 __str__,但当它成为列表的一部分时,我看到的是更丑陋的 __repr__ - SuperGeo
1
@SuperGeo 其他答案已经覆盖了这个问题:容器中使用元素repr,因为[1, 2, 3] != ["1", "2, 3"] - mtraceur
2
@abarnert:对于自定义的class Spameval(repr(spam)) == spam也需要实现Spam.__eq__,是吗?默认情况下,object.__eq__使用is([文档](https://docs.python.org/3/reference/datamodel.html#object.__eq__))。 - djvg
显示剩余3条评论

216

简而言之,__repr__ 的目标是无歧义的,而 __str__ 的目的是易于阅读。

以下是一个很好的例子:

>>> import datetime
>>> today = datetime.datetime.now()
>>> str(today)
'2012-03-14 09:21:58.130922'
>>> repr(today)
'datetime.datetime(2012, 3, 14, 9, 21, 58, 130922)'

阅读repr的文档:

repr(object)

返回一个包含对象可打印表示的字符串。这与转换(反引号)产生的值相同。 有时候能够访问此操作作为普通函数可能会很有用。 对于许多类型,此函数尝试返回一个字符串, 当传递给 eval() 时,将生成具有相同值的对象,否则表示是一个带有对象类型名称和附加信息(通常包括对象名称和地址)的尖括号括起来的字符串。 类可以通过定义一个 __repr__() 方法来控制其实例的此函数返回结果。

这是str的文档:

str(object='')

返回一个包含对象可打印表示的字符串。对于字符串,它返回字符串本身。与 repr(object) 的不同之处在于, str(object) 并不总是尝试返回可接受的字符串 以便传递给 eval();其目标是返回一个可打印的字符串。如果没有给出参数,则返回空字符串 ''


2
这里的可打印字符串是什么意思?你能解释一下吗? - Vicrobot
2
在“bitoffdev”和@deadly的上述示例基础上,我们可以看到__str__是为最终用户设计的,因为它只提供了可读的字符串,而__repr__是为开发人员设计的,因为它不仅提供了值,还提供了类型。如果您正在寻找面试答案,那么这将是完美的。 - PSK0007

215

__repr__

Python对象的表示通常可以通过eval函数将其转换回该对象。

__str__

是以文本形式呈现该对象的任何内容。

例如:

>>> s="""w'o"w"""
>>> repr(s)
'\'w\\\'o"w\''
>>> str(s)
'w\'o"w'
>>> eval(str(s))==s
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    w'o"w
       ^
SyntaxError: EOL while scanning single-quoted string
>>> eval(repr(s))==s
True

3
repr():用于在字符串中创建类似构造函数的表达式,以便eval()可以从此字符串表示中重新构造对象。str():用于创建包含对象可打印表示的字符串。 - Tarun Kumar

177
在Python中,__str__(读作“dunder(双下划线)string”)和__repr__(读作“dunder-repper”(表示“representation”))都是返回基于对象状态的字符串的特殊方法。
如果__str__不存在,__repr__提供备用行为。
因此,首先应编写一个__repr__,它允许您从返回的字符串重新实例化等效对象,例如使用eval或在Python shell中逐字符输入。
随后,可以在需要时为实例编写__str__,以提供用户可读的字符串表示形式。

__str__

如果您打印一个对象,或者将其传递给formatstr.formatstr,那么如果定义了__str__方法,将调用该方法,否则将使用__repr____repr__方法由内置函数repr调用,并在Python shell中评估返回对象的表达式时回显。
由于它为__str__提供了备用选项,如果只能编写一个方法,请从__repr__开始。
以下是关于repr的内置帮助信息:
repr(...)
    repr(object) -> string
    
    Return the canonical string representation of the object.
    For most object types, eval(repr(object)) == object.

那就是说,对于大多数对象来说,如果你输入由repr打印出来的内容,你应该能够创建一个等价的对象。但这不是默认的实现方式。

__repr__的默认实现

默认的对象__repr__是(C Python source)类似于以下内容:

def __repr__(self):
    return '<{0}.{1} object at {2}>'.format(
      type(self).__module__, type(self).__qualname__, hex(id(self)))

这意味着默认情况下,你将打印出对象所属的模块、类名以及其在内存中的十六进制表示,例如:
<__main__.Foo object at 0x7f80665abdd0>

这些信息并不是很有用,但是没有办法推导出如何准确地创建给定实例的规范表示,这总比没有好,至少告诉我们如何在内存中唯一标识它。
__repr__ 如何有用?
让我们看看它可以有多有用,使用 Python shell 和 datetime 对象。首先,我们需要导入 datetime 模块:
import datetime

如果我们在shell中调用datetime.now,我们将看到我们需要重新创建一个等效的datetime对象的所有内容。这是由datetime __repr__创建的。
>>> datetime.datetime.now()
datetime.datetime(2015, 1, 24, 20, 5, 36, 491180)

如果我们打印一个datetime对象,我们会看到一个漂亮的人类可读(实际上是ISO)格式。这是通过datetime的__str__方法实现的。
>>> print(datetime.datetime.now())
2015-01-24 20:05:44.977951

重新创建我们丢失的对象是一件很简单的事情,只需从__repr__输出中复制粘贴,然后打印出来,我们就能得到与其他对象相同的人类可读输出。
>>> the_past = datetime.datetime(2015, 1, 24, 20, 5, 36, 491180)
>>> print(the_past)
2015-01-24 20:05:36.491180

如何实现它们?
在开发过程中,如果可能的话,您希望能够复制对象的相同状态。例如,这就是datetime对象如何定义__repr__(Python源代码链接)。由于需要复制这样一个对象的所有属性,所以它相当复杂。
def __repr__(self):
    """Convert to formal string, for repr()."""
    L = [self._year, self._month, self._day,  # These are never zero
         self._hour, self._minute, self._second, self._microsecond]
    if L[-1] == 0:
        del L[-1]
    if L[-1] == 0:
        del L[-1]
    s = "%s.%s(%s)" % (self.__class__.__module__,
                       self.__class__.__qualname__,
                       ", ".join(map(str, L)))
    if self._tzinfo is not None:
        assert s[-1:] == ")"
        s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
    if self._fold:
        assert s[-1:] == ")"
        s = s[:-1] + ", fold=1)"
    return s

如果你想让你的对象具有更易读的表示形式,你可以实现__str__方法。下面是datetime对象(Python源代码)如何实现__str__方法的示例,它很容易实现,因为它已经有一个函数可以以ISO格式显示它:
def __str__(self):
    "Convert to string, for str()."
    return self.isoformat(sep=' ')

设置 __repr__ = __str__

这是对另一个回答的批评,该回答建议设置 __repr__ = __str__

设置 __repr__ = __str__ 是愚蠢的 - __repr____str__ 的备用选项,而且一个为开发人员在调试时使用的 __repr__ 应该在编写 __str__ 之前编写。

只有在需要对象的文本表示时才需要 __str__

结论

为你编写的对象定义 __repr__,这样你和其他开发人员在开发过程中使用时就有一个可重现的示例。当你需要一个可读的字符串表示时,定义 __str__


1
难道不应该是类似于 type(obj).__qualname__ 这样的东西吗? - Solomon Ucko
@SolomonUcko 是的,在Python 3中似乎是这样 - 我一直在寻找实现这一点的源代码,当我整理好信息后,我会更新我的答案。 - Russia Must Remove Putin
这个回答对初学者更有帮助。解释得非常好!! - Gokul nath
我已将 self.__module__ 更改为 type(self).__module__(因为例如 3 没有 __module__),并将 type(self).__name__ 更改为 type(self).__qualname__(因为例如对于 class A: class B: passrepr(A.B()) 返回的就是这个)。 - Géry Ogam

49

在Hans Petter Langtangen的书Python scripting for computational science第358页上,明确指出:

  • __repr__方法旨在提供对象的完整字符串表示;
  • __str__方法则是返回易于打印的漂亮字符串。

所以,我更愿意从用户的角度理解它们:

  • repr = reproduce(复制)
  • str = string(表示)

尽管这是我在学习Python时所犯下的一个误解。

同一页还给出了一个简单但好的例子:

Example

In [38]: str('s')
Out[38]: 's'

In [39]: repr('s')
Out[39]: "'s'"

In [40]: eval(str('s'))
Traceback (most recent call last):

  File "<ipython-input-40-abd46c0c43e7>", line 1, in <module>
    eval(str('s'))

  File "<string>", line 1, in <module>

NameError: name 's' is not defined


In [41]: eval(repr('s'))
Out[41]: 's'

它在第351页。 - jiten
10
repr 称为“reproduce”有点误导人,更好的理解应该是它表示了一个对象,而不是复制了一个对象。 - NelsonGon
1
@NelsonGon他并不完全错,文档中说-“这应该看起来像一个有效的Python表达式,可以用来重新创建一个具有相同值的对象(在适当的环境下)。如果这不可能,应该返回一个形式为<...一些有用的描述...>的字符串。”所以从某种意义上来说,将其视为复制对象是有道理的,正如许多人指出的使用eval的用法。 - undefined

48
除了已经给出的所有答案,我想再补充几点:
1. 当你在交互式Python控制台上仅仅写下对象的名称并按下回车键时,会调用`__repr__()`方法。
2. 当你在print语句中使用对象时,会调用`__str__()`方法。
3. 如果缺少`__str__()`方法,则print语句和任何使用`str()`的函数都会调用对象的`__repr__()`方法。
4. 当容器的`__str__()`方法被调用时,会执行其包含元素的`__repr__()`方法。
5. 在`__str__()`方法中调用`str()`可能会导致递归而没有基本情况,并在递归深度达到最大时出错。
6. `__repr__()`方法可以调用`repr()`,它会自动尝试避免无限递归,用`...`替换已经表示的对象。

"repr()会自动尝试避免无限递归,用...替换已经表示过的对象" — 你从哪里得到这个信息的?它并不会这样做。也许你在想reprlib.recursive_repr - undefined

38

简而言之:

Differences between str()/repr() and __str__()/__repr__()

当涉及到细枝末节的问题时,这个问题类似于询问str()repr()内置函数之间的区别。我将用自己的话来描述这些区别(这意味着我可能会大量借鉴《核心Python编程》的内容,请原谅我)。

str()repr()的基本工作是相同的:它们的目标是返回Python对象的字符串表示。不同之处在于它们返回的字符串表示的类型

  • str()__str__()返回一个可打印的字符串表示,即人类可读/供人类消费的字符串。
  • repr()__repr__()返回一个字符串表示,它是一个有效的Python表达式,即一个可以传递给eval()或在Python shell中输入而不会出错的对象。
例如,让我们给变量 x 赋一个字符串,给变量 y 赋一个整数,并简单地显示它们的可读字符串版本。
>>> x, y = 'foo', 123
>>> str(x), str(y)
('foo', '123')

我们可以将引号内的内容在两种情况下直接输入Python解释器吗?让我们试试看:
>>> 123
123
>>> foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined

显然你可以为一个int这样做,但不一定适用于str。同样地,虽然我可以将'123'传递给eval(),但对于'foo'却不起作用。
>>> eval('123')
123
>>> eval('foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'foo' is not defined

所以这告诉你Python shell只是对你给它的内容进行eval()。现在,让我们对这两个表达式进行repr(),看看我们得到什么。更具体地说,将其输出转储到解释器中(我们稍后会解释这一点的原因):
>>> repr(x), repr(y)
("'foo'", '123')
>>> 123
123
>>> 'foo'
'foo'

哇,它们两个都能工作?那是因为'foo'虽然是该字符串的可打印字符串表示形式,但它不能被求值,但"'foo'"可以。123是一个有效的Python int,可以通过str()或repr()调用。当我们使用这些调用eval()时会发生什么?
>>> eval('123')
123
>>> eval("'foo'")
'foo'

它有效是因为123'foo'都是有效的Python对象。另一个关键点是,有时候它们返回相同的东西(相同的字符串表示),但并非总是如此。(是的,是的,我可以创建一个变量foo,使eval()有效,但这不是重点。) 关于这两对的更多事实 有时候,str()repr()会被隐式地调用,这意味着它们会代表用户被调用:当用户使用print时,即使他们没有显式地调用str(),在对象显示之前,也会代表他们进行这样的调用。
在Python shell(交互式解释器)中,如果你在>>>提示符下输入一个变量并按下RETURN键,解释器会隐式地调用repr()来显示该对象的结果。
要将str()repr()__str__()__repr__()连接起来,需要意识到对内置函数的调用,即str(x)repr(y)会导致调用它们对象的相应特殊方法:x.__str__()y.__repr__() 通过为你的Python类实现__str__()__repr__(),你可以重载内置函数(str()repr()),允许你的类的实例被传递给str()repr()。当进行这样的调用时,它们会转而调用类的__str__()__repr__()(参见#3)。

请不要发布文字图片。如果您想使用的话,Stack Exchange现在支持表格格式 - undefined
"repr()__repr__()返回一个对象的字符串表示,该表示是一个有效的Python表达式" — 并非总是如此。是的,这是一个目标,但有些对象无法有意义地表示,比如object,例如:object()<object object at 0x7f4aa8b38f50>。我知道其他答案已经涵盖了这一点,你可能也意识到了,但我觉得奇怪的是你在这个长篇解释中没有提到它。 - undefined
@wjandrea,你似乎很擅长编辑帖子,所以你可以编辑帖子并将整个表格从图片复制到Markdown中。并不是每个人都有像你一样的时间可以浪费,目标是传达信息,而这张图片做得非常好。 - undefined

17

简单来说:

__str__ 用于展示对象的字符串表示,以便其他人可以轻松阅读

__repr__ 用于展示对象的字符串表示。

假设我想创建一个 Fraction 类,其中分数的字符串表示为“(1/2)”,而对象(Fraction 类)的表示形式为“Fraction(1,2)”

那么我们可以创建一个简单的 Fraction 类:

class Fraction:
    def __init__(self, num, den):
        self.__num = num
        self.__den = den

    def __str__(self):
        return '(' + str(self.__num) + '/' + str(self.__den) + ')'

    def __repr__(self):
        return 'Fraction (' + str(self.__num) + ',' + str(self.__den) + ')'



f = Fraction(1,2)
print('I want to represent the Fraction STRING as ' + str(f)) # (1/2)
print('I want to represent the Fraction OBJECT as ', repr(f)) # Fraction (1,2)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接