CPython的"repr"是什么时候开始处理递归数据结构的?



这个MWE在过去会导致堆栈溢出,因为x引用引用xy:

class Ref:
def __init__(self, name):
self.name = name
self.value = None
def __repr__(self):
if self.value is None:
return self.name
return f"{self.name}={self.value!r}"

if __name__ == '__main__':
x, y = Ref("x"), Ref("y")
x.value = (1, y)
y.value = (2, x)
print(x)
print(y)

但是当我用CPython 3.10.4测试它时,它可以开箱即用!

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

我找不到这个行为是什么时候改变的。我最近在2020年看到了几个问题,想知道如何处理相互递归或自递归的数据结构。我还发现reprlib内置库产生类似的输出,所以我怀疑一些语言开发人员决定默认使用它。

注意:我也测试了它与__str__,它也工作,所以它不是特定于repr()

它实际上从来没有真正做到过,直到今天(版本3.12.0 alpha 0)。

您展示的例子是最简单的一个:递归repr与同一个类的实例。在这种情况下,解释器很容易检测到repr将导致无限递归,因此停止并产生...:它只需要检查当前类的.__repr__()方法是否正在请求同一类实例的.__repr__()

自Python 1.5.1(1998!)以来一直支持,如Misc/HISTORY所示:

========================================
==> Release 1.5.1 (October 31, 1998) <==
========================================
[...]
- No longer a core dump when attempting to print (or repr(), or str())
a list or dictionary that contains an instance of itself; instead, the
recursive entry is printed as [...] or {...}.  See Py_ReprEnter() and
Py_ReprLeave() below.  Comparisons of such objects still go beserk,
since this requires a different kind of fix; fortunately, this is a
less common scenario in practice.

即使在最新的CPython版本上,任何稍微复杂的情况仍然会导致问题:

class A:
def __init__(self):
self.value = None
def __repr__(self):
return f"{self.value!r}"
class B:
def __init__(self):
self.value = None
def __repr__(self):
return f"{self.value!r}"
a, b = A(), B()
a.value = b
b.value = a
print(a)
# RecursionError: maximum recursion depth exceeded while getting the repr of an object

最新更新