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


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
내 경험상, 모듈 sysresource모듈 모두에서 한계를 늘려야합니다 . 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 커널은이를 분할 오류로 만듭니다.

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

파이썬은 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**nsquaring으로 지수를 사용하여 효과적으로 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.