2014-11-11 16 views
9

Python ma maksymalną głębokość rekursji, ale nie ma maksymalnej głębokości iteracji. Dlaczego rekursja jest ograniczona? Czy nie byłoby bardziej naturalne traktowanie rekurencji jak iteracji, a nie ograniczanie liczby wywołań rekursywnych?Dlaczego Python ma maksymalną głębokość rekursji?

Pozwolę sobie powiedzieć, że źródłem tego problemu było próbowanie implementacji strumienia (więcej informacji o strumieniach można znaleźć na stronie this question). Na przykład, powiedzmy, że chcemy napisać strumień do produkcji liczb naturalnych:

def stream_accum(s, n): # force the stream to a list of length n 
    def loop(s, acc): 
     if len(acc) == n: 
      return acc 
     hd, tl = s() 
     return loop(tl, acc + [hd]) 
    return loop(s, []) 


def nats(): 
    def loop(n): 
     return n, lambda: loop(n+1) 
    return loop(1) 

Recursywna definicja strumieni jest całkiem interesująca. Jednak myślę, że lepszym/bardziej pytonycznym podejściem byłoby użycie generatorów.

+1

"Atrakcyjne" rozwiązanie rekursywne ma wiele nieprzyjemnych aspektów. Po pierwsze, ma zachowanie O (n ** 2), ponieważ ciągle tworzysz nowe listy, aby je rozszerzyć. Po drugie, jest zbyt skomplikowany, biorąc pod uwagę, że możesz po prostu powtórzyć tworzenie liczb naturalnych. Jest to przykład pisania Pythona tak, jakby był Scheme lub Haskell. Różne języki są dobre w różnych rzeczach. Użyj iteracji. –

Odpowiedz

13

W rzeczywistości jest tu kilka problemów.

Po pierwsze, jak wyjaśnia NPE's answer, Python nie eliminuje wywołań końcowych, dlatego wiele funkcji, które pozwalają na nieograniczoną rekurencję w, powiedzmy, Schemacie są ograniczone w Pythonie.

Po drugie, zgodnie z wyjaśnieniami NPE, połączenia, których nie można wyeliminować, zajmują miejsce na stosie wywołań. I nawet w językach, które obsługują TCE, istnieje wiele funkcji rekursywnych, które nie mogą być traktowane jak iteracja. (Rozważmy naiwną funkcję Fibonacciego, która rekursywnie wywołuje się dwa razy.)

Ale dlaczego stos wywołajacy jest zasobem skończonym? Ramki stosów Pythona mogą co najmniej w zasadzie zostać zaimplementowane na stercie i połączone razem (patrz Stackless, aby uzyskać dowód istnienia tej zasady), a w 64-bitowej pamięci jest miejsce na dużo więcej niż 1000 klatek stosu. (W rzeczywistości, nawet stos C na prawie każdej nowoczesnej platformie może pomieścić znacznie więcej niż 1000 rekursywnych wywołań interpretera w języku Python).

Część przyczyn jest historyczna: giełdowy interpreter Pythona używa stałego stosu C, aby wywołać samą siebie rekurencyjnie za każdym razem, gdy wykonujesz rekursywne wywołanie, i został pierwotnie zaprojektowany dla platform 32-bitowych (a nawet 24- lub 20-bitowych), gdzie ten stos C jest dość mały.

Ale to mogło zostać zmienione, a Python 3.0 byłoby idealnym miejscem, aby to zmienić. Dlaczego więc nie? Ponieważ podjęli świadomą decyzję dotyczącą projektowania języka. W kodzie Pythonic rekursja jest generalnie bardzo płytka (np. Kod taki jak os.walk, który trawi płytkie struktury drzewiaste); jeśli twoja funkcja osiąga głębokość w pobliżu 1000, jest bardziej prawdopodobne, że jest błędem, niż celowym. Limit zostaje. Oczywiście jest to nieco okrężne - jeśli usunęliby limit (a zwłaszcza, gdyby wyeliminowali ogony), głębsza rekurencja stałaby się bardziej idiomatyczna. Ale o to chodzi - Guido nie chce języka, w którym głęboka rekurencja jest idiomatyczna. (I większość społeczności Python się zgadza.)

+0

Bardzo dobra odpowiedź ... dziękuję! – rookie

+0

uwaga: " Możesz zwiększyć maksymalną wysokość stosu wywołań Pythona, wywołując' sys.setrecursionlimit' " – Mayou36

6

Rekursja wymaga miejsca na call stack, która ma ograniczony rozmiar. Kod, który używał zbyt wielu poziomów rekursji, spowoduje błąd o nazwie stack overflow (również rozsławiony przez some obscure website). Python wydaje się ograniczać to (nieco arbitralnie) do 1000 poziomów, ale to can be increased ustawiając sys.setrecursionlimit.

Iteracja używa czegoś podobnego do for-ilop, zaimplementowanego poprzez zwiększenie liczby liczników i warunkowe ustawienie instruction pointer z powrotem na początek pętli. Jest to stałe w pamięci.

16

Nie jest to jednoznacznie związane z pythonem i ma do czynienia z każdym połączeniem zajmującym miejsce na call stack, a rozmiar stosu jest ograniczony.

Sama iteracja nie zajmuje miejsca na stosie i dlatego nie podlega temu ograniczeniu.

Nie każde wywołanie rekurencyjne musi zużywać przestrzeń stosu. Na przykład niektóre języki mogą automatycznie przekształcić tail recursion w iterację. Jednak CPython nie wybiera tego (Does Python optimize tail recursion?).

Możesz zwiększyć maksymalną wysokość stosu wywołania Pythona, wywołując sys.setrecursionlimit.

+2

Rozmiar stosu nie jest tak naprawdę ograniczony. (Z wyjątkiem tego, że sterta jest ograniczona.) Python decyduje się na jej celowe ograniczenie. – abarnert

Powiązane problemy