Проверка и промяна на ограничението за рекурсия на Python (напр. sys.setrecursionlimit)

Бизнес

В Python има горна граница на броя на рекурсиите (максимален брой рекурсии). За да се изпълни рекурсивна функция с голям брой извиквания, е необходимо да се промени ограничението. Използвайте функциите в модула sys на стандартната библиотека.

Броят на рекурсиите също е ограничен от размера на стека. В някои среди модулът resource на стандартната библиотека може да се използва, за да се промени максималният размер на стека (работи в 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

Ако зададената горна граница е твърде малка или твърде голяма, ще възникне грешка. Това ограничение (горната и долната граница на самата граница) варира в зависимост от средата.

Максималната стойност на лимита зависи от платформата. Ако се нуждаете от дълбока рекурсия, можете да зададете по-голяма стойност в рамките на поддържания от платформата диапазон, но имайте предвид, че тази стойност ще доведе до срив, ако е твърде голяма.
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 модулът resource в стандартната библиотека може да се използва за промяна на максималния размер на стека. Въпреки това модулът resource е специфичен за Unix и не може да се използва в Windows.

С resource.getrlimit() можете да получите лимита на ресурса, посочен в аргумента, като кортеж от (soft limit, hard limit). Тук посочваме 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