简体   繁体   English

Python 是否优化尾递归?

[英]Does Python optimize tail recursion?

I have the following piece of code which fails with the following error:我有以下一段代码失败并出现以下错误:

RuntimeError: maximum recursion depth exceeded RuntimeError:超出最大递归深度

I attempted to rewrite this to allow for tail recursion optimization (TCO).我试图重写它以允许尾递归优化(TCO)。 I believe that this code should have been successful if a TCO had taken place.我相信如果发生了 TCO,这段代码应该是成功的。

def trisum(n, csum):
    if n == 0:
        return csum
    else:
        return trisum(n - 1, csum + n)

print(trisum(1000, 0))

Should I conclude that Python does not do any type of TCO, or do I just need to define it differently?我应该得出结论 Python 不执行任何类型的 TCO,还是只需要以不同的方式定义它?

No, and it never will since Guido van Rossum prefers to be able to have proper tracebacks:不,它永远不会,因为Guido van Rossum更喜欢能够有适当的回溯:

Tail Recursion Elimination (2009-04-22) 尾递归消除(2009-04-22)

Final Words on Tail Calls (2009-04-27) 尾声的最后一句话(2009-04-27)

You can manually eliminate the recursion with a transformation like this:您可以使用如下转换手动消除递归:

>>> 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

I published a module performing tail-call optimization (handling both tail-recursion and continuation-passing style): https://github.com/baruchel/tco我发布了一个执行尾调用优化的模块(处理尾递归和连续传递样式): https ://github.com/baruchel/tco

Optimizing tail-recursion in Python在 Python 中优化尾递归

It has often been claimed that tail-recursion doesn't suit the Pythonic way of coding and that one shouldn't care about how to embed it in a loop.人们经常声称尾递归不适合 Pythonic 的编码方式,并且不应该关心如何将其嵌入循环中。 I don't want to argue with this point of view;我不想与这种观点争论; sometimes however I like trying or implementing new ideas as tail-recursive functions rather than with loops for various reasons (focusing on the idea rather than on the process, having twenty short functions on my screen in the same time rather than only three "Pythonic" functions, working in an interactive session rather than editing my code, etc.).但有时我喜欢尝试或实现新想法作为尾递归函数而不是循环出于各种原因(专注于想法而不是过程,在我的屏幕上同时有 20 个短函数而不是只有三个“Pythonic”功能,在交互式会话中工作而不是编辑我的代码等)。

Optimizing tail-recursion in Python is in fact quite easy.在 Python 中优化尾递归实际上很容易。 While it is said to be impossible or very tricky, I think it can be achieved with elegant, short and general solutions;虽然据说这是不可能的或非常棘手的,但我认为可以通过优雅、简短和通用的解决方案来实现; I even think that most of these solutions don't use Python features otherwise than they should.我什至认为这些解决方案中的大多数都没有使用 Python 功能,而不是它们应该使用的功能。 Clean lambda expressions working along with very standard loops lead to quick, efficient and fully usable tools for implementing tail-recursion optimization.干净的 lambda 表达式与非常标准的循环一起工作,为实现尾递归优化提供了快速、高效且完全可用的工具。

As a personal convenience, I wrote a small module implementing such an optimization by two different ways.为了个人方便,我编写了一个小模块,通过两种不同的方式实现这种优化。 I would like to discuss here about my two main functions.我想在这里讨论一下我的两个主要功能。

The clean way: modifying the Y combinator干净的方法:修改 Y 组合器

The Y combinator is well known; Y 组合器是众所周知的。 it allows to use lambda functions in a recursive manner, but it doesn't allow by itself to embed recursive calls in a loop.它允许以递归方式使用 lambda 函数,但它本身不允许在循环中嵌入递归调用。 Lambda calculus alone can't do such a thing.仅 Lambda 演算无法做到这一点。 A slight change in the Y combinator however can protect the recursive call to be actually evaluated.然而,Y 组合器的微小变化可以保护要实际评估的递归调用。 Evaluation can thus be delayed.因此,评估可能会被延迟。

Here is the famous expression for the Y combinator:这是 Y 组合器的著名表达式:

lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))

With a very slight change, I could get:稍作改动,我就可以得到:

lambda f: (lambda x: x(x))(lambda y: f(lambda *args: lambda: y(y)(*args)))

Instead of calling itself, the function f now returns a function performing the very same call, but since it returns it, the evaluation can be done later from outside.函数 f 现在不是调用自身,而是返回一个执行相同调用的函数,但由于它返回它,因此可以稍后从外部进行评估。

My code is:我的代码是:

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

The function can be used in the following way;该功能可以通过以下方式使用; here are two examples with tail-recursive versions of factorial and Fibonacci:以下是阶乘和斐波那契尾递归版本的两个示例:

>>> 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

Obviously recursion depth isn't an issue any longer:显然递归深度不再是问题:

>>> bet( lambda f: lambda n: 42 if not n else f(n-1) )(50000)
42

This is of course the single real purpose of the function.这当然是该功能的唯一真正目的。

Only one thing can't be done with this optimization: it can't be used with a tail-recursive function evaluating to another function (this comes from the fact that callable returned objects are all handled as further recursive calls with no distinction).这种优化只有一件事不能完成:它不能与评估另一个函数的尾递归函数一起使用(这是因为可调用的返回对象都被处理为没有区别的进一步递归调用)。 Since I usually don't need such a feature, I am very happy with the code above.由于我通常不需要这样的功能,所以我对上面的代码非常满意。 However, in order to provide a more general module, I thought a little more in order to find some workaround for this issue (see next section).然而,为了提供一个更通用的模块,我想了更多,以便找到一些解决这个问题的方法(见下一节)。

Concerning the speed of this process (which isn't the real issue however), it happens to be quite good;关于这个过程的速度(但这不是真正的问题),它恰好相当好; tail-recursive functions are even evaluated much quicker than with the following code using simpler expressions:尾递归函数的求值甚至比使用更简单表达式的以下代码快得多:

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

I think that evaluating one expression, even complicated, is much quicker than evaluating several simple expressions, which is the case in this second version.我认为评估一个表达式,即使是复杂的,比评估几个简单的表达式要快得多,第二个版本就是这种情况。 I didn't keep this new function in my module, and I see no circumstances where it could be used rather than the "official" one.我没有在我的模块中保留这个新功能,而且我没有看到可以使用它而不是“官方”的情况。

Continuation passing style with exceptions有例外的继续传递风格

Here is a more general function;这是一个更通用的功能; it is able to handle all tail-recursive functions, including those returning other functions.它能够处理所有尾递归函数,包括那些返回其他函数的函数。 Recursive calls are recognized from other return values by the use of exceptions.通过使用异常从其他返回值中识别递归调用。 This solutions is slower than the previous one;此解决方案比前一个解决方案慢; a quicker code could probably be written by using some special values as "flags" being detected in the main loop, but I don't like the idea of using special values or internal keywords.可以通过使用一些特殊值作为在主循环中检测到的“标志”来编写更快的代码,但我不喜欢使用特殊值或内部关键字的想法。 There is some funny interpretation of using exceptions: if Python doesn't like tail-recursive calls, an exception should be raised when a tail-recursive call does occur, and the Pythonic way will be to catch the exception in order to find some clean solution, which is actually what happens here...使用异常有一些有趣的解释:如果 Python 不喜欢尾递归调用,当发生尾递归调用时应该引发异常,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

Now all functions can be used.现在所有功能都可以使用了。 In the following example, f(n) is evaluated to the identity function for any positive value of n:在以下示例中,对于 n 的任何正值,将f(n)计算为恒等函数:

>>> f = bet0( lambda f: lambda n: (lambda x: x) if not n else f(n-1) )
>>> f(5)(42)
42

Of course, it could be argued that exceptions are not intended to be used for intentionally redirecting the interpreter (as a kind of goto statement or probably rather a kind of continuation passing style), which I have to admit.当然,可以说异常并不打算用于故意重定向解释器(作为一种goto语句,或者可能更确切地说是一种延续传递风格),我必须承认这一点。 But, again, I find funny the idea of using try with a single line being a return statement: we try to return something (normal behaviour) but we can't do it because of a recursive call occurring (exception).但是,再一次,我觉得使用单行作为return语句的try的想法很有趣:我们尝试返回一些东西(正常行为)但我们不能这样做,因为发生了递归调用(异常)。

Initial answer (2013-08-29).初步答案(2013-08-29)。

I wrote a very small plugin for handling tail recursion.我写了一个非常小的插件来处理尾递归。 You may find it with my explanations there: https://groups.google.com/forum/?hl=fr#!topic/comp.lang.python/dIsnJ2BoBKs您可以在我的解释中找到它: https ://groups.google.com/forum/?hl=fr#!topic/comp.lang.python/dIsnJ2BoBKs

It can embed a lambda function written with a tail recursion style in another function which will evaluate it as a loop.它可以将用尾递归样式编写的 lambda 函数嵌入到另一个函数中,该函数会将其评估为循环。

The most interesting feature in this small function, in my humble opinion, is that the function doesn't rely on some dirty programming hack but on mere lambda calculus: the behaviour of the function is changed to another one when inserted in another lambda function which looks very like the Y combinator.在我看来,这个小函数中最有趣的特性是,该函数不依赖于一些肮脏的编程技巧,而是仅仅依赖于 lambda 演算:当插入另一个 lambda 函数时,该函数的行为会更改为另一个看起来很像 Y 组合器。

The word of Guido is at http://neopythonic.blogspot.co.uk/2009/04/tail-recursion-elimination.html Guido 的话在http://neopythonic.blogspot.co.uk/2009/04/tail-recursion-elimination.html

I recently posted an entry in my Python History blog on the origins of Python's functional features.我最近在我的 Python 历史博客上发表了一篇关于 Python 函数特性起源的文章。 A side remark about not supporting tail recursion elimination (TRE) immediately sparked several comments about what a pity it is that Python doesn't do this, including links to recent blog entries by others trying to "prove" that TRE can be added to Python easily.一个关于不支持尾递归消除 (TRE) 的评论立即引发了几条评论,说 Python 不这样做是多么遗憾,包括其他人试图“证明” TRE 可以添加到 Python 的最近博客条目的链接容易地。 So let me defend my position (which is that I don't want TRE in the language).因此,让我捍卫我的立场(即我不希望在语言中使用 TRE)。 If you want a short answer, it's simply unpythonic.如果您想要一个简短的答案,那简直是无稽之谈。 Here's the long answer:这是长答案:

CPython does not and will probably never support tail call optimization based on Guido van Rossum's statements on the subject. CPython不支持也可能永远不会支持基于Guido van Rossum 关于该主题的陈述的尾调用优化。

I've heard arguments that it makes debugging more difficult because of how it modifies the stack trace.我听说过它使调试变得更加困难的论点,因为它修改了堆栈跟踪。

Besides optimizing tail recursion, you can set the recursion depth manually by:除了优化尾递归之外,您还可以通过以下方式手动设置递归深度:

import sys
sys.setrecursionlimit(5500000)
print("recursion limit:%d " % (sys.getrecursionlimit()))

尝试使用实验性的macropy TCO 实现来确定大小。

There is no built-in tail recursion optimization in Python. Python 中没有内置的尾递归优化。 However, we can "rebuild" the function through the Abstract Syntax Tree( AST), eliminating the recursion there and replacing it with a loop.但是,我们可以通过抽象语法树(AST)“重建”函数,消除那里的递归并用循环替换它。 Guido was absolutely right, this approach has some limitations, so I can't recommend it for use. Guido 是绝对正确的,这种方法有一些局限性,所以我不推荐使用它。

However, I still wrote (rather as a training example) my own version of the optimizer, and you can even try how it works.但是,我仍然编写(而不是作为训练示例)我自己的优化器版本,您甚至可以尝试它是如何工作的。

Install this package via pip:通过 pip 安装这个包:

pip install astrologic

Now you can run this sample code:现在您可以运行此示例代码:

from astrologic import no_recursion

counter = 0

@no_recursion
def recursion():
    global counter
    counter += 1
    if counter != 10000000:
        return recursion()
    return counter

print(recursion())

This solution is not stable, and you should never use it in production.这个解决方案不稳定,你永远不应该在生产中使用它。 You can read about some significant restrictions on the page in github (in Russian, sorry).您可以在 github 上阅读有关页面上的一些重要限制(俄语,抱歉)。 However, this solution is quite "real", without interrupting the code and other similar tricks.然而,这个解决方案是相当“真实”的,没有中断代码和其他类似的技巧。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM