`__repr__`函数与普通函数相比的意义是什么?

6

我正在尝试自学Python,在__repr__函数这里卡住了。尽管我已经阅读了很多关于__repr__的文章,包括Python文档,但我决定在这里提问。下面的代码解释了我的困惑。

class Point:

    def __init__(self,x,y):
            self.x, self.y = x,y

    def __repr__(self):
        return 'Point(x=%s, y=%s)'%(self.x, self.y)

    def print_class(self):
        return 'Point(x=%s, y=%s)'%(self.x, self.y)



p = Point(1,2)

print p
print p.print_class()


Point(x=1, y=2)
Point(x=1, y=2)

如果一个普通函数也可以执行相似的任务,那么__repr__相对于print_class()(在我的情况下是一个普通函数)函数有什么额外的优势呢?
3个回答

5
__repr__ 函数是被内部的 repr() 调用的。当你直接打印对象并且类没有定义 __str__() 时,会调用 repr()。详见 documentation -

object.__repr__(self)

由 repr() 内置函数和字符串转换(反引号)调用,计算对象的“官方”字符串表示形式。如果可能的话,这应该看起来像一个有效的Python表达式,可以在适当的环境中使用它来重新创建具有相同值的对象。如果这不可能,应该返回形如<...some useful description...> 的字符串。返回值必须是字符串对象。如果一个类定义了 __repr__() 但没有定义 __str__(),那么在需要实例的“非正式”字符串表示时也会使用 __repr__()

在您的情况下,对于print_class(),您必须在打印对象时明确调用该方法。但是,在__repr__()的情况下,它会被print内部调用。
当您混合不同的类/类型时,这特别有用。例如,让我们考虑一个可以包含数字和您的point类对象的列表,现在您想要打印列表的元素。
如果您没有定义__repr__()__str__(),则必须首先检查实例是否属于类型Point,如果是,则调用print_class(),否则直接打印数字。
但是,当您的类定义了__repr__()__str__()时,您可以直接调用列表的所有元素上的printprint语句会在内部处理正确的值。
例如,假设有一个具有print_class()方法但没有__repr__()__str__()的类,代码-
>>> class CA:
...     def __init__(self,x):
...             self.x = x
...     def print_class(self):
...             return self.x
...
>>> l = [1,2,3,CA(4),CA(5)]
>>> for i in l:
...     print(i)
...
1
2
3
<__main__.CA object at 0x00590F10>
<__main__.CA object at 0x005A5070>
SyntaxError: invalid syntax
>>> for i in l:
...     if isinstance(i, CA):
...             print(i.print_class())
...     else:
...             print(i)
...
1
2
3
4
5

如您所见,当我们在列表中混合数字和类型为CA的对象时,当我们只执行print(i)时,它没有打印出我们想要的内容。为了使其正常工作,我们必须检查i的类型并调用适当的方法(如第二个例子中所做)。
现在假设有一个实现__repr__()而不是print_class()的类 -
>>> class CA:
...     def __init__(self,x):
...             self.x = x
...     def __repr__(self):
...             return str(self.x)
...
>>>
>>> l = [1,2,3,CA(4),CA(5)]
>>> for i in l:
...     print(i)
...
1
2
3
4
5

正如您在第二个案例中所看到的,简单地打印输出起作用了,因为print内部首先调用__str__(),而由于它不存在,就回退到__repr__()
不仅如此,当我们执行str(list)时,内部会调用每个列表元素的__repr__()。例如:
第一种情况(没有__repr__())-
>>> str(l)
'[1, 2, 3, <__main__.CA object at 0x005AB3D0>, <__main__.CA object at 0x005AB410>]'

第二种情况(使用__repr__()) -

>>> str(l)
'[1, 2, 3, 4, 5]'

此外,在交互式解释器中,当您直接使用对象时,它会显示repr()函数的输出,例如 -
>>> class CA:
...     def __repr__(self):
...             return "CA instance"
...
>>>
>>> c = CA()
>>> c
CA instance

你能用代码示例解释一下你第三、四和五条(混合不同类/类型)评论的意思吗?感谢你的所有解释。 - jax
好的,让我来更新一下。 - Anand S Kumar
更新了答案并提供了详细的示例。 - Anand S Kumar
非常好的解释,感谢您的所有努力。 - jax

3
区别在于__repr__函数在特定情况下会被Python自动调用,且是预定义API的一部分,并具有特定要求。例如,如果在创建p对象后在交互式shell中仅输入p(而不是print p),它的__repr__将被调用。如果您未在p上定义__str__,则它也将用于print p。 (也就是说,您必须编写print p.print_class(),但不必编写print p.__repr__();Python会自动为您调用__repr__。)对于__repr__的要求在文档中有描述:

由内置的repr()函数和字符串转换(反引号)调用,以计算对象的“official”字符串表示形式。如果可能的话,这应该看起来像一个有效的Python表达式,可以使用它来重新创建具有相同值的对象(在适当的环境中)。如果不可能,则应返回形式如<...some useful description...>的字符串。

简而言之,如果您编写自己的名为print_class的方法,则可以使其执行您想要的任何操作并告诉人们如何使用它,因为这是您的API。如果使用__repr__,则应遵循Python API的惯例。根据上下文,任一种方法都可能有意义。

@BrenBam 那么这意味着,如果我不想使用__repr__,我可以编写一个等效的函数或方法吗?还是有一些任务是不能在没有__repr__函数的情况下完成或假设的? - jax
感谢您的解释。 - jax
@jax:你可以编写自己的方法。 - BrenBarn

0
它可以帮助您更高效地编写代码。即使您使用用户定义方法(如'print_class()')得到相同的结果,但您不需要通过 repr 方法键入'.print_class()'。

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