Lua 協同程序對比 Python 生成器

lua-users home
wiki

下一版本的 Javascript 將顯然具備非常類似於 Python 生成器的一項功能;Lua 式協同程序的替代方案在經歷了一場漫長的辯論後被拒絕,這場辯論的總結請見 [Neil Mix 的部落格]

Python 生成器與 Lua 協同程序有何不同?最重要的特點是,在 Lua 中,coroutine.yield() 是一個可以在 coroutine.resume() 的動態範圍內的任何位置呼叫的一般函數,但限製條件是您不能透過 C 回呼 (callback) 來 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

然後可以在 for 迴圈中將 Lua 函數用作反覆運算元

for label in coroutine.wrap(inorder), coroutine.yield, t do
  -- something with label
end

或作為某種類型的 foreach 函數

inorder(print, t)

為了將遞迴生成器的比較減至最低限度,我撰寫了一對簡單程式來產生無限 [Ruler 函數]。(一個關於此函數更有趣的頁面是 Michael Naylor 的 [Abacaba-Dabacaba],其中包含一段音樂探討。)

Ruler 函數可以非遞迴地產生,但在這個範例中,它代表類似深度的優先搜尋的東西,所以我們只看遞迴實作。這些程式會產生序列中前 2^k 個值,並將它們加總起來作為一種有效性測試,因為很容易證明 Ruler 函數的前 2^k 個元素的和是 2^(k+1)-1。Python 的內建函數和標準程式庫在這裡很方便;在 Lua 中,我必須自己實作 sumislice(序列的前 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 

最新變更 · 喜好設定
編輯 · 歷程
最後編輯時間為 2009 年 9 月 8 日 下午 3:02 GMT (差異)