Mam następujący fragment kodu, który nie działa z następującym błędem:
RuntimeError: przekroczona maksymalna głębokość rekurencji
Próbowałem przepisać to, aby umożliwić optymalizację rekurencji ogona (TCO). Uważam, że ten kod powinien był się udać, gdyby miało miejsce TCO.
def trisum(n, csum):
if n == 0:
return csum
else:
return trisum(n - 1, csum + n)
print(trisum(1000, 0))
Czy powinienem dojść do wniosku, że Python nie wykonuje żadnego rodzaju TCO, czy po prostu muszę go definiować inaczej?
python
recursion
stack
stack-overflow
tail-recursion
Jordan Mack
źródło
źródło
return func(...)
(jawnego lub niejawnego), czy to rekurencyjnego, czy nie. TCO jest właściwym nadzbiorem TRE i jest bardziej użyteczny (np. Umożliwia kontynuację stylu przechodzenia, czego TRE nie potrafi) i nie jest trudniejszy do wdrożenia.foo
z wewnątrz, połączeniefoo
z wnętrza dofoo
środka, połączenie zfoo
... Nie sądzę, aby jakiekolwiek przydatne informacje zostałyby utracone po utracie tego.Odpowiedzi:
Nie, i nigdy nie będzie tak, ponieważ Guido van Rossum woli mieć odpowiednie ślady:
Eliminacja rekurencji ogona (2009-04-22)
Ostatnie słowa na ogonach (27.04.2009)
Możesz ręcznie wyeliminować rekurencję za pomocą takiej transformacji:
źródło
from operator import add; reduce(add, xrange(n + 1), csum)
:?Opublikowałem moduł przeprowadzający optymalizację wywołania ogona (obsługujący zarówno styl rekurencji, jak i przekazywanie kontynuacji): https://github.com/baruchel/tco
Optymalizacja rekurencji ogona w Pythonie
Często twierdzono, że rekurencja ogona nie pasuje do Pythońskiego sposobu kodowania i że nie należy dbać o to, jak osadzić go w pętli. Nie chcę się kłócić z tym punktem widzenia; czasem jednak lubię próbować lub wdrażać nowe pomysły jako funkcje rekurencyjne zamiast z pętlami z różnych powodów (skupiając się raczej na pomyśle niż na procesie, mając na ekranie dwadzieścia krótkich funkcji zamiast tylko trzech „pytonicznych” funkcje, praca w interaktywnej sesji zamiast edycji mojego kodu itp.).
Optymalizacja rekurencji ogona w Pythonie jest w rzeczywistości dość łatwa. Chociaż mówi się, że jest to niemożliwe lub bardzo trudne, myślę, że można to osiągnąć za pomocą eleganckich, krótkich i ogólnych rozwiązań; Myślę nawet, że większość tych rozwiązań nie używa funkcji Pythona inaczej niż powinny. Czyste wyrażenia lambda współpracujące z bardzo standardowymi pętlami prowadzą do szybkich, wydajnych iw pełni użytecznych narzędzi do wdrażania optymalizacji rekurencji ogona.
Dla osobistej wygody napisałem mały moduł implementujący taką optymalizację na dwa różne sposoby. Chciałbym tutaj omówić moje dwie główne funkcje.
Prosty sposób: modyfikowanie kombinatora Y.
Syntezatora Y jest dobrze znane; pozwala na używanie funkcji lambda w sposób rekurencyjny, ale nie pozwala na osadzanie wywołań rekurencyjnych w pętli. Sam rachunek Lambda nie może zrobić czegoś takiego. Niewielka zmiana w kombinatorze Y może jednak ochronić wywołanie rekurencyjne, które ma zostać faktycznie ocenione. Ocena może zatem zostać opóźniona.
Oto słynne wyrażenie dla kombinatora Y:
Z niewielką zmianą mogłem uzyskać:
Zamiast wywoływać się sama, funkcja f zwraca teraz funkcję wykonującą to samo wywołanie, ale ponieważ ją zwraca, oceny można dokonać później z zewnątrz.
Mój kod to:
Z funkcji można skorzystać w następujący sposób; oto dwa przykłady z rekurencyjnymi wersjami silni i Fibonacciego:
Oczywiście głębokość rekurencji nie jest już problemem:
Jest to oczywiście jedyny prawdziwy cel tej funkcji.
Z tą optymalizacją nie można zrobić tylko jednej rzeczy: nie można jej używać z funkcją rekurencyjną ogonową przetwarzającą na inną funkcję (wynika to z faktu, że wszystkie zwracane obiekty, które można wywoływać, są obsługiwane jako kolejne rekurencyjne wywołania bez rozróżnienia). Ponieważ zwykle nie potrzebuję takiej funkcji, jestem bardzo zadowolony z powyższego kodu. Jednak w celu zapewnienia bardziej ogólnego modułu pomyślałem trochę więcej, aby znaleźć obejście tego problemu (patrz następny rozdział).
Jeśli chodzi o szybkość tego procesu (co nie jest jednak prawdziwym problemem), okazuje się, że jest całkiem dobry; funkcje rekurencyjne są sprawdzane nawet znacznie szybciej niż w przypadku następującego kodu przy użyciu prostszych wyrażeń:
Myślę, że ocena jednego wyrażenia, nawet skomplikowanego, jest znacznie szybsza niż ocena kilku prostych wyrażeń, co ma miejsce w drugiej wersji. Nie zachowałem tej nowej funkcji w moim module i nie widzę żadnych okoliczności, w których można by jej użyć, a nie „oficjalnej”.
Styl kontynuacji przejścia z wyjątkami
Oto bardziej ogólna funkcja; jest w stanie obsłużyć wszystkie funkcje rekurencyjne ogona, w tym te zwracające inne funkcje. Połączenia rekurencyjne są rozpoznawane na podstawie innych wartości zwracanych na podstawie wyjątków. To rozwiązanie jest wolniejsze niż poprzednie; szybszy kod może być prawdopodobnie napisany przy użyciu niektórych specjalnych wartości jako „flag” wykrywanych w głównej pętli, ale nie podoba mi się pomysł używania specjalnych wartości lub wewnętrznych słów kluczowych. Istnieje zabawna interpretacja używania wyjątków: jeśli Python nie lubi wywołań rekurencyjnych, należy zgłosić wyjątek, gdy nastąpi wywołanie rekurencyjne, a sposobem Pythona będzie przechwycenie wyjątku w celu znalezienia czystego rozwiązanie, które tak naprawdę dzieje się tutaj ...
Teraz można korzystać ze wszystkich funkcji. W poniższym przykładzie
f(n)
jest oceniane do funkcji tożsamości dla dowolnej dodatniej wartości n:Oczywiście można argumentować, że wyjątki nie są przeznaczone do celowego przekierowywania tłumacza (jako rodzaj
goto
stwierdzenia lub raczej raczej stylu kontynuacji przejścia), co muszę przyznać. Ponownie uważam za zabawny pomysł używaniatry
pojedynczego wiersza jakoreturn
stwierdzenia: próbujemy coś zwrócić (normalne zachowanie), ale nie możemy tego zrobić z powodu wystąpienia wywołania rekurencyjnego (wyjątek).Pierwsza odpowiedź (29.08.2013).
Napisałem bardzo małą wtyczkę do obsługi rekurencji ogona. Możesz go znaleźć z moimi wyjaśnieniami tam: https://groups.google.com/forum/?hl=fr#!topic/comp.lang.python/dIsnJ2BoBKs
Może osadzić funkcję lambda zapisaną stylem rekurencji ogona w innej funkcji, która oceni ją jako pętlę.
Moim skromnym zdaniem najciekawszą cechą tej małej funkcji jest to, że funkcja ta nie polega na jakimś brudnym hacku programistycznym, ale na rachunku lambda: zachowanie funkcji zmienia się na inne po wstawieniu do innej funkcji lambda, która wygląda bardzo podobnie do kombinatora Y.
źródło
bet0
być również używana jako dekorator dla metod klasowych?def
składni dla swoich funkcji, a tak naprawdę ostatni przykład powyżej zależy od warunku. W moim poście baruchel.github.io/python/2015/11/07/ ... możesz zobaczyć akapit zaczynający się od „Oczywiście, że możesz sprzeciwić się, że nikt nie napisałby takiego kodu”, w którym podam przykład ze zwykłą składnią definicji. Jeśli chodzi o drugą część twojego pytania, muszę się nad tym trochę zastanowić, ponieważ nie spędziłem w tym czasie czasu. Pozdrowienia.Słowo Guido znajduje się na stronie http://neopythonic.blogspot.co.uk/2009/04/tail-recursion-elimination.html
źródło
CPython nie wspiera i prawdopodobnie nigdy nie będzie wspierał optymalizacji ogonów na podstawie wypowiedzi Guido van Rossuma na ten temat.
Słyszałem argumenty, że utrudnia to debugowanie z powodu tego, jak modyfikuje ślad stosu.
źródło
Wypróbuj eksperymentalną implementację makropy TCO dla rozmiaru.
źródło
Oprócz optymalizacji rekurencji ogona, możesz ręcznie ustawić głębokość rekurencji poprzez:
źródło