Python 2.2 中引進的簡單產生器可用於簡化狀態機器以及模仿協同程式。David 在“可愛的 Python”專欄較早前的一個部分中介紹了一個 狀態機器處理的抽象模式。從那時起,簡單產生器的引進就為描述機器提供了一些更自然的範例。協同程式是一種“外來”流機制,廣泛使用的語言幾乎都不支援這種機制(甚至連非 Stackless Python 都不支援它)。然而,Python 的新產生器 幾乎完全支援協同程式,幾乎不用模仿任何額外的步驟。在本文中,David 通過說明性代碼樣本解釋了所有相關概念。
您需要花上一段時間才能完全“瞭解”Python 2.2 的新產生器。即使在“可愛的 Python”前面一部分中已經寫過了 介紹簡單產生器,我還是不能說已經完全理解了產生器的“完整結構(gestalt)”。本文介紹了一些供產生器使用的附加模式,並且希望它能讓我本人和讀者更深入地瞭解“可繼續函式”這一理念體系。
簡單產生器有許多優點。產生器除了能夠用更自然的方法表達一類問題的流程之外,還極大地改善了許多效率不足之處。在 Python 中,函數調用代價不菲;除其它因素外,還要花一段時間解決函數參數列表(除了其它的事情外,還要分析位置參數和預設參數)。初始化架構對象還要採取一些建立步驟(據 Tim Peters 在 comp.lang.python 上所說,有 100 多行 C 語言程式;我自己還沒檢查 Python 原始碼呢)。與此相反,恢複一個產生器就相當省力;參數已經解析完了,而且架構對象正“無所事事地”等待恢複(幾乎不需要額外的初始化)。當然,如果速度是最重要的,您不應該使用位元組碼已編譯過的動態語言;但即使在速度不是主要考慮因素的情況下,快點總比慢點好。
回憶狀態機器
在“可愛的 Python”前面的另一篇文章中,我介紹了 StateMachine 類 ,給定的機器需要多少狀態處理常式,它就允許使用者添加多少狀態處理常式。在模型中,將一個或多個狀態定義為終態(end state),僅將一個狀態定義為初始狀態(start state)(調用類方法對此進行配置)。每個處理常式都有某種必需的結構;處理常式將執行一系列操作,然後過一會兒,它帶著一個標記返回到 StateMachine.run() 方法中的迴圈內,該標記指出了想得到的下一個狀態。同樣,用 cargo 變數允許一個狀態把一些(未處理的)資訊傳遞給下一個狀態。
我介紹的 StateMachine 類的典型用途是以一個有狀態的方式使用輸入。例如,我所用的一個文本處理工具(Txt2Html)從一個檔案中讀取數行內容;依據每行所屬的類別,需要以特殊的方式對其進行處理。然而,您經常需要看看前面幾行提供的上下文來確定當前行屬於哪個類別(以及應該怎樣處理它)。構建在 StateMachine 類上的這個過程的實現可以定義一個 A 處理常式,該處理常式讀取幾行,然後以類似 A 的方式處理這些行。不久,滿足了一個條件,這樣下一批的幾行內容就應該由 B 處理常式來處理了。 A 把控制傳遞迴 .run() 迴圈,同時指示切換到 B 狀態 ― 以及任何 A 不能正確處理的、 B 應該在閱讀額外的幾行之前處理的額外的行。最後,某個處理常式將它的控制傳遞給某個被指定為終態的狀態,處理停止(halt)。
對於前面一部分中的具體程式碼範例,我使用了一個簡化過的應用程式。我處理由迭代函數產生的數字流,而不是處理多行內容。每個狀態處理常式僅列印那些在期望的數字範圍內的數字(以及關於有效狀態的一些訊息)。當數字流中的一個數字傳到一個不同的範圍內,另一個不同的處理常式就會接管“處理”。對於這一部分,我們將看看另一種用產生器實現相同數字流處理的方式(有一些額外的技巧和功能)。但是,一個更複雜的產生器樣本有可能對更象上一段中提到的輸入資料流進行處理。我們再來看看前一個狀態機器刪減過代碼的版本:
清單 1. statemachine_test.py
from statemachine import StateMachine
def ones_counter(val):
print "ONES State: ",
while 1:
if val <= 0 or val >= 30:
newState = "Out_of_Range" ; break
elif 20 <= val < 30:
newState = "TWENTIES"; break
elif 10 <= val < 20:
newState = "TENS"; break
else:
print " @ %2.1f+" % val,
val = math_func(val)
print " >>"
return (newState, val)
# ... other handlers ...
def math_func(n):
from math import sin
return abs(sin(n))*31
if __name__== "__main__":
m = StateMachine()
m.add_state("ONES", ones_counter)
m.add_state("TENS", tens_counter)
m.add_state("TWENTIES", twenties_counter)
m.add_state("OUT_OF_RANGE", None, end_state=1)
m.set_start("ONES")
m.run(1)
讀者如果接下來對匯入的 StateMachine 類以及它的方法如何工作感興趣,應該看看前面的文章。