__repr__的默认实现可以基于__init__函数进行改进吗?

3
根据我的理解,__repr__用于表示对象的开发者/解释器友好的表现形式,可能是一段有效的Python代码,当传递给eval()时,可以重新创建一个相同的对象。
来自Python文档:
object.repr(self)
由repr()内置函数和字符串转换(反引号)调用,计算对象的“官方”字符串表示。如果可能的话,这应该看起来像一个有效的Python表达式,可以在适当的环境中用于重新创建具有相同值的对象。如果不可能,应返回形式为<...some useful description...>的字符串。返回值必须是一个字符串对象。如果一个类定义了repr()但没有定义str(),那么在需要该类的实例的“非正式”字符串表示时也会使用repr()。
链接: https://docs.python.org/2/reference/datamodel.html#object.repr

E.g:

class tie(object):
    def __init__(self, color):
        self.color = color
t = tie('green')
repr(t) # prints <tie object at 0x10fdc4c10>

# can the default implementation be improved to tie(color='green')
# based on the parameters passed in the __init__ function

改变这个实现会面临哪些挑战,除了向后兼容/现有行为方面?

1
@DerteTrdelnik 我也有点困惑...我认为他们在问什么阻止Python开发人员提供一个不同的默认实现__repr __(),例如基于类的__init __()签名。 - glibdud
2
问题是为什么Python解释器提供默认实现为<tie object at 0x10fdc4c10>,而不是稍微努力一点生成tie(color='green')。是否存在任何缺陷,导致通过查看__init__参数来生成表示将完全错误? - 6harat
实现为 def __repr__(self): return "tie(color='{}')".format(self.color) 有什么问题? - stovfl
id(x)的表示在许多情况下都非常有用,例如当您关心对象标识或仅类型时。如果不是这样,修改也很容易。我并不认为对于我们90%的人来说,一个更花哨的系统会有多大用处。您还可以轻松地返回str(self.dict)。 - JL Peyret
你可以这样做 return "{0.__class__.__name__}({})".format(self, ", ".join('{}={}'.format(k,v) for k,v in self.__dict__.items())。它可以处理像可变对象一样的东西,但这可能与构造函数参数无关。 - Bakuriu
显示剩余3条评论
2个回答

4

默认的repr基于对象创建方式会带来低效和混乱。

大小

__init__参数必须通过复制存储在对象中,这会使对象变得臃肿。

并非所有对象都只是将这些值简单地复制到自己身上。

例如:

class GreedyMan:
    def __init__(self, coins):
        self.most_valuable_coin = max(coins)

你需要在这里保存整个硬币收藏品。

类是可变的

一个使用0xff00ff初始化的Color类可以在其生命周期内变成另一种颜色。

class Color:
    def __init__(self, color):
        self.color = color

    def dilute(self, factor):
        self.color = self.color * factor
dilute 可以改变类的状态,所以你不再拥有 0xff00ff 的颜色,而是其他的颜色。如果某些东西抛出异常,例如“我不接受非红色——提供的颜色('red')”,那么程序员需要进行一些调试,直到注意到有人使用 dilute 得到了奇怪的颜色。
因此,为什么不打印整个状态——所有的类属性?
结果可能是巨大/无限的。图形可以有包含其他节点甚至循环的节点。
class ChainPart:
    def __init__(self, parent):
        self.parent = parent
        self.children = []

    def add_child(self, child):
        self.children.append(child)

a=ChainPart(None)
b=ChainPart(a)
b.add_child(a)

链条部分 b 打印出所有内容时,需要递归打印 a 部分,而 a 部分又要打印部分 b ... 以此类推。

因此,解决这些问题最明显的方法是保持 repr 简单,并允许程序员在对象类中使用自定义的 __repr__ 方法来更改它。


当我提出这个问题时,我的视野有点狭窄,我对__init__的想象仅限于一个普通对象,其中构造函数只会接收对象属性作为参数,并且参数名称将与对象属性名称保持相同。现在我感觉自己应该给自己的问题投反对票。>_< - 6harat
像这样的对象是存在的 - 命名元组,它们的repr就像你所描述的那样,它们是不可变的容器 - 它们不会改变它们被初始化时的参数。 - Derte Trdelnik

1

这基本上就是pickle试图实现的目标。其想法是,由于内存中的对象是一个图形,如果您重新访问该图形,则可以重构该对象。

Pickle生成一系列指令以重构数据,但原则上,它也可以直接生成Python代码。

虽然我将考虑所有问题,但Pickling在分布式应用程序中被广泛用于系统需要从一个进程传输数据到另一个进程的情况。

__repr__方法在调试时非常方便,因为您希望通过将对象复制并粘贴到提示符中来重构对象。

让我们看看pickling无法工作的地方:

  1. 有些对象实际上代表真实的对象!例如,网络套接字标识管理实际网络连接的内核资源。即使您可以重新创建这些资源,也不会有另一台计算机在监听。

  2. 某些对象具有现实世界的后果。对象可能表示密码或其他不能通过打印到日志等方式离开进程内存的机密信息。

  3. 要确定首先构建哪些对象,必须执行拓扑排序,虽然存在快速算法,但它们并非免费。特别是,如果另一个线程在此过程中修改了图形,则情况可能变得复杂。

  4. 如果保存此数据并尝试在另一个版本上重新加载它,则会失败。如果设计新版本以接受旧数据,则随着代码变得更加扭曲以处理向后兼容性,开发速度会变慢。

  5. 它与您的对象和Python的对象模型紧密耦合,因此您最终需要重新实现它。

  6. 它可以访问Python中的任何内容,因此如果有人传递shutil.rmtree('/'),机器将忠实地执行它。

大多数问题都不是致命的,但要修复它们需要进行权衡。内置的__repr__几乎什么也不做的主要原因是确保简单的事情保持简单。
而像attrs这样的模块,启发了Python 3.7中的dataclasses模块,提供了一个预定义的__repr__,基本上可以做到您所要求的,并且回答了我认为您正在想象的大多数用例。
我正在开发一种语言,以解决我上面提到的一些问题,并总结了一些不同的现有方法来处理这些问题。

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