Sprawdź i zmień limit rekurencji Pythona (np. sys.setrecursionlimit)

Biznes

W Pythonie istnieje górna granica liczby rekurencji (maksymalna liczba rekurencji). Aby wykonać funkcję rekurencyjną z dużą liczbą wywołań, należy zmienić ten limit. W tym celu należy skorzystać z funkcji znajdujących się w module sys biblioteki standardowej.

Liczba rekurencji jest również ograniczona przez rozmiar stosu. W niektórych środowiskach, moduł zasobów biblioteki standardowej może być użyty do zmiany maksymalnego rozmiaru stosu (działało to na Ubuntu, ale nie na Windows lub mac).

Podane są tu następujące informacje.

  • Uzyskaj górną granicę bieżącej liczby rekurencji:sys.getrecursionlimit()
  • Zmień górny limit liczby rekurencji:sys.setrecursionlimit()
  • Zmiana maksymalnego rozmiaru stosu:resource.setrlimit()

Przykładowy kod jest uruchomiony na Ubuntu.

Uzyskaj bieżący limit rekurencji: sys.getrecursionlimit()

Bieżący limit rekurencji może być uzyskany za pomocą sys.getrecursionlimit().

import sys
import resource

print(sys.getrecursionlimit())
# 1000

W przykładzie maksymalna liczba rekurencji wynosi 1000, co może się różnić w zależności od Twojego środowiska. Zauważ, że zasób, który tutaj importujemy, będzie używany później, ale nie w systemie Windows.

Jako przykładu użyjemy następującej prostej funkcji rekurencyjnej. Jeśli jako argument podamy dodatnią liczbę całkowitą n, to liczba wywołań będzie n-krotna.

def recu_test(n):
    if n == 1:
        print('Finish')
        return
    recu_test(n - 1)

Błąd (RecursionError) zostanie podniesiony, jeśli spróbujesz wykonać rekurencję więcej niż wynosi górny limit.

recu_test(950)
# Finish

# recu_test(1500)
# RecursionError: maximum recursion depth exceeded in comparison

Zauważ, że wartość uzyskana przez sys.getrecursionlimit() nie jest ściśle maksymalną liczbą rekurencji, ale maksymalną głębokością stosu interpretera Pythona, więc nawet jeśli liczba rekurencji jest nieco mniejsza od tej wartości, zostanie podniesiony błąd (RecursionError).

Limit rekurencji nie jest limitem rekurencji, ale maksymalną głębokością stosu interpretera pythona.
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

Zmień limit rekurencji: sys.setrecursionlimit()

Górny limit liczby rekurencji może być zmieniony przez sys.setrecursionlimit(). Górna granica jest podawana jako argument.

Umożliwia wykonanie głębszej rekurencji.

sys.setrecursionlimit(2000)

print(sys.getrecursionlimit())
# 2000

recu_test(1500)
# Finish

Jeżeli określona górna granica jest zbyt mała lub zbyt duża, to wystąpi błąd. Ograniczenie to (górna i dolna granica samego ograniczenia) zmienia się w zależności od środowiska.

Maksymalna wartość limitu zależy od platformy. Jeśli potrzebujesz głębokiej rekurencji, możesz podać większą wartość w zakresie obsługiwanym przez platformę, ale pamiętaj, że ta wartość spowoduje awarię, jeśli będzie zbyt duża.
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

Maksymalna liczba rekurencji jest również ograniczona przez rozmiar stosu, jak wyjaśniono poniżej.

Zmień maksymalny rozmiar stosu: resource.setrlimit()

Nawet jeśli w sys.setrecursionlimit() ustawiona jest duża wartość, może ona nie zostać wykonana, jeśli liczba rekurencji jest duża. Błąd segmentacji występuje w następujący sposób.

sys.setrecursionlimit(10 ** 9)
print(sys.getrecursionlimit())
# 1000000000
recu_test(10 ** 4)
# Finish

# recu_test(10 ** 5)
# Segmentation fault

W Pythonie do zmiany maksymalnego rozmiaru stosu można użyć modułu resource w bibliotece standardowej. Jednak moduł zasobów jest modułem specyficznym dla systemu Unix i nie może być używany w systemie Windows.

Za pomocą resource.getrlimit(), możesz uzyskać limit zasobu określonego w argumencie jako tuple (soft limit, hard limit). Tutaj podajemy resource.RLIMIT_STACK jako zasób, który reprezentuje maksymalny rozmiar stosu wywołań bieżącego procesu.

print(resource.getrlimit(resource.RLIMIT_STACK))
# (8388608, -1)

W przykładzie limit miękki wynosi 8388608 (8388608 B = 8192 KB = 8 MB), a twardy -1 (nieograniczony).

Możesz zmienić limit zasobu za pomocą resource.setrlimit(). Tutaj również miękki limit jest ustawiony na -1 (brak limitu). Możesz również użyć stałej resource.RLIM_INFINIT do reprezentowania nieograniczonego limitu.

Głęboka rekurencja, która nie mogła być wykonywana z powodu błędu segmentacji przed zmianą rozmiaru stosu, może być teraz wykonywana.

resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))

print(resource.getrlimit(resource.RLIMIT_STACK))
# (-1, -1)

recu_test(10 ** 5)
# Finish

Tutaj miękki limit jest ustawiony na -1 (brak limitu) dla prostego eksperymentu, ale w rzeczywistości bezpieczniej byłoby ograniczyć go do odpowiedniej wartości.

Ponadto, gdy próbowałem ustawić nieograniczony miękki limit na moim macu, jak również, wystąpił następujący błąd.ValueError: not allowed to raise maximum limit
Uruchomienie skryptu z sudo nie pomogło. Może to być ograniczone przez system.

Proces z efektywnym UID superużytkownika może zażądać dowolnego rozsądnego limitu, włączając w to brak limitu.
Jednak żądanie, które przekroczy limit narzucony przez system, nadal będzie skutkować ValueError.
resource.setrlimit() — Resource usage information — Python 3.10.0 Documentation

Windows nie posiada modułu zasobów, a Mac nie mógł zmienić maksymalnego rozmiaru stosu z powodu ograniczeń systemowych. Jeśli uda nam się w jakiś sposób zwiększyć rozmiar stosu, powinniśmy być w stanie rozwiązać problem błędu segmentacji, ale nie byliśmy w stanie tego potwierdzić.