这是一个有趣的问题。您的预期行为不是现实的原因似乎是当引发 RuntimeError 时,会关闭有问题的“过于递归”的堆栈框架。这意味着当异常在下一个较低的堆栈帧中被捕获时,该帧可以再次向上递归,直到达到限制。
也就是说,您预计一旦递归深度(比如 N)被击中,它将:
- 抛出一个 RuntimeError
- 跳转到第 (N-1) 个除块
- 得到另一个 RuntimeError
- 跳转到第 (N-2) 个,除了
- 等等
实际上发生的是:
- 抛出一个 RuntimeError
- 跳转到第 (N-1) 个除块
- 再次递归到 N
- 得到另一个 RuntimeError
- 跳转到第 (N-2) 个,除了
- 再次递归到 N
- 等等
此外,必须使用相同的异常-递归-异常增量过程来展开每个“再次递归直到 N”。因此,递归比您预期的要多得多。
在您的输出中很难看到的原因是您的原始代码无法区分具有相同x
值的多个调用。当进行第 1001 次调用时,第 1000 次调用中的异常将控制权返回给第 999 次调用。然后,此调用使用 进行另一个调用x=1000
,创建具有某些x
值的调用的并行“沿袭”。
通过如下修改原始代码可以看到该行为:
y = 2000
def a(x=0, t=''):
print(t + "In a({0})".format(x))
global y
if y:
y -= 1
try:
a(x+1, t)
except RuntimeError:
print(t + "*** E: %d" % x)
a(x+1, t+'\t')
这增加了缩进,因此您可以看到哪些调用进入了哪些其他调用。结果输出的示例是:
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 985
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 984
In a(985)
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 985
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 983
In a(984)
In a(985)
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 985
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 984
In a(985)
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
*** E: 985
In a(986)
In a(987)
*** E: 987
*** E: 986
In a(987)
*** E: 987
(出于某种原因,我的解释器首先在第 988 次调用而不是第 1000 次调用中生成错误,但这并没有太大变化。)您可以看到,每个错误只会在层次结构中将事情踢回一步,从而允许整个森林“嵌套递归”发生。
这导致呼叫数量呈指数增长。事实上,我通过将递归限制设置为一个较小的值(我尝试了 20 和 25)对此进行了测试,并确认递归最终会终止。在我的系统上,它在总调用后终止2**(R-12)
,其中 R 是递归限制。(12 是递归限制和实际引发第一个异常的数量之间的差异,如我的示例中所示,当第一个异常在 N=988 引发时;大概这 12 帧在内部被“使用”我的翻译。)
毫不奇怪,您的解释器似乎挂起,因为限制为 1000,这将需要比宇宙年龄更长的时间才能完成。