Lua协程与Python生成器 |
|
Python生成器与Lua协程有何区别?最重要的是,在Lua中,coroutine.yield()
是一个普通的函数,可以在coroutine.resume()
的动态范围内任何地方调用,但限制是不能通过C
回调yield
(除非使用MikePall的[Coco]库)。在Python中,yield
是语法上的,只能出现在生成器函数的词法体中。
这意味着Python生成器必须编写为生成器,不能轻易地分解成更小的函数;也不能轻易地递归。可以通过一种链式形式实现递归;[Python邮件列表]上的一条消息描述了这种机制
!Python def inorder(t): if t: for x in inorder(t.left): yield x yield t.label for x in inorder(t.right): yield x
在Lua中,可以将一个更通用的inorder
函数编写为高阶函数
function inorder(f, t) if t then inorder(f, t.left) f(t.label) return inorder(f, t.right) end end
然后,Lua函数可以在for
循环中用作迭代器
for label in coroutine.wrap(inorder), coroutine.yield, t do -- something with label end
或者用作一种foreach
函数
inorder(print, t)
为了将递归生成器的比较简化到最小程度,我编写了一对简单的程序,它们生成无限的[尺子函数]。(关于此函数更有趣的页面是Michael Naylor的[Abacaba-Dabacaba],其中包括音乐探索。)
尺子函数可以非递归地生成,但在本例中,它代表着类似于深度优先搜索的东西,因此我们只关注递归实现。程序生成序列中的前2^k
个值,并将它们加起来作为一种有效性测试,因为很容易证明尺子函数前2^k
个元素的总和为2^(k+1)-1
。Python的内置函数和标准库在这里很方便;在Lua中,我不得不自己实现sum
和islice
(序列的前k
个元素)函数,但幸运的是这并不难。
以下是Lua实现
function ruler(put, k) for i = 1, k do put(i) ruler(put, i-1) end end function eachruler() return coroutine.wrap(function() return ruler(coroutine.yield, 100) end) end function sumseq(k, f, o, s) local sum = 0 for v in f, o, s do k, sum = k-1, sum + v if k <= 0 then break end end return sum end local howmany = tonumber(arg[1]) or 24 local now = os.clock() local sum = sumseq(2^howmany, eachruler()) local took = os.clock()-now print(("%2i: %7.3f seconds %i check %i"):format(howmany, took, sum, 2^(howmany+1)-1))
以及非常相似且略短的Python实现
!Python from itertools import islice from sys import argv from time import clock def ruler(k): for i in range(1, k+1): yield i for x in ruler(i-1): yield x howmany = int(argv[1]) now = clock() total = sum(islice(ruler(100), 2**howmany)) took = clock()-now print "%2d: %7.3f seconds %d check %d" % (howmany, took, total, 2**(howmany+1)-1)
递归的略微奇怪的公式是手动将尾调用更改为for
循环的结果,因为 Python 不支持尾调用,而原始的尾调用实现让 Python 处于更加不利的境地。
由于两个程序都通过了检查,我将只在此处粘贴比较计时结果(以秒为单位,如上所述)。我不认为这仅仅是“Lua 比 Python 快”的基准测试;我认为这表明协程本质上更快;主要区别在于不必通过一系列 yield 传递值。
N Lua Python -- ----- ------ 12: 0.000 0.008 13: 0.000 0.023 14: 0.008 0.055 15: 0.016 0.109 16: 0.031 0.227 17: 0.078 0.469 18: 0.148 0.961 19: 0.305 1.961 20: 0.602 4.000 21: 1.211 8.148 22: 2.430 17.211 23: 4.820 40.094 24: 9.875 94.992