Độ sâu đệ quy tối đa trong Python là gì và làm thế nào để tăng nó?


421

Tôi có chức năng đệ quy đuôi này ở đây:

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ó hoạt động đến n=997, sau đó nó chỉ phá vỡ và phun ra a RecursionError: maximum recursion depth exceeded in comparison. Đây có phải chỉ là một chồng tràn? Có cách nào để đi xung quanh nó?



9
ghi nhớ có thể tăng tốc chức năng của bạn và tăng độ sâu đệ quy hiệu quả của nó bằng cách làm cho các giá trị được tính toán trước đó chấm dứt thay vì tăng kích thước ngăn xếp.
Cyoce

2
Giới hạn đệ quy thường là 1000.
Boris

1
@tonix trình thông dịch thêm khung stack ( line <n>, in <module>dấu vết trong ngăn xếp) và mã này lấy 2 khung stack cho n=1(vì trường hợp cơ sở là n < 1vậy, vì vậy n=1nó vẫn được lặp lại). Và tôi đoán giới hạn đệ quy là không bao gồm, vì trong đó là "lỗi khi bạn chạm 1000" không "lỗi nếu bạn vượt quá 1000 (1001)". 997 + 2nhỏ hơn 1000 nên nó 998 + 2không hoạt động vì nó đạt đến giới hạn.
Boris

1
@tonix không. recursive_function(997)hoạt động, nó phá vỡ tại 998. Khi bạn gọi recursive_function(998)nó, sử dụng 999 khung xếp chồng và 1 khung được thêm bởi trình thông dịch (vì mã của bạn luôn chạy như thể nó là một phần của mô-đun cấp cao nhất), khiến nó đạt đến giới hạn 1000.
Boris

Câu trả lời:


469

Đó là một người bảo vệ chống lại một chồng tràn, vâng. Python (hay đúng hơn là việc triển khai CPython) không tối ưu hóa đệ quy đuôi và đệ quy không được kiểm soát gây ra tràn ngăn xếp. Bạn có thể kiểm tra giới hạn đệ quy sys.getrecursionlimitvà thay đổi giới hạn đệ quy với sys.setrecursionlimit, nhưng làm như vậy rất nguy hiểm - giới hạn tiêu chuẩn hơi bảo thủ, nhưng stackframes Python có thể khá lớn.

Python không phải là ngôn ngữ chức năng và đệ quy đuôi không phải là một kỹ thuật đặc biệt hiệu quả. Viết lại thuật toán lặp đi lặp lại, nếu có thể, nói chung là một ý tưởng tốt hơn.


4
Từ kinh nghiệm của tôi, bạn cần tăng giới hạn cả trong sysvà các resourcemô-đun: stackoverflow.com/a/16248113/205521
Thomas Ahle

3
như một chiến thuật để chuyển đổi nó thành phiên bản lặp, có thể sử dụng trang trí tối ưu hóa cuộc gọi đuôi
jfs

3
bạn có thể sử dụng svn.python.org/projects/python/trunk/Tools/scripts/iêu để tìm ra giới hạn trên hệ điều hành của bạn
Ullullu

8
Đối với những người quan tâm đến nguồn, giới hạn đệ quy mặc định được đặt thành 1000 hg.python.org/cpython/file/tip/Python/ceval.c#l691 và có thể thay đổi bằng API tại hg.python.org/cpython /file/tip/Python/sysmodule.c#l643 , lần lượt đặt giới hạn cho giá trị mới tại hg.python.org/cpython/file/tip/Python/ceval.c#l703
Pramod

17
Đệ quy đuôi là một kỹ thuật hoàn toàn hiệu quả trong ngôn ngữ lập trình được tối ưu hóa cho nó. Đối với loại vấn đề phù hợp, nó có thể biểu hiện rõ ràng hơn một cách thực hiện lặp đi lặp lại. Câu trả lời có thể có nghĩa là "cụ thể bằng Python" nhưng đó không phải là những gì nó nói
Peter R

135

Có vẻ như bạn chỉ cần đặt độ sâu đệ quy cao hơn :

import sys
sys.setrecursionlimit(1500)

Trong trường hợp của tôi, tôi đã quên câu lệnh return trong trường hợp cơ sở và nó đã vượt quá 1000. Python bắt đầu ném ngoại lệ này và tôi rất ngạc nhiên, vì tôi chắc chắn về điều không. ngăn xếp của nó sẽ tạo ra để chạy nó.
vijayraj34

sys.setrecursionlimit (50) hoặc một lượng nhỏ là hữu ích nếu chương trình của bạn đang được đệ quy và bạn muốn thông báo lỗi KHÔNG phải là các trang và các trang của cùng một văn bản. Tôi thấy điều này rất hữu ích trong khi gỡ lỗi mã đệ quy xấu (của tôi).
peawormsworth

56

Đó là để tránh một chồng tràn. Trình thông dịch Python giới hạn độ sâu của đệ quy để giúp bạn tránh các lần truy cập vô hạn, dẫn đến tràn ngăn xếp. Hãy thử tăng giới hạn đệ quy ( sys.setrecursionlimit) hoặc viết lại mã của bạn mà không cần đệ quy.

Từ tài liệu Python :

sys.getrecursionlimit()

Trả về giá trị hiện tại của giới hạn đệ quy, độ sâu tối đa của ngăn xếp trình thông dịch Python. Giới hạn này ngăn đệ quy vô hạn gây ra tràn C stack và làm sập Python. Nó có thể được thiết lập bởi setrecursionlimit().


Trên Anaconda x64, 3.5 Python trên Windows, giới hạn mặc định là 1000.
Guillaume Chevalier

30

Nếu bạn thường cần thay đổi giới hạn đệ quy (ví dụ: trong khi giải các câu đố lập trình), bạn có thể xác định một trình quản lý bối cảnh đơn giản như thế này:

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)

Sau đó, để gọi một chức năng với một giới hạn tùy chỉnh, bạn có thể làm:

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

Khi thoát khỏi phần thân của withcâu lệnh, giới hạn đệ quy sẽ được khôi phục về giá trị mặc định.


Bạn cũng muốn tăng giới hạn đệ quy của quy trình vớiresource . Nếu không có nó, bạn sẽ gặp Lỗi phân đoạn và toàn bộ quá trình Python sẽ gặp sự cố nếu bạn setrecursionlimitquá cao và cố gắng sử dụng giới hạn mới (khoảng 8 megabyte khung xếp chồng, có nghĩa là ~ 30.000 khung ngăn xếp với chức năng đơn giản ở trên, trên máy tính xách tay của tôi).
Boris

16

Sử dụng một ngôn ngữ đảm bảo tối ưu hóa cuộc gọi đuôi. Hoặc sử dụng phép lặp. Ngoài ra, có được dễ thương với trang trí .


36
Điều đó đúng hơn là ném em bé ra ngoài bằng nước tắm.
Russell Borogove

3
@Russell: Chỉ một trong những lựa chọn tôi đưa ra khuyên bạn điều này.
Marcelo Cantos

"Trở nên dễ thương với người trang trí" không hẳn là một lựa chọn.
Ông B

@ Mr.B trừ khi bạn cần nhiều hơn ulimit -scác khung stack, vâng, đó là stackoverflow.com/a/50120316
Boris

14

resource.setrlimit cũng phải được sử dụng để tăng kích thước ngăn xếp và ngăn chặn segfault

Nhân Linux giới hạn chồng các tiến trình .

Python lưu trữ các biến cục bộ trên ngăn xếp của trình thông dịch và do đó đệ quy chiếm không gian ngăn xếp của trình thông dịch.

Nếu trình thông dịch Python cố gắng vượt quá giới hạn ngăn xếp, nhân Linux sẽ khiến nó bị lỗi phân đoạn.

Kích thước giới hạn ngăn xếp được kiểm soát với các cuộc gọi getrlimitsetrlimithệ thống.

Python cung cấp quyền truy cập vào các cuộc gọi hệ thống đó thông qua resourcemô-đun.

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)

Tất nhiên, nếu bạn tiếp tục tăng ulimit, RAM của bạn sẽ hết, điều này sẽ làm chậm máy tính của bạn do ngừng trao đổi điên cuồng hoặc giết Python thông qua OOM Killer.

Từ bash, bạn có thể xem và đặt giới hạn ngăn xếp (tính bằng kb) với:

ulimit -s
ulimit -s 10000

Giá trị mặc định đối với tôi là 8Mb.

Xem thêm:

Đã thử nghiệm trên Ubuntu 16.10, Python 2.7.12.


1
Cố gắng thiết lập rlimit_stacksau khi khắc phục Stack Clash có thể dẫn đến thất bại hoặc các vấn đề liên quan. Xem thêm Red Hat Issue 1463241
jww

Tôi đã sử dụng phần này (phần tài nguyên Python) để giúp tôi thực hiện thuật toán của Kosaraju trên bộ dữ liệu trung bình (rất lớn) của giáo sư Tim Roughgarden. Việc triển khai của tôi đã làm việc trên các tập nhỏ, chắc chắn vấn đề với một tập dữ liệu lớn là giới hạn đệ quy / ngăn xếp ... Hay là vậy? Vâng, đúng vậy! Cảm ơn!
nilo

9

Tôi nhận ra đây là một câu hỏi cũ nhưng đối với những người đọc, tôi khuyên bạn không nên sử dụng đệ quy cho các vấn đề như thế này - danh sách nhanh hơn nhiều và tránh hoàn toàn đệ quy. Tôi sẽ thực hiện điều này như:

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

(Sử dụng n + 1 trong xrange nếu bạn bắt đầu đếm chuỗi Wikipedia từ 0 thay vì 1.)


13
tại sao sử dụng không gian O (n) khi bạn có thể sử dụng O (1)?
Janus Troelsen

11
Chỉ trong trường hợp nhận xét không gian O (n) gây nhầm lẫn: không sử dụng danh sách. Danh sách sẽ giữ tất cả các giá trị khi tất cả những gì bạn cần là giá trị thứ n. Một thuật toán đơn giản sẽ là giữ hai số cuối cùng của Wikipedia và thêm chúng cho đến khi bạn nhận được số bạn cần. Có những thuật toán tốt hơn.
Milimetric

3
@Mathime: xrangeđược gọi đơn giản range, trong Python 3.
Eric O Lebigot

1
@EOL Tôi biết điều này
Mathime

7
@Mathime Tôi đã làm cho mọi thứ rõ ràng cho những người đọc những bình luận này.
Eric O Lebigot

9

Tất nhiên, số Fibonacci có thể được tính bằng O (n) bằng cách áp dụng công thức Binet:

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

Như các nhà bình luận lưu ý, không phải O (1) mà là O (n) vì 2**n. Ngoài ra, một sự khác biệt là bạn chỉ nhận được một giá trị, trong khi với đệ quy, bạn nhận được tất cả các giá trị Fibonacci(n)lên đến giá trị đó.


8
Không có kích thước tối đa của một con trăn dài.
pppery

8
Điều đáng chú ý là điều này thất bại lớn hơn nvì sự thiếu chính xác của dấu phẩy động - sự khác biệt giữa (1+sqrt(5))**n(1+sqrt(5))**(n+1)trở thành ít hơn 1 ulp, vì vậy bạn bắt đầu nhận được kết quả không chính xác.

2
Thực tế không có số nguyên lớn nào trong NumPy Nhận
Eric O Lebigot

@Mego gì? Đó là sự khác biệt giữa (1+sqrt(5))**n((1+sqrt(5))**n)+1trở thành ít hơn 1 ulp! (lỗi đánh máy nhỏ) Ngoài ra, {@} rwst Đó không phải là O (1)! Việc tính toán 2**nmất ít nhất O (n) thời gian.
dùng202729

3
@ user202729 Điều đó không đúng, tính toán thực sự 2**nlà O (log (n)) bằng cách sử dụng Exponentiattion bằng bình phương .
Sam

6

Tôi gặp vấn đề tương tự với lỗi "vượt quá độ sâu đệ quy tối đa". Tôi phát hiện ra lỗi đang được kích hoạt bởi một tệp bị hỏng trong thư mục mà tôi đang lặp lại os.walk. Nếu bạn gặp khó khăn khi giải quyết vấn đề này và bạn đang làm việc với các đường dẫn tệp, hãy chắc chắn thu hẹp nó lại, vì đó có thể là một tệp bị hỏng.


2
OP đưa ra mã của anh ấy và thử nghiệm của anh ấy có thể được tái tạo theo ý muốn. Nó không liên quan đến các tập tin tham nhũng.
T. Verron 1/03/2015

5
Bạn nói đúng, nhưng câu trả lời của tôi không hướng đến OP, vì điều này đã hơn bốn năm trước. Câu trả lời của tôi nhằm giúp những người có lỗi MRD gián tiếp gây ra bởi các tệp bị hỏng - vì đây là một trong những kết quả tìm kiếm đầu tiên. Nó đã giúp ai đó, vì nó đã được bình chọn. Cảm ơn đã bỏ phiếu.
Tyler

2
Đây là điều duy nhất tôi tìm thấy ở bất cứ đâu khi tìm kiếm sự cố của mình có liên quan đến truy nguyên "độ sâu đệ quy tối đa" với tệp bị hỏng. Cảm ơn!
Jeff

5

Nếu bạn muốn chỉ nhận được một vài số Fibonacci, bạn có thể sử dụng phương thức ma trận.

from numpy import matrix

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

Nó nhanh như numpy sử dụng thuật toán lũy thừa nhanh. Bạn nhận được câu trả lời trong O (log n). Và nó tốt hơn công thức của Binet vì nó chỉ sử dụng số nguyên. Nhưng nếu bạn muốn tất cả các số Fibonacci lên đến n, thì tốt hơn là thực hiện bằng cách ghi nhớ.


Đáng buồn thay, bạn không thể sử dụng numpy trong hầu hết các thẩm phán lập trình cạnh tranh. Nhưng vâng thưa ngài, giải pháp của bạn là yêu thích của tôi. Tôi đã sử dụng ma trận giải cho một số vấn đề. Đó là giải pháp tốt nhất khi bạn cần một số lượng rất lớn và bạn không thể sử dụng mô-đun. Nếu bạn được phép sử dụng một mô-đun, thời kỳ pisano là cách tốt hơn để làm điều đó.
mentatkgs

4

Sử dụng máy phát điện?

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

hàm trên () được điều chỉnh từ: http://interantlypythonista.com/python-generators


1
Lý do phải gán một trình tạo cho một biến là vì [fibs().next() for ...]sẽ tạo một trình tạo mới mỗi lần.
tox123

3

Như @alex đã đề xuất , bạn có thể sử dụng hàm tạo để thực hiện việc này một cách tuần tự thay vì đệ quy.

Đây là mã tương đương trong câu hỏi của bạn:

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

Nhiều người khuyên rằng tăng giới hạn đệ quy là một giải pháp tốt tuy nhiên không phải vì sẽ luôn có giới hạn. Thay vào đó sử dụng một giải pháp lặp đi lặp lại.

def fib(n):
    a,b = 1,1
    for i in range(n-1):
        a,b = b,a+b
    return a
print fib(5)

1

Tôi muốn cho bạn một ví dụ về việc sử dụng ghi nhớ để tính toán Fibonacci vì điều này sẽ cho phép bạn tính toán các số lớn hơn đáng kể bằng cách sử dụng đệ quy:

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

Điều này vẫn là đệ quy, nhưng sử dụng hàm băm đơn giản cho phép sử dụng lại các số Fibonacci đã tính toán trước đó thay vì thực hiện lại chúng.


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
Câu trả lời tương tự này đã được đưa ra nhiều lần. Xin vui lòng loại bỏ nó.
ZF007

0

Chúng ta có thể làm điều đó bằng cách sử dụng @lru_cachetrang trí và setrecursionlimit()phương pháp:

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

Đầu ra

3002468761178461090995494179715025648692747937490792943468375429502230242942284835863402333575216217865811638730389352239181342307756720414619391217798542575996541081060501905302157019002614964717310808809478675602711440361241500732699145834377856326394037071666274321657305320804055307021019793251762830816701587386994888032362232198219843549865275880699612359275125243457132496772854886508703396643365042454333009802006384286859581649296390803003232654898464561589234445139863242606285711591746222880807391057211912655818499798720987302540712067959840802106849776547522247429904618357394771725653253559346195282601285019169360207355179223814857106405285007997547692546378757062999581657867188420995770650565521377874333085963123444258953052751461206977615079511435862879678439081175536265576977106865074099512897235100538241196445815568291377846656352979228098911566675956525644182645608178603837172227838896725425605719942300037650526231486881066037397866942013838296769284745527778439272995067231492069369130289154753132313883294398593507873555667211005422003204156154859031529462152953119957597195735953686798871131148255050140450845034240095305094449911578598539658855704158240221809528010179414493499583473568873253067921639513996596738275817909624857593693291980841303291145613566466575233283651420134915764961372875933822262953420444548349180436583183291944875599477240814774580187144637965487250578134990402443365677985388481961492444981994523034245619781853365476552719460960795929666883665704293897310201276011658074359194189359660792496027472226428571547971602259808697441435358578480589837766911684200275636889192254762678512597000452676191374475932796663842865744658264924913771676415404179920096074751516422872997665425047457428327276230059296132722787915300105002019006293320082955378715908263653377755031155794063450515731009402407584683132870206376994025920790298591144213659942668622062191441346200098342943955169522532574271644954360217472458521489671859465232568419404182043966092211744372699797375966048010775453444600153524772238401414789562651410289808994960533132759532092895779406940925252906166612153699850759933762897947175972147868784008320247586210378556711332739463277940255289047962323306946068381887446046387745247925675240182981190836264964640612069909458682443392729946084099312047752966806439331403663934969942958022237945205992581178803606156982034385347182766573351768749665172549908638337611953199808161937885366709285043276595726484068138091188914698151703122773726725261370542355162118164302728812259192476428938730724109825922331973256105091200551566581350508061922762910078528219869913214146575557249199263634241165352226570749618907050553115468306669184485910269806225894530809823102279231750061652042560772530576713148647858705369649642907780603247428680176236527220826640665659902650188140474762163503557640566711903907798932853656216227739411210513756695569391593763704981001125

Nguồn

funcools lru_cache


0

Chúng ta cũng có thể sử dụng một biến thể của cách tiếp cận từ dưới lên lập trình động

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))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.