检查并改变Python的递归限制(例如,sys.setrecursionlimit)

商业

在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上使用。

通过resource.getrlimit(),你可以得到参数中指定的资源的限制,是一个(软限制,硬限制)的元组。在这里,我们指定resource.RLIMIT_STACK作为资源,它代表了当前进程的调用堆栈的最大尺寸。

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无法改变最大堆栈大小。如果我们能通过某种手段增加堆栈的大小,我们应该能解决分段故障,但我们还不能确认这一点。

Copied title and URL