Яка максимальна глибина рекурсії в Python та як її збільшити?


421

У мене є ця хвостова рекурсивна функція тут:

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
запам'ятовування може пришвидшити вашу функцію та збільшити її ефективну рекурсивну глибину, зробивши припинене раніше обчислені значення замість збільшення розміру стека.
Кіос

2
Ліміт рекурсії зазвичай 1000.
Борис

1
@tonix інтерпретатор додає фрейм стека ( line <n>, in <module>сліди в стеці), і цей код бере 2 кадри стека для n=1(оскільки базовий випадок є n < 1, тому для n=1нього все одно повторюється). І я здогадуюсь, що межа рекурсії не є включною, оскільки в цьому "помилка, коли ви потрапляєте на 1000" не "помилка, якщо ви перевищуєте 1000 (1001)". 997 + 2менше 1000, тому вона працює 998 + 2не тому, що вона досягає межі.
Борис

1
@tonix немає. recursive_function(997)працює, він ламається при 998. Під час виклику recursive_function(998)він використовує 999 стекових кадрів, а інтерпретатор додає 1 кадр (оскільки ваш код завжди працює так, ніби він є частиною модуля верхнього рівня), завдяки чому він досягає межі 1000.
Борис

Відповіді:


469

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

Python не є функціональною мовою, а хвостова рекурсія не є особливо ефективною методикою. Переписувати алгоритм повторно, якщо можливо, це, як правило, краща ідея.


4
З мого досвіду, вам необхідно збільшити ліміт як в sysі в resourceмодулях: stackoverflow.com/a/16248113/205521
Thomas Ахль

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
Прамод

16
Хвостова рекурсія - це ідеально ефективна методика в мові програмування, оптимізованої для неї. Для правильного виду проблеми це може бути значно виразніше ітераційною реалізацією. Відповідь, ймовірно, означає "конкретно в Python", але це не те, що сказано
Петро R

135

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

import sys
sys.setrecursionlimit(1500)

У моєму випадку я забув декларацію про повернення в базовому випадку, і вона перевищила 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)

Звичайно, якщо ви продовжуєте збільшувати безліч, ваша оперативна пам’ять закінчиться, що або сповільнить ваш комп'ютер внаслідок зупинки божевілля, або вб'є Python через OOM Killer.

З bash ви можете побачити та встановити ліміт стека (у кб) за допомогою:

ulimit -s
ulimit -s 10000

Значення за замовчуванням для мене - 8Mb.

Дивись також:

Тестовано на Ubuntu 16.10, Python 2.7.12.


1
Спроба встановити rlimit_stackпісля виправлення стека зіткнення може призвести до помилок або пов'язаних з цим проблем. Також дивіться випуск
1717

Я використовував це (ресурсна частина Python), щоб допомогти моїй реалізації алгоритму Косараджу на середньому (величезному) наборі даних професора Тіма Рафгардена. Моя реалізація працювала на невеликих наборах, безумовно, проблема з великим набором даних була межа рекурсії / стека ... Або це було? Ну так, так було! Дякую!
nilo

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 від 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
Немає максимального розміру довгого у пітона.
pppery

8
Варто зауважити, що це не вдається для більшого nчерез неточність з плаваючою комою - різниця між (1+sqrt(5))**nта (1+sqrt(5))**(n+1)стає менше 1 ulp, тому ви починаєте отримувати неправильні результати.

2
В NumPy насправді немає великих цілих чисел…
Eric O Lebigot

@Mego Що? Це різниця між (1+sqrt(5))**nі , ((1+sqrt(5))**n)+1що стає менше 1 ULP! (невеликий друк) Також {@} rwst Це не О (1)! Розрахунок 2**nзаймає принаймні O (n) час.
користувач202729

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