【问题标题】:are python lambdas implemented differently from standard functions?python lambdas 的实现方式与标准函数不同吗?
【发布时间】:2012-06-25 21:33:03
【问题描述】:

在尝试为另一个 SO 问题写答案时,发生了一件非常奇怪的事情。

我基本上想出了一个单行gcd并说it maybe slower because of recursion
gcd = lambda a,b : a if not b else gcd(b, a % b)

这是一个简单的测试:

assert gcd(10, 3) == 1 and gcd(21, 7) == 7 and gcd(100, 1000) == 100

这里有一些基准:

timeit.Timer('gcd(2**2048, 2**2048+123)', setup = 'from fractions import gcd').repeat(3, 100)
# [0.0022919178009033203, 0.0016410350799560547, 0.0016489028930664062]
timeit.Timer('gcd(2**2048, 2**2048+123)', setup = 'gcd = lambda a,b : a if not b else gcd(b, a % b)').repeat(3, 100)
# [0.0020480155944824219, 0.0016460418701171875, 0.0014090538024902344]

这很有趣,我预计会慢得多,但时间相当接近,?也许导入模块是问题...

>>> setup = '''
... def gcd(a, b):
...     """Calculate the Greatest Common Divisor of a and b.
... 
...     Unless b==0, the result will have the same sign as b (so that when
...     b is divided by it, the result comes out positive).
...     """
...     while b:
...         a, b = b, a%b
...     return a
... '''
>>> timeit.Timer('gcd(2**2048, 2**2048+123)', setup = setup).repeat(3, 100)
[0.0015637874603271484, 0.0014810562133789062, 0.0014750957489013672]

不,时间仍然相当接近,好吧,让我们尝试更大的值。

timeit.Timer('gcd(2**9048, 2**248212)', setup = 'gcd = lambda a,b : a if not b else gcd(b, a % b)').repeat(3, 100) [2.866894006729126, 2.8396279811859131, 2.8353509902954102]
[2.866894006729126, 2.8396279811859131, 2.8353509902954102]
timeit.Timer('gcd(2**9048, 2**248212)', setup = setup).repeat(3, 100)
[2.8533108234405518, 2.8411397933959961, 2.8430981636047363]

很有趣,我想知道发生了什么事?
由于调用函数的开销,我一直认为递归较慢,lambdas 是例外吗?为什么我还没有达到我的递归限制?
如果使用def 实现,我会立即点击它,如果我将递归深度增加到10**9 之类的东西,我实际上会得到segmentation fault 可能是堆栈溢出......

更新

>>> setup = '''
... import sys
... sys.setrecursionlimit(10**6)
... 
... def gcd(a, b):
...     return a if not b else gcd(b, a % b)
... '''
>>> 
>>> timeit.Timer('gcd(2**9048, 2**248212)', setup = 'gcd = lambda a,b:a if not b else gcd(b, a%b)').repeat(3, 100)
[3.0647969245910645, 3.0081429481506348, 2.9654929637908936]
>>> timeit.Timer('gcd(2**9048, 2**248212)', setup = 'from fractions import gcd').repeat(3,   100)
[3.0753359794616699, 2.97499680519104, 3.0096950531005859]
>>> timeit.Timer('gcd(2**9048, 2**248212)', setup = setup).repeat(3, 100)
[3.0334799289703369, 2.9955930709838867, 2.9726388454437256]
>>> 

更令人费解...

【问题讨论】:

  • @FelixBonkoski,Python 不优化尾递归。这段代码只是有一点堆栈使用:)
  • @astyntax 似乎对 TRE 是正确的:Guido says this. 但是,This SO answer 似乎表明解释器实际运行 TR 函数的方式存在一些差异。比我更有资格的人需要回答这个问题! :)
  • @kosii:写一个fibonacci函数,然后使用fibonacci(1200)fibonacci(1201)。连续的斐波那契数是欧几里得算法的最坏情况。
  • @MarkDickinson 并以这些 Fib 数字为例,即使使用 OP 的 lambda 形式的 gcd 函数,我也达到了递归限制。进一步支持 @astynax 关于 Python 未优化 Tail 递归的初始评论,并且 lambdadef fun() 处理 TR 的方式没有区别。
  • @samy.vilar will we ever see it? 正如我之前链接的,Guido van Rossum says TRE would be "unpythonic"

标签: python function lambda


【解决方案1】:
counter = 0

def gcd(a, b):
    global counter
    counter += 1
    return a if not b else gcd(b, a % b)

gcd(2**9048, 2**248212)
print counter

打印3。当然,深度为 3 的递归并没有太多开销。

【讨论】:

  • 是的,我现在很清楚这一点,我应该使用斐波那契数来运行我的测试,每天学习新的东西......
【解决方案2】:

lambda 的类型与任何其他函数的类型完全相同,如果两者都定义在另一个本地范围内,则会发生环境捕获。

唯一的区别是用 lambda 语法定义的函数不会自动成为它出现的范围内的变量的值,并且 lambda 语法要求主体是一个(可能是复合的)表达式,其值从函数返回。

至于递归的速度 - 是的,有一点开销,但显然没有那么多。调用开销似乎主要来自分配堆栈帧的成本。

【讨论】:

  • 你有没有看过他的表演时间?您没有看到库函数fractions.gcd() 更快的效果。他试图表明这是一个折腾,他对此感到困惑。 -1 表示不阅读问题。
  • @Marcin 我还使用常规 python 定义了非递归函数,时间仍然没有不同,发生了一些相当奇怪的事情,为什么我还没有达到递归限制?
  • @samy.vilar 每个堆栈帧只创建一个新的 int,因此内存消耗不是问题。这里没有什么神秘之处。至于递归限制,你为什么期望你会用这个例子来达到它?
  • @Marcin 经过一些微调并重新运行了更多示例,我认为你是对的,但我仍然感到惊喜,python 中的递归非常快,谁知道,谢谢。
  • 递归(显着)慢here。有什么解释吗?
猜你喜欢
  • 2019-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多