В 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.
- Unix Specific Services — Python 3.10.0 Documentation
- resource — Resource usage information — Python 3.10.0 Documentation
С resource.getrlimit() можете да получите лимита на ресурса, посочен в аргумента, като кортеж от (soft limit, hard limit). Тук посочваме 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 не може да промени максималния размер на стека поради системни ограничения. Ако можем да увеличим размера на стека по някакъв начин, би трябвало да успеем да решим проблема с грешката при сегментиране, но не успяхме да потвърдим това.