파이썬에서 최대 재귀 깊이는 얼마이며 어떻게 증가합니까?


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
메모를 사용하면 스택 크기를 늘리지 않고 이전에 계산 된 값을 종료하여 함수 속도를 높이고 효과적인 재귀 수준을 높일 수 있습니다.
— Cyoce

2
재귀 한계는 보통 1000입니다.
— Boris

1
@tonix 인터프리터는 스택 프레임 ( line <n>, in <module>스택 추적)을 추가 하고이 코드는 2 개의 스택 프레임을 취합니다 n=1(기본 사례는 n < 1이므로 n=1여전히 재귀합니다). 그리고 재귀 제한은 "1000을 눌렀을 때의 오류"가 아니라 "1000 (1001)을 초과하면 오류"가 아니기 때문에 포괄적 인 것이 아니라고 생각합니다. 997 + 21000 미만이므로 998 + 2한계에 도달하기 때문에 작동 하지 않습니다.
— 보리스

1
@tonix 번호 recursive_function(997)작동합니다 998. 호출 recursive_function(998)할 때 999 스택 프레임을 사용하고 인터프리터가 1 프레임을 추가합니다 (코드가 항상 최상위 모듈의 일부 인 것처럼 실행되기 때문에) .1000 제한에 도달합니다.
— Boris

답변:


469

스택 오버플로에 대한 보호입니다. 파이썬 (또는 CPython 구현)은 테일 재귀를 최적화하지 않으며 브 래딩되지 않은 재귀로 인해 스택 오버플로가 발생합니다. 으로 재귀 한계를 확인하고으로 재귀 한계를 sys.getrecursionlimit변경할 수 sys.setrecursionlimit있지만 그렇게하는 것은 위험합니다. 표준 한계는 약간 보수적이지만 파이썬 스택 프레임은 상당히 클 수 있습니다.

파이썬은 기능적 언어가 아니며 꼬리 재귀는 특히 효율적인 기술이 아닙니다. 가능한 경우 알고리즘을 반복적으로 다시 작성하는 것이 일반적으로 더 좋습니다.


4
내 경험상, 모듈 sys과 resource모듈 모두에서 한계를 늘려야합니다 . stackoverflow.com/a/16248113/205521
— Thomas Ahle

3
그것을 반복적 인 버전으로 변환하는 전술로서, 테일 콜 최적화 데코레이터가 사용될 수있다
— jfs

3
svn.python.org/projects/python/trunk/Tools/scripts/… 를 사용 하여 OS 상한을 알아볼 수 있습니다.
— 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
테일 재귀는 최적화 된 프로그래밍 언어에서 완벽하게 효율적인 기술입니다. 올바른 종류의 문제의 경우 반복 구현이 훨씬 더 표현적일 수 있습니다. 대답은 아마도 "파이썬에서"라는 뜻일 것입니다. 그러나 그것은 그렇지 않습니다
— Peter R

135

더 높은 재귀 깊이 를 설정 해야하는 것처럼 보입니다 .

import sys
sys.setrecursionlimit(1500)

내 경우에는 기본 사례에서 return 문을 잊어 버렸고 1000을 초과했습니다. Python 은이 예외를 던지기 시작했으며 아니오에 대해 확신했기 때문에 놀랐습니다. 스택을 실행하여 실행할 것입니다.
— vijayraj34

sys.setrecursionlimit (50) 또는 소량은 프로그램이 재귀를 입력하고 오류 메시지가 동일한 텍스트의 페이지 및 페이지가 아닌 경우에 유용합니다. 나쁜 재귀 코드를 디버깅하는 동안 이것이 매우 유용하다는 것을 알았습니다.
— peawormsworth

56

스택 오버플로를 피해야합니다. Python 인터프리터는 재귀 깊이를 제한하여 무한 재귀를 피하여 스택 오버플로를 발생시킵니다. 재귀 제한을 늘리 sys.setrecursionlimit거나 ( ) 재귀없이 코드를 다시 작성하십시오.

로부터 파이썬 문서 :

sys.getrecursionlimit()

파이썬 인터프리터 스택의 최대 깊이 인 재귀 한계의 현재 값을 반환합니다. 이 제한은 무한 재귀가 C 스택의 오버플로를 유발하고 Python을 충돌시키는 것을 방지합니다. 로 설정할 수 있습니다 setrecursionlimit().


내 아나콘다 x64, 윈도우 3.5 파이썬에서 기본 제한은 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 를 높이고 싶습니다 . 그것 없이는 분할 오류가 발생하고 setrecursionlimit너무 높으면 전체 파이썬 프로세스가 중단 되고 새로운 한계 (약 8 메가 바이트의 스택 프레임, 위의 간단한 기능으로 ~ 30,000 스택 프레임으로 변환)를 사용하려고하면 내 노트북).
— 보리스

16

테일 콜 최적화를 보장하는 언어를 사용하십시오. 또는 반복을 사용하십시오. 또는 데코레이터로 귀여워하십시오 .


36
그것은 오히려 목욕물로 아기를 버리는 것입니다.
— Russell Borogove

3
@Russell : 내가 제안한 옵션 중 하나만이 이것을 조언합니다.
— Marcelo Cantos

"데코레이터로 귀여워"는 옵션이 아닙니다.
— Mr. B

@ Mr.B ulimit -s스택 프레임 보다 더 많이 필요하지 않은 한 , 네, stackoverflow.com/a/50120316
— Boris

14

resource.setrlimit 또한 스택 크기를 늘리고 segfault를 방지하는 데 사용해야합니다.

리눅스 커널 은 프로세스 스택을 제한한다 .

파이썬은 지역 변수를 인터프리터의 스택에 저장하므로 재귀는 인터프리터의 스택 공간을 차지합니다.

파이썬 인터프리터가 스택 제한을 초과하려고 시도하면 Linux 커널은이를 분할 오류로 만듭니다.

스택 제한 크기는 getrlimit및 setrlimit시스템 호출로 제어됩니다 .

파이썬은 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를 계속 늘리면 RAM이 부족하여 스왑 광기로 인해 컴퓨터가 정지되거나 OOM Killer를 통해 Python을 종료합니다.

bash에서 다음을 사용하여 스택 제한 (KB)을보고 설정할 수 있습니다.

ulimit -s
ulimit -s 10000

저의 기본값은 8Mb입니다.

또한보십시오:

Ubuntu 16.10, Python 2.7.12에서 테스트되었습니다.


1
스택 충돌 수정 rlimit_stack후 설정 을 시도하면 실패 또는 관련 문제가 발생할 수 있습니다. 또한 Red Hat Issue 1463241
— jww

나는 이것을 (Python 리소스 부분) 사용하여 Tim Roughgarden 교수의 평균 (거대한) 데이터 세트에 Kosaraju 알고리즘을 구현하는 데 도움을주었습니다. 내 구현은 작은 세트에서 작동했습니다. 확실히 큰 데이터 세트의 문제는 재귀 / 스택 제한이었습니다 ... 아니면 그렇지 않습니까? 글쎄, 그래! 감사!
— 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])

(피보나치 수열을 1이 아닌 0부터 시작하면 xrange에서 n + 1을 사용하십시오.)


13
O (1)을 사용할 수 있는데 왜 O (n) 공간을 사용합니까?
— Janus Troelsen

11
O (n) 공간 주석이 혼란 스러울 경우를 대비하여 목록을 사용하지 마십시오. 필요한 모든 값이 n 번째 값이면 목록에 모든 값이 유지됩니다. 간단한 알고리즘은 마지막 두 피보나치 수를 유지하고 필요한 수에 도달 할 때까지 추가하는 것입니다. 더 나은 알고리즘도 있습니다.
— 밀리 메트릭

3
@Mathime : 파이썬 3에서 xrange간단히 호출 됩니다.range
— Eric O Lebigot

1
@EOL 알고 있습니다
— Mathime

7
@Mathime 나는이 의견을 읽는 사람들을 위해 명시 적으로 만들었습니다.
— Eric O Lebigot

9

물론 피보나치 수는 Binet 공식을 적용하여 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에는 실제로 큰 정수가 없습니다…
— Eric O Lebigot

@ 메고 무엇? 이 사이의 차이점 (1+sqrt(5))**n과 ((1+sqrt(5))**n)+1그 미만 1 ULP된다! (작은 오타) 또한 {@} rwst O (1)가 아닙니다! 계산 2**n에는 최소한 O (n) 시간이 걸립니다.
— user202729

3
@ user202729 사실이 아닙니다. 계산 2**n은 squaring으로 지수를 사용하여 효과적으로 O (log (n)) 입니다.
— Sam

6

"최대 재귀 깊이를 초과했습니다"라는 오류와 비슷한 문제가있었습니다. 내가 루핑하고있는 디렉토리의 손상된 파일로 인해 오류가 발생하고 있음을 발견했습니다 os.walk. 이 문제를 해결하는 데 문제가 있고 파일 경로를 사용하는 경우 손상된 파일 일 수 있으므로 좁히십시오.


2
OP는 코드를 제공하며 실험은 마음대로 재현 할 수 있습니다. 손상된 파일은 포함되지 않습니다.
— T. Verron

5
네 말이 맞지만 내 대답은 4 년 전부터 OP를 향한 것이 아닙니다. 내 대답은 손상된 파일로 인해 간접적으로 MRD 오류가있는 사람들을 돕는 것입니다. 이는 첫 번째 검색 결과 중 하나이기 때문입니다. 투표를 한 이후 누군가를 도왔습니다. 다운 투표 해 주셔서 감사합니다.
— Tyler

2
이것은 "최대 재귀 깊이"역 추적을 손상된 파일에 연결 한 문제를 검색 할 때 내가 찾은 유일한 곳입니다. 감사!
— Jeff

5

피보나치 수를 적게 얻으려면 행렬 방법을 사용할 수 있습니다.

from numpy import matrix

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

numpy가 빠른 지수 알고리즘을 사용하기 때문에 빠릅니다. O (log n)로 답을 얻습니다. 그리고 정수만 사용하기 때문에 Binet의 공식보다 낫습니다. 그러나 모든 피보나치 수를 최대 n 개까지 원한다면 암기하여하는 것이 좋습니다.


안타깝게도 대부분의 경쟁 프로그래밍 판사에게는 numpy를 사용할 수 없습니다. 그러나 네 선생님, 당신의 해결책은 내가 가장 좋아하는 것입니다. 몇 가지 문제에 대해 매트릭스 분해를 사용했습니다. 피보나치 수가 매우 많고 모듈러스를 사용할 수없는 경우 가장 좋은 솔루션입니다. 모듈러스를 사용할 수 있으면 피사노 기간이 더 나은 방법입니다.
— mentatkgs 2018 년

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 2016 년

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

산출

300246876117846109099549417971502564869274793749079294346837542950223024294228483586340233357521621786581163873038935223918134230775672041461939121779854257599654108106050190530215701900261496471731080880947867560271144036124150073269914583437785632639403707166627432165730532080405530702101979325176283081670158738699488803236223219821984354986527588069961235927512524345713249677285488650870339664336504245433300980200638428685958164929639080300323265489846456158923444513986324260628571159174622288080739105721191265581849979872098730254071206795984080210684977654752224742990461835739477172565325355934619528260128501916936020735517922381485710640528500799754769254637875706299958165786718842099577065056552137787433308596312344425895305275146120697761507951143586287967843908117553626557697710686507409951289723510053824119644581556829137784665635297922809891156667595652564418264560817860383717222783889672542560571994230003765052623148688106603739786694201383829676928474552777843927299506723149206936913028915475313231388329439859350787355566721100542200320415615485903152946215295311995759719573595368679887113114825505014045084503424009530509444991157859853965885570415824022180952801017941449349958347356887325306792163951399659673827581790962485759369329198084130329114561356646657523328365142013491576496137287593382226295342044454834918043658318329194487559947724081477458018714463796548725057813499040244336567798538848196149244498199452303424561978185336547655271946096079592966688366570429389731020127601165807435919418935966079249602747222642857154797160225980869744143535857848058983776691168420027563688919225476267851259700045267619137447593279666384286574465826492491377167641540417992009607475151642287299766542504745742832727623005929613272278791530010500201900629332008295537871590826365337775503115579406345051573100940240758468313287020637699402592079029859114421365994266862206219144134620009834294395516952253257427164495436021747245852148967185946523256841940418204396609221174437269979737596604801077545344460015352477223840141478956265141028980899496053313275953209289577940694092525290616661215369985075993376289794717597214786878400832024758621037855671133273946327794025528904796232330694606838188744604638774524792567524018298119083626496464061206990945868244339272994608409931204775296680643933140366393496994295802223794520599258117880360615698203438534718276657335176874966517254990863833761195319980816193788536670928504327659572648406813809118891469815170312277372672526137054235516211816430272881225919247642893873072410982592233197325610509120055156658135050806192276291007852821986991321414657555724919926363424116535222657074961890705055311546830666918448591026980622589453080982310227923175006165204256077253057671314864785870536964964290778060312666589899353895389539389

출처

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))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.