Какова максимальная глубина рекурсии в Python и как ее увеличить?

423

У меня есть эта хвостовая рекурсивная функция здесь:

def recursive_function(n, sum):
    if n < 1:
        return sum
    else:
        return recursive_function(n-1, sum+n)

c = 998
print(recursive_function(c, 0))

Это работает до n=997, тогда это просто ломается и выплевывает RecursionError: maximum recursion depth exceeded in comparison. Это просто переполнение стека? Есть ли способ обойти это?

quantumSoup
источник
3
См. Также stackoverflow.com/questions/5061582/…
Томас Але
9
памятка может ускорить вашу функцию и увеличить ее рекурсивную глубину, заставляя ранее вычисленные значения завершаться вместо увеличения размера стека.
Cyoce
2
Предел рекурсии обычно составляет 1000.
Борис
1
@tonix интерпретатор добавляет кадр стека ( line <n>, in <module>трассировки в стеке), и для этого кода требуется 2 кадра стека n=1(потому что базовый случай есть n < 1, поэтому n=1он все еще рекурсивен). И я предполагаю, что предел рекурсии не является включающим, как в «ошибка, когда вы нажмете 1000», а не «ошибка, если вы превысите 1000 (1001)». 997 + 2меньше 1000, поэтому работает 998 + 2не потому, что достигает предела.
Борис
1
@ тоникс нет. recursive_function(997)работает, это ломает в 998. Когда вы звоните, recursive_function(998)он использует 999 стековых фреймов, и интерпретатор добавляет 1 фрейм (потому что ваш код всегда выполняется так, как если бы он был частью модуля верхнего уровня), что заставляет его достигать предела 1000.
Борис

Ответы:

470

Да, это защита от переполнения стека. Python (точнее, реализация CPython) не оптимизирует хвостовую рекурсию, а необузданная рекурсия вызывает переполнение стека. Вы можете проверить предел рекурсии с помощью sys.getrecursionlimitи изменить предел рекурсии с помощью sys.setrecursionlimit, но это опасно - стандартный лимит немного консервативный, но стековые рамки Python могут быть довольно большими.

Python не является функциональным языком, а хвостовая рекурсия не особенно эффективна. Переписывание алгоритма итеративно, если это возможно, обычно является лучшей идеей.

Томас Воутерс
источник
4
Исходя из моего опыта, вам нужно увеличить лимит как в, так sysи в resourceмодулях: stackoverflow.com/a/16248113/205521
Томас Але
3
в качестве тактики для преобразования его в итеративную версию можно использовать декоратор оптимизации хвостового вызова
jfs
3
вы можете использовать svn.python.org/projects/python/trunk/Tools/scripts/… чтобы узнать верхний предел вашей ОС
Ullullu
8
Для тех, кто интересуется источником, предел рекурсии по умолчанию установлен в 1000 hg.python.org/cpython/file/tip/Python/ceval.c#l691, и его можно изменить с помощью API по адресу hg.python.org/cpython. /file/tip/Python/sysmodule.c#l643, который, в свою очередь, устанавливает предел для нового значения в hg.python.org/cpython/file/tip/Python/ceval.c#l703
Pramod
17
Хвостовая рекурсия является совершенно эффективной техникой на оптимизированном для него языке программирования. Для правильной задачи это может быть значительно более выразительным и итеративной реализацией. Ответ, вероятно, означает «конкретно в Python», но это не то, что он говорит
Питер Р
137

Похоже, вам просто нужно установить большую глубину рекурсии :

import sys
sys.setrecursionlimit(1500)
Дэвид Янг
источник
В моем случае я забыл выражение return в базовом случае, и оно превысило 1000. Python начал выдавать это исключение, и я был поражен, потому что я был уверен, что нет. стеков его собирается создать, чтобы запустить его.
vijayraj34
sys.setrecursionlimit (50) или небольшое количество полезно, если ваша программа вводит рекурсию, и вы хотите, чтобы сообщение об ошибке НЕ было страницами и страницами одного и того же текста. Я нашел это очень полезным при отладке (моего) плохого рекурсивного кода.
Peawormsworth
56

Это чтобы избежать переполнения стека. Интерпретатор Python ограничивает глубину рекурсии, чтобы помочь вам избежать бесконечных рекурсий, что приводит к переполнению стека. Попробуйте увеличить предел рекурсии ( sys.setrecursionlimit) или переписать свой код без рекурсии.

Из документации Python :

sys.getrecursionlimit()

Возвращает текущее значение предела рекурсии, максимальную глубину стека интерпретатора Python. Это ограничение предотвращает переполнение стека C и сбой Python в бесконечной рекурсии. Это может быть установлено setrecursionlimit().

Scharron
источник
На моем Anaconda x64, 3.5 Python для Windows ограничение по умолчанию составляет 1000.
Гийом Шевалье,
30

Если вам часто нужно изменить предел рекурсии (например, при решении задач программирования), вы можете определить простой менеджер контекста, например:

import sys

class recursionlimit:
    def __init__(self, limit):
        self.limit = limit
        self.old_limit = sys.getrecursionlimit()

    def __enter__(self):
        sys.setrecursionlimit(self.limit)

    def __exit__(self, type, value, tb):
        sys.setrecursionlimit(self.old_limit)

Затем для вызова функции с пользовательским ограничением вы можете сделать:

with recursionlimit(1500):
    print(fib(1000, 0))

При выходе из тела withоператора предел рекурсии будет восстановлен до значения по умолчанию.

Евгений Ярмаш
источник
Вы также хотите увеличить предел рекурсии процесса с помощьюresource . Без этого вы получите ошибку сегментации, и весь процесс Python завершится сбоем, если вы setrecursionlimitслишком велики и попытаетесь использовать новый лимит (около 8 мегабайт стековых фреймов, что означает ~ 30 000 стековых фреймов с простой функцией, указанной выше, на мой ноутбук).
Борис
16

Используйте язык, который гарантирует оптимизацию хвостового вызова. Или используйте итерацию. В качестве альтернативы, будьте милыми с декораторами .

Марсело Кантос
источник
36
Это скорее выбрасывание ребенка с водой в ванной.
Рассел Борогове
3
@Russell: Только один из предложенных мной вариантов советует это.
Марсело Кантос
«Получить мило с декораторами» не совсем вариант.
Мистер Б.
@ Mr.B, если вам не нужно больше, чем ulimit -sстековых фреймов, да, это stackoverflow.com/a/50120316
Борис
14

resource.setrlimit также необходимо использовать для увеличения размера стека и предотвращения segfault

Ядро Linux ограничивает стек процессов .

Python хранит локальные переменные в стеке интерпретатора, поэтому рекурсия занимает место в стеке интерпретатора.

Если интерпретатор Python пытается превысить ограничение стека, ядро ​​Linux делает это из-за ошибки сегментации.

Предельный размер стека контролируется с getrlimitи setrlimitсистемных вызовов.

Python предлагает доступ к этим системным вызовам через resourceмодуль.

import resource
import sys

print resource.getrlimit(resource.RLIMIT_STACK)
print sys.getrecursionlimit()
print

# Will segfault without this line.
resource.setrlimit(resource.RLIMIT_STACK, [0x10000000, resource.RLIM_INFINITY])
sys.setrecursionlimit(0x100000)

def f(i):
    print i
    sys.stdout.flush()
    f(i + 1)
f(0)

Конечно, если вы продолжите увеличивать ulimit, ваша ОЗУ будет исчерпана, что либо замедлит работу вашего компьютера из-за безумия свопинга, либо убьет Python через OOM Killer.

Из bash вы можете увидеть и установить ограничение стека (в килобайтах) с помощью:

ulimit -s
ulimit -s 10000

Значение по умолчанию для меня 8Mb.

Смотрите также:

Протестировано на Ubuntu 16.10, Python 2.7.12.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
1
Попытка установки rlimit_stackпосле исправлений Stack Clash может привести к сбою или связанным с этим проблемам. Также смотрите Red Hat Issue 1463241
17
Я использовал это (ресурсная часть Python), чтобы помочь моей реализации алгоритма Косараджу для среднего (огромного) набора данных профессора Тима Рафгардена. Моя реализация работала на небольших наборах, конечно, проблема с большим набором данных заключалась в ограничении рекурсии / стека ... Или это было? Ну да, это было! Спасибо!
Нило
9

Я понимаю, что это старый вопрос, но для тех, кто читает, я бы рекомендовал не использовать рекурсию для таких проблем - списки намного быстрее и полностью избегают рекурсии. Я бы реализовал это как:

def fibonacci(n):
    f = [0,1,1]
    for i in xrange(3,n):
        f.append(f[i-1] + f[i-2])
    return 'The %.0fth fibonacci number is: %.0f' % (n,f[-1])

(Используйте n + 1 в xrange, если вы начинаете считать вашу последовательность Фибоначчи от 0 вместо 1.)

Даниил
источник
13
зачем использовать O (n) пробел, если вы можете использовать O (1)?
Янус Троелсен
11
На всякий случай комментарий пробела O (n) сбивал с толку: не используйте список. Список будет хранить все значения, когда все, что вам нужно, это n-е значение. Простой алгоритм состоит в том, чтобы сохранить последние два числа Фибоначчи и добавлять их до тех пор, пока вы не получите то, что вам нужно. Есть и лучшие алгоритмы.
Милиметрический
3
@Mathime: xrangeназывается просто range, в Python 3.
Eric O Lebigot
1
@EOL Я знаю об этом
Mathime
7
@Mathime Я делал вещи явными для тех, кто читает эти комментарии.
Эрик О Лебиго
9

Конечно, числа Фибоначчи можно вычислить в O (n), применяя формулу Бине:

from math import floor, sqrt

def fib(n):                                                     
    return int(floor(((1+sqrt(5))**n-(1-sqrt(5))**n)/(2**n*sqrt(5))+0.5))

Как отмечают комментаторы, это не O (1), а O (n) из-за 2**n. Разница также в том, что вы получаете только одно значение, в то время как с помощью рекурсии вы получаете все значения Fibonacci(n)вплоть до этого значения.

rwst
источник
8
В питоне нет максимального размера long.
pppery
8
Стоит отметить, что это не удается для большего nиз-за неточности с плавающей запятой - разница между (1+sqrt(5))**nи (1+sqrt(5))**(n+1)становится меньше, чем 1 ulp, поэтому вы начинаете получать неправильные результаты.
2
На самом деле в NumPy нет больших целых чисел ...
Эрик О Лебигот
@ Мего Что? Это разница между (1+sqrt(5))**nи , ((1+sqrt(5))**n)+1что становится меньше 1 ULP! (небольшая опечатка) Кроме того, {@} сначала это не O (1)! Расчет 2**nзанимает не менее O (n) времени.
user202729
3
@ user202729 Это не правда, вычисление 2**nэффективно O (log (n)) с использованием Exponentiattion путем возведения в квадрат .
Сэм
6

У меня была похожая проблема с ошибкой «Максимальная глубина рекурсии превышена». Я обнаружил, что ошибка была вызвана поврежденным файлом в каталоге, с которым я зацикливался os.walk. Если у вас возникли проблемы с решением этой проблемы, и вы работаете с путями к файлам, обязательно сузьте их, так как это может быть поврежденный файл.

Тайлер
источник
2
ОП дает свой код, и его эксперимент воспроизводится по желанию. Это не связано с поврежденными файлами.
Т. Веррон
5
Вы правы, но мой ответ не направлен на ФП, поскольку это было более четырех лет назад. Мой ответ призван помочь тем, у кого ошибки MRD косвенно вызваны поврежденными файлами - так как это один из первых результатов поиска. Это помогло кому-то, так как за него проголосовали. Спасибо за отрицательное голосование.
Тайлер
2
Это было единственное, что я нашел где-то, когда искал мою проблему, которая связывала «максимальную глубину рекурсии» с поврежденным файлом. Спасибо!
Джефф
5

Если вы хотите получить только несколько чисел Фибоначчи, вы можете использовать матричный метод.

from numpy import matrix

def fib(n):
    return (matrix('0 1; 1 1', dtype='object') ** n).item(1)

Это быстро, поскольку NumPy использует быстрый алгоритм возведения в степень. Вы получите ответ в O (войдите n). И это лучше, чем формула Бине, потому что она использует только целые числа. Но если вы хотите, чтобы все числа Фибоначчи были до n, то лучше сделать это запоминанием.

bebidek
источник
К сожалению, вы не можете использовать NumPy в большинстве соревновательных судей по программированию. Но да, сэр, ваше решение мое любимое. Я использовал матричное решение для некоторых задач. Это лучшее решение, когда вам нужно очень большое число Фибоначчи, и вы не можете использовать модуль. Если вам разрешено использовать модуль, период Пизано - лучший способ сделать это.
mentatkgs
4

Использовать генераторы?

def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fibs = fib() #seems to be the only way to get the following line to work is to
             #assign the infinite generator to a variable

f = [fibs.next() for x in xrange(1001)]

for num in f:
        print num

вышеуказанная функция fib () адаптирована из: http://intermediatepythonista.com/python-generators

Алекс
источник
1
причина того, что необходимо присвоить генератор переменной, заключается в том, что [fibs().next() for ...]каждый раз создается новый генератор.
tox123
3

Как и @alex предложил , вы можете использовать функцию генератора, чтобы сделать это последовательно, а не рекурсивно.

Вот эквивалент кода в вашем вопросе:

def fib(n):
    def fibseq(n):
        """ Iteratively return the first n Fibonacci numbers, starting from 0. """
        a, b = 0, 1
        for _ in xrange(n):
            yield a
            a, b = b, a + b

    return sum(v for v in fibseq(n))

print format(fib(100000), ',d')  # -> no recursion depth error
Мартино
источник
2

Многие рекомендуют, чтобы увеличение предела рекурсии было хорошим решением, но не потому, что оно всегда будет. Вместо этого используйте итеративное решение.

def fib(n):
    a,b = 1,1
    for i in range(n-1):
        a,b = b,a+b
    return a
print fib(5)
Харун ЭРГУЛЬ
источник
1

Я хотел бы привести пример использования запоминания для вычисления Фибоначчи, поскольку это позволит вам вычислять значительно большие числа с помощью рекурсии:

cache = {}
def fib_dp(n):
    if n in cache:
        return cache[n]
    if n == 0: return 0
    elif n == 1: return 1
    else:
        value = fib_dp(n-1) + fib_dp(n-2)
    cache[n] = value
    return value

print(fib_dp(998))

Это все еще рекурсивно, но использует простую хеш-таблицу, которая позволяет повторно использовать ранее вычисленные числа Фибоначчи, а не делать их снова.

user3393266
источник
1
import sys
sys.setrecursionlimit(1500)

def fib(n, sum):
    if n < 1:
        return sum
    else:
        return fib(n-1, sum+n)

c = 998
print(fib(c, 0))
user11462758
источник
1
Этот же ответ был дан много раз. Пожалуйста, удалите это.
ZF007
0

Мы можем сделать это, используя @lru_cacheдекоратор и setrecursionlimit()метод:

import sys
from functools import lru_cache

sys.setrecursionlimit(15000)


@lru_cache(128)
def fib(n: int) -> int:
    if n == 0:
        return 0
    if n == 1:
        return 1

    return fib(n - 2) + fib(n - 1)


print(fib(14000))

Вывод

3002468761178461090995494179715025648692747937490792943468375429502230242942284835863402333575216217865811638730389352239181342307756720414619391217798542575996541081060501905302157019002614964717310808809478675602711440361241500732699145834377856326394037071666274321657305320804055307021019793251762830816701587386994888032362232198219843549865275880699612359275125243457132496772854886508703396643365042454333009802006384286859581649296390803003232654898464561589234445139863242606285711591746222880807391057211912655818499798720987302540712067959840802106849776547522247429904618357394771725653253559346195282601285019169360207355179223814857106405285007997547692546378757062999581657867188420995770650565521377874333085963123444258953052751461206977615079511435862879678439081175536265576977106865074099512897235100538241196445815568291377846656352979228098911566675956525644182645608178603837172227838896725425605719942300037650526231486881066037397866942013838296769284745527778439272995067231492069369130289154753132313883294398593507873555667211005422003204156154859031529462152953119957597195735953686798871131148255050140450845034240095305094449911578598539658855704158240221809528010179414493499583473568873253067921639513996596738275817909624857593693291980841303291145613566466575233283651420134915764961372875933822262953420444548349180436583183291944875599477240814774580187144637965487250578134990402443365677985388481961492444981994523034245619781853365476552719460960795929666883665704293897310201276011658074359194189359660792496027472226428571547971602259808697441435358578480589837766911684200275636889192254762678512597000452676191374475932796663842865744658264924913771676415404179920096074751516422872997665425047457428327276230059296132722787915300105002019006293320082955378715908263653377755031155794063450515731009402407584683132870206376994025920790298591144213659942668622062191441346200098342943955169522532574271644954360217472458521489671859465232568419404182043966092211744372699797375966048010775453444600153524772238401414789562651410289808994960533132759532092895779406940925252906166612153699850759933762897947175972147868784008320247586210378556711332739463277940255289047962323306946068381887446046387745247925675240182981190836264964640612069909458682443392729946084099312047752966806439331403663934969942958022237945205992581178803606156982034385347182766573351768749665172549908638337611953199808161937885366709285043276595726484068138091188914698151703122773726725261370542355162118164302728812259192476428938730724109825922331973256105091200551566581350508061922762910078528219869913214146575557249199263634241165352226570749618907050553115468306669184485910269806225894530809823102279231750061652042560772530576713148647858705369649642907780603247428680176236527220826640665659902650188140474762163503557640566711903907798932853656216227739411210513756695569391593763704981001125

Источник

functools lru_cache

Эмма
источник
0

Мы также могли бы использовать вариант динамического программирования снизу вверх

def fib_bottom_up(n):

    bottom_up = [None] * (n+1)
    bottom_up[0] = 1
    bottom_up[1] = 1

    for i in range(2, n+1):
        bottom_up[i] = bottom_up[i-1] + bottom_up[i-2]

    return bottom_up[n]

print(fib_bottom_up(20000))
algowhiz
источник