Какова максимальная глубина рекурсии в 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. Это просто переполнение стека? Есть ли способ обойти это?



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().


На моем 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.


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)вплоть до этого значения.


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, то лучше сделать это запоминанием.


К сожалению, вы не можете использовать 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))

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


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))

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))
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.