Python类的repr信息

8
作为Python 2对__repr__文档的解释所述:

如果可能的话,这个(即__repr__)应该看起来像一个有效的Python表达式,可以用来重新创建具有相同值的对象(在适当的环境下)。

那么为什么类的内置__repr__没有按照这个准则行事呢?

示例

>>> class A(object):
...   pass
>>> repr(A)
"<class 'A'>"

为了符合指南, 默认的 __repr__ 应该返回 "A",通常是 A.__name__。为什么它的行为不同?我相信这将非常容易实现。

编辑:'reproduction' 的范围

从回答中我可以看出,在讨论中并不清楚 repr 应该返回什么。在我看来,repr 函数应该返回一个字符串,使你能够复制对象:

  1. 在任意上下文中
  2. 自动地(即非手动地)。

附录1:看一个内置类的情况(摘自 this SO question):

>>> from datetime import date
>>>
>>> repr(date.today())        # calls date.today().__repr__()
'datetime.date(2009, 1, 16)'

显然,假定的上下文是你使用基本形式的导入,即import datetime,因为如果你尝试eval(repr(date.today()))datetime将不会被识别。所以重点是__repr__不需要从头开始表示对象。如果在社区已经商定的上下文中是无歧义的,例如使用直接模块的类型和函数,那就足够了。听起来合理,对吧? 广告.2.我认为,仅仅给出对象如何重建的印象是不够的,repr的目的是帮助调试。

结论

我希望从repr中得到的结果能够让我在eval中使用。对于类而言,我不想获取完整的代码以便从头开始重构类。相反,我想要在我的作用域中看到一个明确的类引用。 "Module.Class"就足够了。不冒犯,Python,但是"<class 'Module.Class'>"并不太合适。

实际上,它需要返回类似于type("A",(),{})的东西:能够生成等同于A的类的代码。正如mgilson所指出的那样,构建一个创建类的表达式(请注意,类通常是由class 语句定义的)是困难的。 - chepner
这实际上是非常棘手的实现,而且相当无用和令人困惑。 - Fred Foo
@larsmans 取决于你定义一个对象的深度。我不是在谈论从头开始复制一个类。请参见编辑。 - Janusz Lenar
@JanuszLenar:啊,是的,如果那是你想要的,实际上相当简单和合理。 - Fred Foo
4个回答

3
考虑一个稍微复杂一些的类:
class B(object):
    def __init__(self):
        self.foo=3

repr需要返回类似以下的内容

type("B", (object,), { "__init__": lambda self: setattr(self, "foo", 3) })

注意已经存在一个困难:并非所有由def语句定义的函数都能被转换成单个的lambda表达式。稍微改变一下B
class B(object):
    def __init__(self, x=2, y, **kwargs):
        print "in B.__init__"

你如何编写一个定义B.__init__表达式?您无法使用
lambda self: print "in B.__init__"

因为lambda表达式不能包含语句。对于这个简单的类来说,已经不可能编写一个单独的表达式来完全定义该类。


Python 3通过将print变为一个函数来解决这个问题,因此对于类的新repr没有任何障碍(尽管eval在Python 2中也可能可以使用 ;))。 - Fred Foo
我之前在 Python 3 中忽略了 print 函数,但还有其他语句(如 ifwhiletry 等)需要处理。eval 函数也需要一个表达式(或代码对象);你可能想到的是 exec。但 __repr__ 不应该成为一个强大的动态代码生成器;它只是一个方便的函数,如果可能的话应该尽可能可用。 - chepner
exec 是一个语句。使用 eval,您实际上可以设置变量:eval('locals().__setitem__("ham", "spam")')while 可以通过固定点运算符成为表达式,if 也有表达式形式... 我完全意识到我在说一些不好的话 :) - Fred Foo
2
我们将这个问题转化为“我如何用Lisp重写我的类?” :) - chepner
@chepner您对于lambda函数在通用解决方案中的不适用完全正确。正如Martijn所说,如果我要完整地表示整个类,我宁愿将其pickle化。我认为类上的repr的目的是确定类的名称。您同意吗? - Janusz Lenar

2
因为默认的__repr__无法知道用于创建类的语句。您引用的文档以如果可能的话开头。由于不可能以可重建的方式表示自定义类,因此使用了不同的格式,该格式遵循所有难以重新创建的内容的默认格式。如果repr(A)只返回'A',那将毫无意义。您并没有重新创建A,您只是在引用它。"type('A', (object,), {})"更接近于反映类构造函数,但是这会a) 令人困惑,对于不熟悉Python类是type实例的人来说,b) 永远无法准确地反映方法和属性。相比之下,将其与repr(type)repr(int)的输出进行比较,这些都遵循相同的模式。

1
值得注意的是,我们谈论的是类本身,而不是类的实例。 - NPE
请注意,此处的OP并未尝试表示实例...Python确实知道(至少在许多情况下)用于创建类的源代码。 - mgilson
没错。通过 type(name,subclasses,cls_dict) 创建一个类将会非常难以将所有信息收集到一个有意义的字符串中以便重新创建它。 - mgilson
@MartijnPieters 您说得对,repr 的结果可能很难阅读。但它的目的不是可读性;它旨在能够重现对象。因此,关于a) ,我不会介意。想要可读的字符串?使用 str - Janusz Lenar
@JanuszLenar:我想要一个字符串,让我更好地了解我正在调试的内容。如果你想要一个序列化格式,可以使用pickle或marshall。 - Martijn Pieters
@MartijnPieters 说得好。 我正在编辑问题,以明确对repr的要求。 - Janusz Lenar

1

我知道这是一个较旧的问题,但我找到了一种方法来解决它。

我所知道的唯一方法是使用元类,如下所示:

class A(object):
    secret = 'a'

    class _metaA(type):
        @classmethod
        def __repr__(cls):
            return "<Repr for A: secret:{}>".format(A.secret)
    __metaclass__ =_metaA

输出:

>>> A
<Repr for A: secret:a>

0

既然当类的定义不可用时,既不能使用"<class 'A'>"也不能使用"A"来重新创建该类,那么我认为这个问题是无意义的。


1
即使答案是“无法实现”,问题仍然有效。 - Janusz Lenar

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