Оптимизирует ли Python хвостовую рекурсию?
У меня есть следующий фрагмент кода, который завершается со следующей ошибкой:
RuntimeError: максимальная глубина рекурсии превысил
Я попытался переписать это, чтобы обеспечить оптимизацию хвостовой рекурсии (TCO). Я считаю, что этот код должен был быть успешным, если бы имел место TCO.
def trisum(n, csum):
if n == 0:
return csum
else:
return trisum(n - 1, csum + n)
print(trisum(1000, 0))
должен ли я заключить, что Python не делает никакого типа TCO, или мне просто нужно определить его по-другому?
6 ответов:
нет, и это никогда не будет, так как Гвидо предпочитает иметь правильные трассировки
http://neopythonic.blogspot.com.au/2009/04/tail-recursion-elimination.html
http://neopythonic.blogspot.com.au/2009/04/final-words-on-tail-calls.html
вы можете вручную устранить рекурсию с помощью такого преобразования
>>> def trisum(n, csum): ... while True: # change recursion to a while loop ... if n == 0: ... return csum ... n, csum = n - 1, csum + n # update parameters instead of tail recursion >>> trisum(1000,0) 500500
Edit (2015-07-02):со временем мой ответ стал довольно популярным, и поскольку изначально это была скорее Ссылка, чем что-либо еще, я решил взять некоторое время и полностью переписать его (однако первоначальный ответ можно найти в конце).
Edit (2015-07-12): я наконец опубликовал модуль, выполняющий оптимизацию хвостового вызова (обработка как хвостовой рекурсии, так и стиля продолжения): https://github.com/baruchel/tco
оптимизация хвостовой рекурсии в Python
неоднократно утверждалось, что хвост-рекурсия не устраивает подходящие для Python способ кодирования и это не должно заботиться о том, как встроить его в цикл. Я не хочу с тобой спорить. эта точка зрения; Иногда, однако, мне нравится пробовать или реализовывать новые идеи как хвост-рекурсивные функции, а не с петлями по разным причинам (с упором на идея, а не процесс, имея двадцать коротких функций на моем экране в том же время, а не только три "весть" функции, работая в интерактивной сессии, а чем редактировать мой код, и т. д.).
оптимизация хвостовой рекурсии в Python на самом деле довольно проста. Хотя это, как говорят, невозможно или очень сложно, я думаю, что это может быть достигнуто с помощью элегантных, коротких и общих решений; я даже думаю, что большинство из этих решений не используют возможностей Python иначе, чем они должны. Чистые лямбда-выражения работа вместе с очень стандартными петлями водит к быстрому, эффективному и полностью применимые инструменты для реализации оптимизации хвостовой рекурсии.
в качестве личного удобства я написал небольшой модуль, реализующий такую оптимизацию двумя разными способами. Я хотел бы обсудить здесь мои две основные функции.
чистый способ: изменение комбинатора Y
комбинатор Y хорошо известен; он позволяет использовать лямбда-функции в рекурсивном порядке, но это не позволяет сам по себе вставлять рекурсивные вызовы в цикл. Лямбда одно только исчисление не может этого сделать. Однако небольшое изменение в Y-комбинатор может защитить рекурсивный вызов, который будет фактически оценен. Таким образом, оценка может быть отложена.
вот знаменитое выражение для y-комбинатора:
lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))С очень небольшим изменением, я мог бы получить:
lambda f: (lambda x: x(x))(lambda y: f(lambda *args: lambda: y(y)(*args)))вместо того, чтобы вызывать себя, функция f теперь возвращает функцию, выполняющую Очень же вызов, но так как он возвращает его, оценка может быть выполнена позже извне.
мой код:
def bet(func): b = (lambda f: (lambda x: x(x))(lambda y: f(lambda *args: lambda: y(y)(*args))))(func) def wrapper(*args): out = b(*args) while callable(out): out = out() return out return wrapperэта функция может быть использована следующим образом; вот два примера с хвостовой рекурсии версии факториала и Фибоначчи:
>>> from recursion import * >>> fac = bet( lambda f: lambda n, a: a if not n else f(n-1,a*n) ) >>> fac(5,1) 120 >>> fibo = bet( lambda f: lambda n,p,q: p if not n else f(n-1,q,p+q) ) >>> fibo(10,0,1) 55очевидно, что глубина рекурсии больше не является проблемой:
>>> bet( lambda f: lambda n: 42 if not n else f(n-1) )(50000) 42это, конечно, единственная реальная цель функции.
только одна вещь может быть сделано с этим оптимизация: он не может быть использован с хвост-рекурсивная функция, оценивающая другую функцию (это происходит из факта что вызываемые возвращаемые объекты все обрабатываются как дальнейшие рекурсивные вызовы с помощью никакой разницы). Поскольку мне обычно не нужна такая функция, я очень счастлив с кодом выше. Однако, чтобы обеспечить более общий модуль, я подумал еще немного, чтобы найти обходной путь для этой проблемы (см. Следующий раздел).
относительно скорости этого процесса (что не является реальной проблемой, однако), это происходит чтобы быть довольно хорошим; хвост-рекурсивные функции даже оцениваются намного быстрее, чем с помощью следующий код использует более простые выражения:
def bet1(func): def wrapper(*args): out = func(lambda *x: lambda: x)(*args) while callable(out): out = func(lambda *x: lambda: x)(*out()) return out return wrapperя думаю, что оценка одного выражения, даже сложного, намного быстрее, чем оценка нескольких простых выражений, что имеет место в этой второй версии. Я не сохранил эту новую функцию в своем модуле, и я не вижу никаких обстоятельств, где она может быть использован, а не чиновник "" один.
продолжение прохождения стиля с исключениями
вот более общая функция; он способен обрабатывать все хвостовые рекурсивные функции, в том числе возвращающие другие функции. Рекурсивные вызовы распознаются из другие возвращаемые значения с помощью исключений. Это решение работает медленнее, чем предыдущий; более быстрый код, вероятно, может быть написана с помощью специальных значения как "флаги" обнаруживаются в основном цикле, но мне не нравится идея с помощью специальные значения или внутренние ключевые слова. Есть какая-то забавная интерпретация использования исключений: если Python не любит хвостовые рекурсивные вызовы, исключение должен быть поднят, когда происходит хвост-рекурсивный вызов, и питонический способ будет чтобы поймать исключение, чтобы найти какое-то чистое решение, которое на самом деле то, что происходить здесь...
class _RecursiveCall(Exception): def __init__(self, *args): self.args = args def _recursiveCallback(*args): raise _RecursiveCall(*args) def bet0(func): def wrapper(*args): while True: try: return func(_recursiveCallback)(*args) except _RecursiveCall as e: args = e.args return wrapperтеперь все функции могут быть использованы. В следующем примере,
f(n)оценивается в функция идентичности для любого положительного значения Н:>>> f = bet0( lambda f: lambda n: (lambda x: x) if not n else f(n-1) ) >>> f(5)(42) 42конечно, можно утверждать, что исключения не предназначены для намеренного использования перенаправление интерпретатора (как своего рода
gotoзаявление или, скорее, своего рода продолжение проходящего стиля), который я должен признать. Но, опять же, Я нахожу забавной идею использованияtryС одной строкой, являющейсяreturnзаявление: мы пытаемся вернуться что-то (нормальное поведение), но мы не можем этого сделать из-за рекурсивного вызова (исключение.)первоначальный ответ (2013-08-29).
я написал очень маленький плагин для обработки хвостовая рекурсия. Вы можете найти его с моими объяснениями там: https://groups.google.com/forum/?hl=fr#!topic/comp.lang.python/dIsnJ2BoBKs
он может встроить лямбда-функцию, написанную в стиле хвостовой рекурсии, в другую функцию, которая будет оценивать ее как цикл.
самая интересная особенность в этом небольшом функция, по моему скромному мнению, заключается в том, что функция не полагается на какой-то грязный программный хак, а на простое лямбда-исчисление: поведение функции изменяется на другое при вставке в другую лямбда-функцию, которая очень похожа на Y-комбинатор.
С уважением.
слово Гвидо находится в http://neopythonic.blogspot.co.uk/2009/04/tail-recursion-elimination.html
недавно я опубликовал запись в своем блоге истории Python о происхождении Функциональные возможности Python. Замечание о том, что не поддерживая хвост устранение рекурсии (TRE) сразу же вызвало несколько комментариев о как жаль, что Python этого не делает, включая ссылки на последние записи в блоге других, пытающихся "доказать", что TRE может добавлено к питону легко. Так что позвольте мне защитить мою позицию (которая заключается в том, что я этого не делаю хочу Тре на языке). Если вы хотите короткий ответ, это просто unpythonic. Вот длинный ответ:
CPython не поддерживает и, вероятно, никогда не будет поддерживать оптимизацию хвостового вызова на основе утверждений Гвидо по этому вопросу. Я слышал аргументы, что это делает отладку более сложной из-за того, как она изменяет трассировку стека.
попробовать экспериментальную macropy реализация TCO для размера.
кроме оптимизации хвостовой рекурсии, вы можете установить глубину рекурсии вручную:
import sys sys.setrecursionlimit(5500000) print("recursion limit:%d " % (sys.getrecursionlimit()))
Comments