檢查並更改 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作為resource,代表當前進程調用棧的最大大小。

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 由於系統限制無法更改最大堆棧大小。如果我們可以通過某種方式增加堆棧大小,我們應該可以解決分段錯誤,但我們無法確認這一點。