ここにこの末尾再帰関数があります。
def fib(n, sum):
if n < 1:
return sum
else:
return fib(n-1, sum+n)
c = 998
print(fib(c, 0))
それはn = 997まで働き、そしてそれはただ壊れて「最大再帰深さが比較で超えた」RuntimeError
を吐きます。これは単なるスタックオーバーフローですか。それを回避する方法はありますか?
はい、それはスタックオーバーフローに対する保護です。 Python(あるいはCPythonの実装)は末尾再帰を最適化せず、束縛されていない再帰はスタックオーバーフローを引き起こします。再帰制限は sys.setrecursionlimit
で変更できますが、そうすることは危険です - 標準的な制限は少し控え目ですが、Pythonのスタックフレームはかなり大きくなる可能性があります。
Pythonは関数型言語ではなく、末尾再帰は特に効率的な手法ではありません。可能であれば、アルゴリズムを繰り返し書き直すことが一般的には良い考えです。
あなただけのより高い再帰の深さを設定する必要があるように見えます
sys.setrecursionlimit(1500)
スタックオーバーフローを避けるためです。 Pythonインタプリタは無限の再帰を避けられるように再帰の深さを制限して、スタックオーバーフローを引き起こします。再帰制限(sys.setrecursionlimit)を増やすか、または再帰なしでコードを書き直す。
python Webサイト から:
sys.getrecursionlimit()
再帰制限の現在の値、Pythonインタプリタスタックの最大の深さを返します。この制限により、無限再帰がCスタックのオーバーフローやPythonのクラッシュを防ぐことができます。 setrecursionlimit()で設定できます。
末尾呼び出しの最適化を保証する言語を使用してください。または繰り返しを使用してください。あるいは、 デコレータ で可愛くなります。
頻繁に再帰制限を変更する必要がある場合(例えばプログラミングのパズルを解くとき)、以下のように単純な コンテキストマネージャ を定義することができます。
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
ステートメントの本体から出ると、再帰制限はデフォルト値に戻ります。
私はこれが古い質問であることを認識しています、しかしそれらを読むために、私はこのような問題のために再帰を使うことに対してお勧めします - リストはずっと速く、完全に再帰を避けます。これを次のように実装します。
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を使用してください。)
もちろんフィボナッチ数は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))
コメントしている人は、2**n
のためにO(1)ではなくO(n)と述べています。また、違いは、1つの値しか取得できないのに対し、再帰ではその値までのFibonacci(n)
のすべての値が取得されることです。
resource.setrlimit
はスタックサイズを増やしてセグメンテーション違反を防ぐためにも使わなければなりません
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を増やし続けるなら、あなたのRAMは使い尽くされるでしょう。それはスワップ狂気のためにあなたのコンピュータを停止させるか、あるいはOOM Killerを通してPythonを殺すでしょう。
Bashから、スタック制限(kb)を確認して設定できます。
ulimit -s
ulimit -s 10000
私のデフォルト値は8Mbです。
また見なさい:
Ubuntu 16.10、Python 2.7.12でテスト済み。
私はエラー "最大再帰の深さを超えました"と同様の問題を抱えていた。私は、エラーがos.walkでループしていたディレクトリの破損したファイルによって引き起こされていることを発見しました。この問題の解決に問題があり、ファイルパスを使用している場合は、ファイルが破損している可能性があるので、必ず絞り込んでください。
発電機を使用しますか?
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 から適応したものです。
少数のフィボナッチ数だけを取得したい場合は、行列法を使用できます。
from numpy import matrix
def fib(n):
return (matrix('0 1; 1 1', dtype='object') ** n).item(1)
テンキーが速いべき乗アルゴリズムを使うので速いです。あなたはO(log n)で答えを得ます。整数のみを使用しているので、それはBinetの式よりも優れています。しかし、もしあなたがすべてのフィボナッチ数をnまで上げたいのであれば、暗記することをお勧めします。
多くの人は再帰制限を増やすことが良い解決策であることを勧めますが、それは常に制限があるからではありません。代わりに反復解を使用してください。
def fib(n):
a,b = 1,1
for i in range(n-1):
a,b = b,a+b
return a
print fib(5)
メモを使ってフィボナッチを計算する例を挙げたいと思います。これにより、再帰を使ってかなり大きな数を計算できるようになります。
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))
これはまだ再帰的ですが、以前に計算したフィボナッチ数を再利用するのではなく再利用できるようにする単純なハッシュテーブルを使用します。
@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
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))