在Python中,递归的数量有一个上限(最大递归数)。为了执行一个有大量调用的递归函数,有必要改变这个限制。使用标准库的sys模块中的函数。
递归的数量也受到堆栈大小的限制。在某些环境下,可以用标准库的资源模块来改变最大的堆栈大小(在Ubuntu上有效,但在Windows或mac上无效)。
这里提供了以下信息。
- 获取当前递归数量的上限。
sys.getrecursionlimit()
- 改变递归数量的上限。
sys.setrecursionlimit()
- 改变堆栈的最大尺寸。
resource.setrlimit()
该样本代码在Ubuntu上运行。
获取当前递归限制:sys.getrecursionlimit()
当前的递归限制可以通过sys.getrecursionlimit()获得。
import sys
import resource
print(sys.getrecursionlimit())
# 1000
在这个例子中,递归的最大数量是1000,这可能因你的环境而异。注意,我们在这里导入的资源将在以后使用,但不是在Windows上。
作为一个例子,我们将使用下面这个简单的递归函数。如果指定一个正整数n作为参数,那么调用的次数将是n次。
def recu_test(n):
if n == 1:
print('Finish')
return
recu_test(n - 1)
如果你试图执行超过上限的递归,将会产生一个错误(RecursionError)。
recu_test(950)
# Finish
# recu_test(1500)
# RecursionError: maximum recursion depth exceeded in comparison
注意,由sys.getrecursionlimit()得到的值严格来说不是最大的递归数,而是Python解释器的最大堆栈深度,所以即使递归数比这个值略少,也会引发一个错误(RecursionError)。
递归极限不是递归的极限,而是python解释器堆栈的最大深度。
python – Max recursion is not exactly what sys.getrecursionlimit() claims. How come? – Stack Overflow
# recu_test(995)
# RecursionError: maximum recursion depth exceeded while calling a Python object
改变递归限制:sys.setrecursionlimit()
递归次数的上限可以通过sys.setrecursionlimit()改变。上限是作为一个参数指定的。
允许进行更深入的递归。
sys.setrecursionlimit(2000)
print(sys.getrecursionlimit())
# 2000
recu_test(1500)
# Finish
如果指定的上限过小或过大,就会发生错误。这种约束(极限本身的上限和下限)根据环境的不同而变化。
limit的最大值取决于平台。如果你需要深度递归,你可以在平台支持的范围内指定一个更大的值,但要注意这个值如果太大,会导致崩溃。
If the new limit is too low at the current recursion depth, a RecursionError exception is raised.
sys.setrecursionlimit() — System-specific parameters and functions — Python 3.10.0 Documentation
sys.setrecursionlimit(4)
print(sys.getrecursionlimit())
# 4
# sys.setrecursionlimit(3)
# RecursionError: cannot set the recursion limit to 3 at the recursion depth 1: the limit is too low
sys.setrecursionlimit(10 ** 9)
print(sys.getrecursionlimit())
# 1000000000
# sys.setrecursionlimit(10 ** 10)
# OverflowError: signed integer is greater than maximum
递归的最大数量也受到堆栈大小的限制,这一点接下来会解释。
改变堆栈的最大尺寸: resource.setrlimit()
即使在sys.setrecursionlimit()中设置了一个大值,如果递归的数量很大,也可能无法执行。出现分段故障的情况如下。
sys.setrecursionlimit(10 ** 9)
print(sys.getrecursionlimit())
# 1000000000
recu_test(10 ** 4)
# Finish
# recu_test(10 ** 5)
# Segmentation fault
在Python中,可以使用标准库中的资源模块来改变最大堆栈大小。然而,资源模块是一个Unix特有的模块,不能在Windows上使用。
- Unix Specific Services — Python 3.10.0 Documentation
- resource — Resource usage information — Python 3.10.0 Documentation
通过resource.getrlimit(),你可以得到参数中指定的资源的限制,是一个(软限制,硬限制)的元组。在这里,我们指定resource.RLIMIT_STACK作为资源,它代表了当前进程的调用堆栈的最大尺寸。
- resource.getrlimit() — Resource usage information — Python 3.10.0 Documentation
- resource.RLIMIT_STACK — Resource usage information — Python 3.10.0 Documentation
print(resource.getrlimit(resource.RLIMIT_STACK))
# (8388608, -1)
在这个例子中,软限制是8388608(8388608 B = 8192 KB = 8 MB),硬限制是-1(无限)。
你可以用resource.setrlimit()改变资源的限制。这里,软限制也被设置为-1(无限制)。你也可以用常数resource.RLIM_INFINIT来表示无限的限制。
深度递归,在堆栈大小改变之前由于分段故障而无法执行,现在可以执行了。
resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))
print(resource.getrlimit(resource.RLIMIT_STACK))
# (-1, -1)
recu_test(10 ** 5)
# Finish
在这里,为了一个简单的实验,软限制被设置为-1(无限制),但在现实中,将其限制在一个适当的值上会更安全。
此外,当我试图在我的mac上也设置一个无限的软限制时,出现了以下错误。ValueError: not allowed to raise maximum limit
用sudo运行脚本并没有帮助。它可能受到系统的限制。
一个拥有超级用户有效UID的进程可以请求任何合理的限制,包括没有限制。
然而,超过系统规定的限制的请求仍然会导致ValueError。
resource.setrlimit() — Resource usage information — Python 3.10.0 Documentation
Windows没有资源模块,由于系统限制,mac无法改变最大堆栈大小。如果我们能通过某种手段增加堆栈的大小,我们应该能解决分段故障,但我们还不能确认这一点。