Оптимизирует ли 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, или мне просто нужно определить его по-другому?

652   6  

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

    Ничего не найдено.