У меня есть следующий кусок кода, который завершается с ошибкой:
RuntimeError: превышена максимальная глубина рекурсии
Я попытался переписать это, чтобы учесть оптимизацию хвостовой рекурсии (TCO). Я считаю, что этот код должен был быть успешным, если бы имелась ТШО.
def trisum(n, csum):
if n == 0:
return csum
else:
return trisum(n - 1, csum + n)
print(trisum(1000, 0))
Должен ли я заключить, что Python не выполняет никаких типов TCO, или мне просто нужно определить его по-другому?
python
recursion
stack
stack-overflow
tail-recursion
Джордан Мак
источник
источник
return func(...)
(явному или неявному), независимо от того, рекурсивен он или нет. TCO является надлежащим расширенным набором TRE и более полезен (например, он делает возможным стиль передачи продолжения, чего не может TRE), и не намного сложнее в реализации.foo
изнутри звонкаfoo
изнутриfoo
внутрь звонкаfoo
... Я не думаю, что потеря этой полезной информации будет потеряна.Ответы:
Нет, и никогда не будет, так как Гвидо ван Россум предпочитает иметь надлежащие трассировки:
Исключение рекурсии хвоста (2009-04-22)
Заключительные слова о вызовах в хвост (2009-04-27)
Вы можете вручную исключить рекурсию с помощью преобразования, подобного этому:
источник
from operator import add; reduce(add, xrange(n + 1), csum)
:?Я опубликовал модуль, выполняющий оптимизацию хвостового вызова (обрабатывая как хвостовую рекурсию, так и стиль продолжения продолжения): https://github.com/baruchel/tco
Оптимизация хвостовой рекурсии в Python
Часто утверждается, что хвостовая рекурсия не подходит к пиктоническому способу кодирования, и что не нужно заботиться о том, как встроить ее в цикл. Я не хочу спорить с этой точкой зрения; однако иногда мне нравится пробовать или реализовывать новые идеи в виде хвостовых рекурсивных функций, а не с помощью циклов по разным причинам (фокусируясь на идее, а не на процессе, имея на экране двадцать коротких функций одновременно, а не только три «Pythonic») функции, работающие в интерактивном сеансе вместо редактирования моего кода и т. д.).
Оптимизация хвостовой рекурсии в Python на самом деле довольно проста. Хотя говорят, что это невозможно или очень сложно, я думаю, что этого можно достичь с помощью элегантных, коротких и общих решений; Я даже думаю, что большинство из этих решений не используют функции Python иначе, чем должны. Чистые лямбда-выражения, работающие вместе со стандартными циклами, приводят к быстрым, эффективным и полностью используемым инструментам для реализации оптимизации хвостовой рекурсии.
Для личного удобства я написал небольшой модуль, реализующий такую оптимизацию двумя различными способами. Я хотел бы обсудить здесь две мои основные функции.
Чистый путь: модификация Y комбинатора
Y комбинатор хорошо известен; он позволяет рекурсивно использовать лямбда-функции, но сам по себе не позволяет встраивать рекурсивные вызовы в цикл. Лямбда-исчисление само по себе не может сделать такую вещь. Небольшое изменение в Y-комбинаторе, однако, может защитить рекурсивный вызов для фактической оценки. Таким образом, оценка может быть отложена.
Вот знаменитое выражение для Y комбинатора:
С очень небольшим изменением я мог получить:
Вместо того, чтобы вызывать себя, функция f теперь возвращает функцию, выполняющую тот же самый вызов, но, поскольку она ее возвращает, оценку можно выполнить позже извне.
Мой код:
Функцию можно использовать следующим образом; Вот два примера с хвостовой рекурсивной версией факториала и Фибоначчи:
Очевидно, глубина рекурсии больше не является проблемой:
Это, конечно, единственная реальная цель функции.
С этой оптимизацией нельзя сделать только одно: ее нельзя использовать с хвостовой рекурсивной функцией, выполняющей оценку другой функции (это связано с тем фактом, что все возвращаемые объекты с возможностью вызова обрабатываются как дальнейшие рекурсивные вызовы без различия). Поскольку мне обычно не нужна такая функция, я очень доволен приведенным выше кодом. Однако, чтобы предоставить более общий модуль, я подумал немного больше, чтобы найти обходной путь для этой проблемы (см. Следующий раздел).
Что касается скорости этого процесса (который, однако, не является реальной проблемой), то он довольно хорош; хвостовые рекурсивные функции даже оцениваются гораздо быстрее, чем в следующем коде с использованием более простых выражений:
Я думаю, что вычисление одного выражения, даже сложного, происходит намного быстрее, чем вычисление нескольких простых выражений, что имеет место во второй версии. Я не сохранил эту новую функцию в своем модуле и не вижу обстоятельств, где ее можно было бы использовать вместо «официальной».
Продолжение прохождения стиля с исключениями
Вот более общая функция; он способен обрабатывать все хвостовые рекурсивные функции, включая те, которые возвращают другие функции. Рекурсивные вызовы распознаются из других возвращаемых значений с помощью исключений. Это решение медленнее, чем предыдущее; более быстрый код, вероятно, можно было бы написать, используя некоторые специальные значения, поскольку «флаги» обнаруживаются в основном цикле, но мне не нравится идея использования специальных значений или внутренних ключевых слов. Существует некоторая забавная интерпретация использования исключений: если Python не любит хвост-рекурсивные вызовы, то должно возникать исключение, когда происходит хвост-рекурсивный вызов, и Pythonic будет перехватывать исключение, чтобы найти некоторый чистый решение, которое на самом деле то, что происходит здесь ...
Теперь все функции могут быть использованы. В следующем примере
f(n)
оценивается функция тождества для любого положительного значения n:Конечно, можно утверждать, что исключения не предназначены для преднамеренного перенаправления интерпретатора (как своего рода
goto
утверждение или, скорее, скорее как стиль передачи продолжения), что я должен признать. Но, опять же, я нахожу забавной идею использованияtry
одной строки, являющейсяreturn
оператором: мы пытаемся что-то вернуть (нормальное поведение), но мы не можем этого сделать из-за рекурсивного вызова (исключение).Первоначальный ответ (2013-08-29).
Я написал очень маленький плагин для обработки хвостовой рекурсии. Вы можете найти это с моими объяснениями там: https://groups.google.com/forum/?hl=fr#!topic/comp.lang.python/dIsnJ2BoBKs
Он может встроить лямбда-функцию, написанную в стиле хвостовой рекурсии, в другую функцию, которая будет оценивать ее как цикл.
Самая интересная особенность этой маленькой функции, по моему скромному мнению, заключается в том, что эта функция основана не на грязном программировании, а на простом лямбда-исчислении: поведение функции меняется на другое при вставке в другую лямбда-функцию, которая выглядит очень похоже на Y комбинатор.
источник
bet0
использоваться в качестве декоратора для методов класса?def
синтаксис для своих функций, и фактически последний пример выше опирается на условие. В моем посте baruchel.github.io/python/2015/11/07/… вы можете увидеть абзац, начинающийся с «Конечно, вы можете возразить, что никто не напишет такой код», где я привожу пример с обычным синтаксисом определения. Что касается второй части вашего вопроса, я должен подумать об этом немного больше, так как я не тратил время на это некоторое время. С уважением.Слово Гвидо находится по адресу http://neopythonic.blogspot.co.uk/2009/04/tail-recursion-elmination.html
источник
CPython не поддерживает и, вероятно, никогда не будет поддерживать оптимизацию хвостовых вызовов на основе Гвидо ван Россума высказываний по этому вопросу.
Я слышал аргументы, что это затрудняет отладку из-за того, как он изменяет трассировку стека.
источник
Попробуйте экспериментальную реализацию TCO для определения размера.
источник
Помимо оптимизации хвостовой рекурсии, вы можете установить глубину рекурсии вручную:
источник