Python'daki maksimum özyineleme derinliği nedir ve nasıl artırılır?


421

Burada bu kuyruk özyinelemeli işlevi var:

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

Kadar çalışır n=997, sadece kırılır ve tükürür a RecursionError: maximum recursion depth exceeded in comparison. Bu sadece bir yığın taşması mı? Etrafında dolaşmanın bir yolu var mı?



9
bellek , yığın boyutunu artırmak yerine önceden hesaplanmış değerleri sonlandırarak işlevinizi hızlandırabilir ve etkin özyinelemeli derinliğini artırabilir.
Cyoce

2
Özyineleme sınırı genellikle 1000'dir.
Boris

1
@tonix yorumlayıcı yığın çerçeve (ekler line <n>, in <module>yığın içinde eser miktarda) ve bu kod 2 yığın çerçeveleri alır n=1(temel durum olduğu için n < 1çok için, n=1hala recurses). Ve sanırım özyineleme sınırı kapsayıcı değildir, çünkü "1000'e çarptığınızda hata" 1000 (1001) değerini aşarsanız "değil" hatası. 997 + 21000'den az olduğu için işe yarıyor 998 + 2çünkü sınıra ulaşıyor.
Boris

1
@tonix no. recursive_function(997)çalışır, kırılır 998. Aradığınızda recursive_function(998)999 yığın çerçeve kullanır ve yorumlayıcı tarafından 1 çerçeve eklenir (çünkü kodunuz her zaman üst düzey modülün parçasıymış gibi çalışır), bu da 1000 sınırına ulaşmasını sağlar.
Boris

Yanıtlar:


469

Bir yığın taşmasına karşı bir koruyucudur, evet. Python (veya daha doğrusu CPython uygulaması) kuyruk yinelemesini optimize etmez ve dizginsiz yineleme yığın taşmalarına neden olur. Yineleme sınırını ile kontrol edebilir ve yineleme sınırını ile sys.getrecursionlimitdeğiştirebilirsiniz sys.setrecursionlimit, ancak bunu yapmak tehlikelidir - standart sınır biraz muhafazakar, ancak Python yığın çerçeveleri oldukça büyük olabilir.

Python işlevsel bir dil değildir ve kuyruk özyineleme özellikle etkili bir teknik değildir. Mümkünse algoritmayı tekrar tekrar yazmak genellikle daha iyi bir fikirdir.


4
Deneyimlerime göre, hem modüllerde hem sysde resourcemodüllerde limiti artırmanız gerekiyor : stackoverflow.com/a/16248113/205521
Thomas Ahle

3
yinelemeli bir sürüme dönüştürmek için bir taktik olarak, bir kuyruk çağrı optimizasyonu dekoratör kullanılabilir
jfs

3
İşletim Sistemi üst sınırınızı öğrenmek için svn.python.org/projects/python/trunk/Tools/scripts/… adresini kullanabilirsiniz
Ullullu

8
Kaynağında ilgilenen kişiler için, varsayılan yineleme sınırına 1000 ayarlanır hg.python.org/cpython/file/tip/Python/ceval.c#l691 ve en API kullanılarak değiştirilebilir hg.python.org/cpython /file/tip/Python/sysmodule.c#l643 ve bu da hg.python.org/cpython/file/tip/Python/ceval.c#l703
Pramod

16
Kuyruk özyineleme, kendisi için optimize edilmiş bir programlama dilinde mükemmel verimli bir tekniktir. Doğru türde bir sorun için, yinelemeli bir yinelemeli uygulama olabilir. Cevap muhtemelen "özellikle Python'da" anlamına gelir, ancak böyle değildir
Peter R

135

Görünüşe göre daha yüksek bir özyineleme derinliği ayarlamanız gerekiyor :

import sys
sys.setrecursionlimit(1500)

Benim durumumda temel durumda dönüş ifadesini unuttum ve 1000'i aşmaya devam etti. Python bu istisnayı atmaya başladı ve hayrete düştüm, çünkü hayır hakkında emindim. yığınları onu çalıştırmak için yaratacaktır.
vijayraj34

sys.setrecursionlimit (50) veya küçük bir miktar, programınız özyinelemeye giriyorsa ve hata mesajının aynı metnin sayfaları ve sayfaları OLMAMASI istiyorsanız yararlıdır. (Benim) kötü özyinelemeli kod hata ayıklama sırasında bu çok yararlı buldum.
peawormsworth

56

Bir yığın taşmasını önlemek içindir. Python yorumlayıcısı, sonsuz yinelemeleri önlemenize yardımcı olacak ve yığın taşmasına neden olacak yineleme derinliklerini sınırlar. Özyineleme sınırını ( sys.setrecursionlimit) artırmayı veya özyineleme olmadan kodunuzu yeniden yazmayı deneyin .

Gönderen Python belgelerinde :

sys.getrecursionlimit()

Özyineleme sınırının geçerli değerini, Python yorumlayıcı yığının maksimum derinliğini döndürür. Bu sınır, sonsuz özyinelemenin C yığınının taşmasına ve Python'un çökmesine neden olmasını önler. Tarafından ayarlanabilir setrecursionlimit().


Anaconda x64, Windows'taki 3.5 Python'da varsayılan sınır 1000'dir.
Guillaume Chevalier

30

Yineleme sınırını sık sık değiştirmeniz gerekiyorsa (örn. Programlama bulmacalarını çözerken) aşağıdaki gibi basit bir bağlam yöneticisi tanımlayabilirsiniz :

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)

Ardından, özel bir limite sahip bir işlevi çağırmak için şunları yapabilirsiniz:

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

İfadenin gövdesinden çıkışta withözyineleme sınırı varsayılan değere geri yüklenir.


Ayrıca , sürecin yineleme sınırını ile artırmakresource istersiniz . Bu olmadan, bir Segmentasyon Hatası alırsınız ve setrecursionlimitçok yüksekseniz tüm Python süreci çökecektir ve yeni sınırı kullanmaya çalışın (yukarıdaki basit işlevle ~ 30.000 yığın kareye dönüşen yaklaşık 8 megabayt yığın kare, dizüstü bilgisayarım).
Boris

16

Kuyruk çağrısı optimizasyonunu garanti eden bir dil kullanın. Veya yinelemeyi kullanın. Alternatif olarak, dekoratörler ile sevimli olun .


36
Bu daha çok bebeği banyo suyuyla dışarı atıyor.
Russell Borogove

3
@Russell: Sunduğum seçeneklerden sadece biri bunu tavsiye ediyor.
Marcelo Cantos

"Dekoratörler ile şirin olun" tam olarak bir seçenek değil.
Bay B

@ Mr.B ulimit -syığın çerçevelerinden daha fazlasına ihtiyacınız yoksa , evet stackoverflow.com/a/50120316
Boris

14

resource.setrlimit ayrıca yığın boyutunu artırmak ve segfaultu önlemek için kullanılmalıdır

Linux çekirdeği süreç yığınını sınırlar .

Python yerel değişkenleri yorumlayıcının yığınında saklar ve böylece özyineleme yorumlayıcının yığın alanını kaplar.

Python yorumlayıcısı yığın sınırını aşmaya çalışırsa, Linux çekirdeği bölümleme hatası yapar.

Yığın sınırı boyutu getrlimitve setrlimitsistem çağrılarıyla kontrol edilir .

Python bu sistem çağrılarına resourcemodül üzerinden erişim sağlar .

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)

Tabii ki, ulimit artmaya devam ederseniz, RAM'iniz tükenir, bu da takas deliliği nedeniyle bilgisayarınızı durduracak veya OOM Killer aracılığıyla Python'u öldürecektir.

Bash'den yığın sınırını (kb cinsinden) görebilir ve ayarlayabilirsiniz:

ulimit -s
ulimit -s 10000

Benim için varsayılan değer 8Mb'dir.

Ayrıca bakınız:

Ubuntu 16.10, Python 2.7.12'de test edildi.


1
Stack Clash düzeltmelerinden rlimit_stacksonra ayarlanmaya çalışmak hataya veya ilgili sorunlara neden olabilir. Ayrıca bkz.Kırmızı Şapka Sayı 1463241
jww

Bunu (Python kaynak kısmı) Profesör Tim Roughgarden'in ortalama (dev) veri seti üzerinde Kosaraju algoritmasını uygulamama yardımcı olmak için kullandım. Uygulamam küçük setler üzerinde çalıştı, kesinlikle büyük bir veri kümesiyle ilgili sorun özyineleme / yığın sınırıydı ... Yoksa değil mi? Evet, öyleydi! Teşekkürler!
nilo

9

Bunun eski bir soru olduğunun farkındayım, ancak okuyanlar için, bu tür problemler için özyineleme kullanılmasını öneriyorum - listeler çok daha hızlı ve özyinelemeyi tamamen önlüyor. Bunu şu şekilde uygularım:

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

(Fibonacci dizinizi 1 yerine 0'dan saymaya başlarsanız xrange içinde n + 1 kullanın.)


13
O (1) kullanabildiğinizde neden O (n) boşluğunu kullanıyorsunuz?
Janus Troelsen

11
O (n) boşluk yorumunun kafa karıştırıcı olması durumunda: bir liste kullanmayın. İhtiyacınız olan tek şey n. Değer olduğunda liste tüm değerleri tutacaktır. Basit bir algoritma, son iki fibonacci numarasını tutmak ve ihtiyacınız olana ulaşana kadar bunları eklemek olacaktır. Daha iyi algoritmalar da var.
Milimetrik

3
@Mathime: Python 3'te xrangebasitçe çağrılır range.
Eric O Lebigot

1
@EOL Bunun farkındayım
Mathime

7
@Matime Bu yorumları okuyanlar için bazı şeyleri açık yapıyordum.
Eric O Lebigot

9

Tabii ki Fibonacci sayıları Binet formülü uygulanarak O (n) cinsinden hesaplanabilir:

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

Yorumcuların belirttiği gibi, çünkü O (1) değil, O (n) 2**n. Ayrıca bir fark, tek bir değer elde etmenizdir, özyineleme Fibonacci(n)ile bu değere kadar olan tüm değerleri alırsınız .


8
Python'da uzun bir maksimum boyut yoktur.
15:14

8
nKayan nokta belirsizliği nedeniyle bunun daha büyük başarısız olduğunu belirtmek gerekir - 1 ulp arasındaki fark (1+sqrt(5))**nve (1+sqrt(5))**(n+1)daha az olur, böylece yanlış sonuçlar almaya başlarsınız.

2
NumPy'de aslında büyük bir tam sayı yok…
Eric O Lebigot

@Mego Ne? Bu (1+sqrt(5))**nve arasındaki fark ((1+sqrt(5))**n)+11 ulp'den az olur! (küçük yazım hatası) Ayrıca, {@} rwst Bu O (1) değil! Hesaplama 2**nen az O (n) zaman alır.
user202729

3
@ user202729 Bu doğru değil, hesaplama 2**n, kareleme yoluyla Exponentiattion kullanarak etkili O (log (n)) .
Sam

6

"Maks özyineleme derinliği aşıldı" hatasıyla benzer bir sorun yaşadım. Hatanın üzerinde dolaştığım dizindeki bozuk bir dosya tarafından tetiklendiğini keşfettim os.walk. Bu sorunu çözmede sorun yaşıyorsanız ve dosya yollarıyla çalışıyorsanız, bozuk bir dosya olabileceğinden daralttığınızdan emin olun.


2
OP kodunu verir ve deneyi istediği gibi tekrarlanabilir. Bozuk dosyaları içermez.
T. Verron

5
Haklısın, ama cevabım OP'ye yönelik değil, çünkü bu dört yıl önceydi. Cevabım, MRD hataları olanlara dolaylı olarak bozuk dosyaların neden olduğu - bu ilk arama sonuçlarından biri olduğu için yardımcı olmayı amaçlıyor. Oy verildiği için birine yardım etti. Aşağı oy için teşekkürler.
Tyler

2
Sorunumu ararken "max özyineleme derinliği" izleme geri bozuk bir dosyaya bağlandığım her yerde bu tek şeydi. Teşekkürler!
Jeff

5

Sadece birkaç Fibonacci numarası almak istiyorsanız, matris yöntemini kullanabilirsiniz.

from numpy import matrix

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

Numpy hızlı üs alma algoritması kullandığından hızlıdır. Cevap O (log n). Ve Binet'in formülünden daha iyi çünkü sadece tamsayılar kullanıyor. Ancak tüm Fibonacci sayılarını n'ye kadar istiyorsanız, ezberleyerek bunu yapmak daha iyidir.


Ne yazık ki en rekabetçi programlama hakimlerinde numpy kullanamazsınız. Ama evet efendim, çözümünüz benim favorim. Matris çözmeyi bazı problemler için kullandım. Çok büyük bir fibonacci sayısına ihtiyacınız olduğunda ve bir modül kullanamadığınızda en iyi çözümdür. Bir modül kullanmanıza izin verilirse, pisano dönemi bunu yapmanın daha iyi bir yoludur.
mentatkgs

4

Jeneratörler mi kullanıyorsunuz?

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

uyarlanmış fib () işlevi: http://intermediatepythonista.com/python-generators


1
bir değişkene jeneratör atamak zorunda olmanın nedeni [fibs().next() for ...]her seferinde yeni bir jeneratör üretmesidir.
tox123

3

@Alex'in önerdiği gibi , bunu tekrarlayan yerine sıralı olarak yapmak için bir jeneratör işlevi kullanabilirsiniz .

Sorunuzdaki kodun karşılığı:

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

Birçoğu, özyineleme sınırının artırılmasının iyi bir çözüm olmasını önerir, ancak bunun nedeni her zaman sınırlama olacaktır. Bunun yerine yinelemeli bir çözüm kullanın.

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

1

Fibonacci'yi hesaplamak için not kullanmanın bir örneğini vermek istedim, çünkü bu özyineleme kullanarak önemli ölçüde daha büyük sayıları hesaplamanıza izin verecektir:

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

Bu hala özyinelemeli, ancak daha önce hesaplanmış Fibonacci sayılarının tekrar yapmak yerine yeniden kullanılmasına izin veren basit bir hashtable kullanır.


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
Aynı cevap birçok kez verilmiştir. Lütfen kaldırın.
ZF007

0

Bunu @lru_cachedekoratör ve setrecursionlimit()yöntem kullanarak yapabiliriz :

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

Çıktı

3002468761178461090995494179715025648692747937490792943468375429502230242942284835863402333575216217865811638730389352239181342307756720414619391217798542575996541081060501905302157019002614964717310808809478675602711440361241500732699145834377856326394037071666274321657305320804055307021019793251762830816701587386994888032362232198219843549865275880699612359275125243457132496772854886508703396643365042454333009802006384286859581649296390803003232654898464561589234445139863242606285711591746222880807391057211912655818499798720987302540712067959840802106849776547522247429904618357394771725653253559346195282601285019169360207355179223814857106405285007997547692546378757062999581657867188420995770650565521377874333085963123444258953052751461206977615079511435862879678439081175536265576977106865074099512897235100538241196445815568291377846656352979228098911566675956525644182645608178603837172227838896725425605719942300037650526231486881066037397866942013838296769284745527778439272995067231492069369130289154753132313883294398593507873555667211005422003204156154859031529462152953119957597195735953686798871131148255050140450845034240095305094449911578598539658855704158240221809528010179414493499583473568873253067921639513996596738275817909624857593693291980841303291145613566466575233283651420134915764961372875933822262953420444548349180436583183291944875599477240814774580187144637965487250578134990402443365677985388481961492444981994523034245619781853365476552719460960795929666883665704293897310201276011658074359194189359660792496027472226428571547971602259808697441435358578480589837766911684200275636889192254762678512597000452676191374475932796663842865744658264924913771676415404179920096074751516422872997665425047457428327276230059296132722787915300105002019006293320082955378715908263653377755031155794063450515731009402407584683132870206376994025920790298591144213659942668622062191441346200098342943955169522532574271644954360217472458521489671859465232568419404182043966092211744372699797375966048010775453444600153524772238401414789562651410289808994960533132759532092895779406940925252906166612153699850759933762897947175972147868784008320247586210378556711332739463277940255289047962323306946068381887446046387745247925675240182981190836264964640612069909458682443392729946084099312047752966806439331403663934969942958022237945205992581178803606156982034385347182766573351768749665172549908638337611953199808161937885366709285043276595726484068138091188914698151703122773726725261370542355162118164302728812259192476428938730724109825922331973256105091200551566581350508061922762910078528219869913214146575557249199263634241165352226570749618907050553115468306669184485910269806225894530809823102279231750061652042560772530576713148647858705369649642907780603247428680176236527220826640665659902650188140474762163503557640566711903907798932853656216227739411210513756695569391593763704981001125

Kaynak

functools Instagram Hesabındaki Resim ve Videoları lru_cache


0

Dinamik programlama aşağıdan yukarıya yaklaşımının bir varyasyonunu da kullanabiliriz

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))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.