1. 程式人生 > >對twisted詩歌伺服器的總結和筆記

對twisted詩歌伺服器的總結和筆記

差不多兩個月之前的時候看過一段時間的twisted原始碼和詩歌伺服器的教程,但是當時的筆記都記在筆記本,兩個月之後想要再用的時候印象又已經模糊了。況且當時對於事件驅動和非同步回撥的理解沒有現在深,系統地看一遍教程記一下twisted和defer怎樣一步步產生的。個人感覺這個比當時看tcp差錯控制的演進還是要簡單一點。。
第一步,用2to3 -w 將所有程式碼轉換成python3格式

1.Twisted 理論基礎
首先介紹了一下多執行緒和非同步模型
在多執行緒程式中,對於停止某個執行緒啟動另外一個執行緒,其決定權並不在程式設計師手裡而在作業系統那裡,因此,程式設計師在編寫程式過程中必須要假設在任何時候一個執行緒都有可能被停止而啟動另外一個執行緒。相反,在非同步模型中,一個任務要想執行必須顯式放棄當前執行的任務的控制權。這也是相比多執行緒模型來說,最簡潔的地方。

個人理解:簡潔的意思是如果你能手動控制當前任務放棄控制權,能避免一些糟糕的情況(比如說死鎖?消費者生產者問題。。)。雖然這裡看起來需要手動新增到哪個位置時防止,增加了程式碼量,但是有效避免出錯及為了處理出錯而增加的程式碼量。(多執行緒並不適合邏輯複雜的任務)(協程與非同步,協程既有非同步又有同步)

非同步模型要比同步模型複雜得多。程式設計師必須將任務組織成序列來交替的小步完成。因此,若其中一個任務用到另外一個任務的輸出,則依賴的任務(即接收輸出的任務)需要被設計成為要接收系列位元或分片而不是一下全部接收。

因此,就要問了,為什麼還要使用非同步模型呢? 在這兒,我們至少有兩個原因。首先,如果有一到兩個任務需要完成面向人的介面,如果交替執行這些任務,系統在保持對使用者響應的同時在後臺執行其它的任務。因此,雖然後臺的任務可能不會執行的更快,但這樣的系統可能會受歡迎的多。
然而,有一種情況下,非同步模型的效能會高於同步模型,有時甚至會非常突出,即在比較短的時間內完成所有的任務。這種情況就是任務被強行等待或阻塞,如圖4所示:

據我瞭解,第一種情況的典型代表就是圖形介面,其中事件驅動是很廣泛使用的設計模式,第二種的代表就是爬蟲了。

因此一個非同步程式只有在沒有任務可執行時才會出現"阻塞",這也是為什麼非同步程式被稱為非阻塞程式的原因。
一個網路服務是非同步模型的典型代表

2.非同步程式設計模式與Reactor初探
先實現了一個阻塞的伺服器blocking-server/slowpoetry.py和一個阻塞的客戶端blocking-client/get-poetry.py
自然效率很低
然後使用一個非同步客戶端async-client/get-poetry.py,提升了效率。這個客戶端並沒有用到twisted
核心程式碼:

            while True:
                try:
                    new_data = sock.recv(1024)
                except socket.error, e:
                    if e.args[0] == errno.EWOULDBLOCK:
                        # this error code means we would have
                        # blocked if the socket was blocking.
                        # instead we skip to the next socket
                        break
                    raise
                else:
                    if not new_data:
                        break
                    else:
                        data += new_data

1用來進行通訊的Socket方法是非阻塞模的,這是通過呼叫setblocking(0)來實現的。
2select模組中的select方法是用來識別其監視的socket是否有完成資料接收的,如果沒有它就處於阻塞狀態。
3當從伺服器中讀取資料時,會盡量多地從Socket讀取資料直到它阻塞為止,然後讀下一個Socket接收的資料(如果有資料接收的話)。

我們的非同步模式的客戶端必須要有一個迴圈體來保證我們能夠同時監視所有的socket端。這樣我們就能在一次迴圈體中處理儘可能多的資料。
這個利用迴圈體來等待事件發生,然後處理髮生的事件的模型非常常見,而被設計成為一個模式:reactor模式。

說起這個圈,其實我想到讓服務生打電話叫自己起床這麼個有名的事件驅動的比喻。事實上,服務生一直在不斷地檢查:是否天亮了,如果天亮了就打電話,沒有天亮就繼續檢查是否天亮。並不是輪詢消失了,而是輪詢由我變成了另一個人(服務生),所以事件驅動有個別名叫事件輪詢。但是,要注意在twisted裡,只有一個主執行緒,不管是輪詢還是業務邏輯,都只有一個執行緒。

一個真正reactor模式的實現是需要實現迴圈獨立抽象出來並具有如下的功能:
1監視一系列與你I/O操作相關的檔案描述符(description)
2不停地向你彙報那些準備好的I/O操作的檔案描述符

感覺就像是一個老闆的祕書,報告重要事件。

3.初識Twisted
用twisted的方式實現前面的內容

from twisted.internet import reactor
reactor.run()

正常情況下,我們需要給出事件迴圈或者檔案描述符來監視I/O(連線到某個伺服器上,比如說我們那個詩歌伺服器)。
值得注意的是,這裡並不是一個在不停執行的簡單迴圈。如果你在桌面上有個CPU效能檢視器,可以發現這個迴圈體不會帶來任何效能損失。實際上,這個reactor被卡住在第二部分圖5的最頂端,等待永遠不會到來的事件發生(更具體點說是一個呼叫select函式,卻沒有監視任何檔案描述符)。
注意,此時程序是屬於阻塞態的(linux下程序狀態顯示為S),不消耗任何系統資源。
為了避免CPU空轉,可以引進了一個代理(一開始有一位叫做select的代理,後來又有一位叫做poll的代理,不過兩者的本質是一樣的)。這個代理比較厲害,可以同時觀察許多流的I/O事件,在空閒的時候,會把當前執行緒阻塞掉,當有一個或多個流有I/O事件時,就從阻塞態中醒來,於是我們的程式就會輪詢一遍所有的流(於是我們可以把“忙”字去掉了)。
在這裡插入圖片描述
這一段還需要深入理解。為什麼他被“卡”在最頂端,是真的“卡住了”?
reactor是單例模式,只能存在一個,並且只要import就會建立
若使用其它的reactor,需要在引入twisted.internet.reactor前安裝它。

from twited.internet import pollreactor
pollreactor.install()
from twisted.internet import reactor
reactor.run()

通過呼叫reactor的callWhenRunning函式,並傳給它一個我們想呼叫函式的引用來實現hello函式的呼叫。
由於Twisted迴圈是獨立於我們的程式碼,我們的業務程式碼與reactor核心程式碼的絕大多數互動都是通過使用Twisted的APIs回撥我們的業務函式來實現的。
reactor和我們的業務函式是在一個執行緒裡的,我之前認為輪詢是由另一個執行緒來完成(可能其他事件驅動的程式可能是這樣的,比如服務生打電話,但reactor不是。)

很多標準的Python方法沒有辦法轉換為非阻塞方式。例如,os.system中的很多方法會在子程序完成前一直處於阻塞狀態,這也就是它工作的方式。所以當你使用Twisted時,避開使用os.system。
避免在自己的函式裡寫會阻塞的程式碼

reactor.stop() # 退出
reactor.callLater(1, self.count)

註冊一個1秒後執行的函式,這個方法和之前Tk的方法名字都一樣啊
你可以將超時作為圖5中迴圈等待中的一種事件來看待。
Twisted的callLater機制並不為硬實時系統提供任何時間上的保證。

捕獲它,Twisted
reactor並不會因為回撥函式中出現失敗(雖然它會報告異常)而停止執行。而是會繼續執行下一個函式

4.由twisted支援的客戶端
前面一節講解了twisted的一些最基礎語法。這一節開始分析兩個twisted程式,一個是伺服器,一個是客戶端
第一個twisted支援的詩歌伺服器
先講解客戶端:
twisted-client-1/get-poetry.py
這個檔案有一個類PoetrySocket,其中有fileno,connectLost,doRead等方法,這些都是內建方法。當觸發了對應的事件的時候reactor會自動呼叫他們。
addReader將自己傳遞給 reactor,給reactor指定監控的檔案描述符。
doRead方法。當其被Twisted的reactor呼叫時,就會採用非同步的方式從socket中讀取資料。(IReadDescriptor)
fileno返回我們想監聽的檔案描述符,connectionLost是當連線關閉時被呼叫。(IFileDescriptor)
logPrefix(ILoggingContext)

doRead等其實就是一個回撥函式,只是沒有直接將其傳遞給reactor,而是傳遞一個實現此方法的物件例項。這也是Twisted框架中的慣例—不是直接傳遞實現某個介面的函式而是傳遞實現它的物件。這樣我們通過一個引數就可以傳遞一組相關的回撥函式。而且也可以讓回撥函式之間通過儲存在物件中的資料進行通訊。

twisted-client-1/get-poetry-broken.py 沒有選擇非阻塞的寫法。效率和一開始的客戶端沒有區別

應該禁止使用其它各類的阻塞函式,如os.system中的函式。除此之外,當我們遇到計算型的任務(長時間佔用CPU),最好是將任務切成若干個部分執行以讓I/O操作儘可能地執行。

其他:兩個低層reactor的APIs:removeReader和getReaders。

5.由Twisted扶持的客戶端
三個新概念:
Transports, Protocols, Protocol Factories
一個Twisted的Transport代表一個可以收發位元組的單條連線。對於我們的詩歌下載客戶端而言,就是對一條TCP連線的抽象。但是Twisted也支援諸如Unix中管道和UDP。
Protocol例項是儲存協議狀態與間斷性(由於我們是通過非同步I/O方式以任意大小來接收資料的)接收並累積資料的地方。
factory是用來給不同protocol之間交換資訊,一個protocol可以有不同factory
reactor run之前建立過程,Factory生成 protocol
reactor run之前
在Protocol創立的第二步便是通過makeConnection與一個Transport聯絡起來。
點選加入

處理接收到資料的主要方法是dataReceived

def dataReceived(self, data):
    self.poem += data

twisted-client-2/get-poetry.py 實現了上面的一切
twisted-client-2/get-poetry-stack.py 列印一個執行時的跟蹤堆疊
IReadDescriptor實際上就是變相的Transport類

當transport的連線關閉時,conncetionLost回撥會被啟用。reason引數是一個twisted.python.failure.Failure的例項物件,其攜帶的資訊能夠說明連線是被安全的關閉還是由於出錯被關閉的。

6.更加"抽象"的運用Twisted
客戶端版本2僅僅比較初步地使用了Protocol和Factory,3優化了設計
使Factory只做好兩件事,1、生成一個PoetryProtocol的例項 2、收集下載完畢的詩歌的工作
由get_poetry負責建立工廠和connectTCP
Factory用一個回撥來將下載完畢的詩歌傳回去。
這樣子更好地複用這三個類,而開啟和關閉reactor都在main函式中完成。

開始討論異常問題
在一個非同步互動的程式中,錯誤資訊也必須非同步的傳遞出去。
1、使用Exception
2、使用一個異常跟蹤棧
所以,我們需要寫一個處理錯誤資訊的回撥函式

7.小插曲 Deferred
defer解決回撥異常處理問題:
client3-1中的poem_failed錯誤回撥是由我們自己的程式碼啟用並呼叫的,即PoetryClientFactory的clientConnectFailed函式。是我們自己而不是Python來確保當出錯時錯誤處理程式碼能夠執行。因此我們必須保證通過呼叫攜帶Failure物件的errback來處理任何可能的錯誤。
否則,我們的程式就會因為等待一個永遠不會出現的回撥而止步不前。

在非同步程式中處理錯誤資訊比處理正常的資訊要重要的多,這是因為錯誤會以多種方式出現,而正確的結果出現的方式是唯一的。當使用Twisted程式設計時忘記處理異常是一個常犯的錯誤。

在程式中出現錯誤後,如果沒有捕捉到,reactor都會一直執行下去,不會終止。
deferred不允許別人啟用它兩次
先使用d = defer.Deferred() 建立一個回撥鏈,再使用addcallback和addboth往裡面塞東西,最後
reactor.callWhenRunning(d.callback, ‘Another short poem.’)
可以呼叫reactor的callWhenRunning函式來啟用deferred。再給callWhenRunning函式一個額外的引數傳給回撥函式。

小實驗:
在defer-9.py中加入一行

def poem_done(_):  # done是回撥鏈最後一環
    a = 1/0
    from twisted.internet import reactor
    reactor.stop()

依然會導致reactor無法結束的錯誤。

如果

def got_poem(poem):  # 接著執行done
    print(poem)
    a=1/0

系統不會報錯但會正常結束。接著在done裡面加入

def poem_done(_):
    print(_)
    from twisted.internet import reactor
    reactor.stop()

系統正常結束,且會報錯誤
應該保證最後addBoth的程式碼簡單,這樣不會出錯。

8.使用Deferred的詩歌下載客戶端
實現一個使用defer的客戶端4.0:
之前3.0版本是直接使用回撥,這樣如果的話,出錯就會GG,所以要使用deferred的d.callback呼叫回撥
所以要先把d傳入工廠,接著在poem_finish回撥之後在工廠裡呼叫callback

d, self.deferred = self.deferred, None  # 銷燬其引用。這樣做可以保證我們不會啟用一個deferred兩次。

討論:
同步函式也能返回一個deferred,因此嚴格來說,返回deferred只能說可能是非同步的。我們會在將來的例子中會看到同步函式返回deferred。

9.第二個小插曲,deferred
在3.1的基礎上進行講解,如果沒有正確捕獲異常會發生什麼:
這個異常會傳播到工廠中的poem_finished回撥,即啟用got_poem的方法
由於poem_finished並沒有捕獲這個異常,因此其會傳遞到protocol中的poemReceive函式
然後來到connectionLost函式,仍然在protocol中
然後就來到Twisted的核心區,最後止步於reactor。

在got_poem後面任何一步都沒有可能以符合我們客戶端的具體要求來處理異常的機會。
這段內容詳細介紹了defered和異常處理。
在這裡插入圖片描述
在正常順序呼叫時,最底層的是connect函式,get_poem的呼叫順序顯然應該在connect之前。
在回撥情況下,最底層的是got_poem函式,它發生了錯誤之後,反而會傳遞到connect,所以:
由於bug可能存在於我們程式碼中的每個角落,因此我們必須將每個回撥都放入try/except中,這樣一來所有的異常都才有可能被捕獲。這對於我們的errback同樣適用,因為errback中也可能含有bugs。
回撥鏈的執行順序
上圖為回撥鏈一個可能的的執行順序

未處理的defer異常(最後一步出現錯誤):
最後一個print函式成功執行,意味著程式並沒有因為出現未處理異常而崩潰。
其只是將跟蹤棧打印出來,而沒有宕掉直譯器
跟蹤棧的內容告訴我們deferred在何處捕獲了異常
"Unhandle"的字元在"Finished"之後出現。

之所以出現第4條是因為,這個訊息只有在deferred被垃圾回收時才會打印出來。

Callbacks與Errbacks,總會成對出現
addCallbacks
addCallback
addErrback
addBoth
很明顯的是,第一個與第四個是向鏈中新增函式對。當然中間兩個也向鏈中新增函式對。addCallback向鏈中新增一個顯式的callback函式與一個隱式的"pass-through"函式(實在想不出一個對應的詞)。一個pass-through函式只是虛設的函式,只將其第一個引數返回。由於errback回撥函式的第一個引數是Failure,因此一個"path-through"的errback總是執行"失敗",即將異常傳給下個errback回撥。

deferred模擬器
這部分內容,沒有譯。其主要是幫助理解deferred,但你會發現,讀其中的程式碼twisted-deferred/deferred-simulator.py ,可以更好的理解deferred。主要是我還沒有理解,嘿嘿。所以就不知為不知吧。

10.增強defer功能的客戶端
增強defer客戶端5.0
在獲得詩歌之後進行新的處理邏輯
如果上游的鏈返回了一個None,控權會進入到下一級的callback鏈中。

5.1版本改寫:
之前在5.0版本中try_to_cummingsify函式依然使用了try/except語句,可以將其改寫成使用defered的形式。
讓deferred來捕獲 GibberishError 與ValueError 異常在這裡插入圖片描述

總結:控制權在deferred的回撥鏈中交錯傳遞具體方向依賴於返回值的型別。

11.改進詩歌下載伺服器(一)
伺服器端使用Protocol來管理連線(transport)
實現原始碼:twisted-server-1/fastpoetry.py
Protocol是從Factory中獲得詩歌內容的:
除了建立PoetryProtocol, 工廠僅有的工作是儲存要傳送的詩歌。

*connectionMade都會在Protocol初始化時被呼叫,只是我們在客戶端處沒有使用這個方法。
並且我們在客戶端的portocal中實現的方法也沒有在伺服器中用到。因此,如果我們有這個需要,可以建立一個共享的PoetryProtocol供客戶端與伺服器端同時使用。*這樣可以實現向伺服器推送資料。
每一個客戶端連線都有一個transport,代表一個client socket,加上listening socket總共是四個被select迴圈監聽的檔案描述符(file descriptor).

12.改進詩歌下載伺服器(二)
實現一個詩歌樣式轉換伺服器
樣式轉換服務需要兩者進行雙向互動-客戶端將原始式樣的詩歌傳送給伺服器,然後伺服器轉換格式並將其返回給對應的客戶端。因此,我們需要使用或自己實現一個協議來實現這種互動。
客戶端需要向伺服器端傳送兩部分資訊:轉換方式與詩歌原始內容。伺服器只是將轉換格式之後的詩歌傳送給客戶端。這裡使用到了簡單的運程呼叫。
遠端過程呼叫:
Twisted支援若干種能解決這個問題的協議:XML-RPC, Perspective Broker, AMP。
什麼是netstring
twisted-server-1/transformedpoetry.py
格式轉換服務與具體協議的實現是完全分離的。將協議邏輯與服務邏輯分開是Twisted程式設計中常見的模式。這樣做可以通過多種協議實現同一種服務,以增加程式碼的重用性。

    def transform(self, xform_name, poem):
        thunk = getattr(self, 'xform_%s' % (xform_name,), None)

通過xfomr_字首式方法來獲取服務方法。
考慮到客戶端可以傳送任意的transform方法名,這是一種防止客戶端蓄意使用惡性程式碼來讓伺服器端執行的方法。這種方法也提供了實現由服務提供具體協議代理的機制。
NetStringProtocol實現不錯

客戶端實現
twisted-server-1/transform-test

總結:
雙向通訊
基於Twisted已有的協議實現新協議
將協議實現與服務功能實現獨立分開

13.使用Deferred新功能實現新客戶端
twisted-deferred/defer-10.py
展示了deferred最複雜的功能,一個外層defer,一個內層defer,一開始外層先執行,當外層defer執行到內層時,暫停,當內層被啟用時開始繼續執行執行內層完畢,然後轉向外層。
在這裡插入圖片描述

客戶端版本6.0
使用新學的deferred巢狀來重寫我們的客戶端來使用由伺服器提供的樣式轉換服務。
這裡之所以要使用雙重巢狀是因為我們要實現兩個服務,第一步,從兩個伺服器下載詩歌,隨後將這兩首詩歌傳給詩歌轉換伺服器並得到最終結果,我們把詩歌傳給轉換伺服器時,reactor會執行,此時在進行等待轉換後的詩歌到來(這一步如果作為一個單獨的任務拆分出來是需要自己的deferred的。)。

14.Deferred用於同步環境
twisted-deferred/defer-11.py
第一組例子:
演示了deferred可以在返回之前就啟用。可以在一個已經啟用的deferred上添加回調處理函式。一個非常值得注意的是:已經被啟用的deferred可以立即啟用新新增的回撥處理函式。

第二組例子:
演示了deferred中的pause與unpause函式的功能,pause可以暫停一個已經啟用的deferred對其回撥鏈上回調的啟用,unpause可以解除暫停。這個機制類似於“當Deferred回撥鏈上的回撥函式又返回Deferred時,Deferred暫停自己”。

d = Deferred()
print('Pausing, then firing deferred.')
d.pause()
d.callback(0)

print('Adding callbacks.')
d.addCallback(callback_1)
d.addCallback(callback_2)
d.addCallback(callback_3)

print('Unpausing the deferred.')
d.unpause()

代理 1.0版本
一個有快取的代理伺服器版本:
該伺服器既作為伺服器向客戶端請求提供本地快取的詩歌,同時也要作為向外部詩歌下載伺服器提出下載請求的客戶端,因此其有兩套協議/工廠,一套實現伺服器角色,另一套實現客戶端角色。
考慮到客戶端端傳送請求來時,快取代理可能會將本地緩衝的詩歌取出返回,也有可能需要非同步等待外部詩歌下載伺服器的回覆。如此一來,就會出現這樣的情景:客戶端傳送來的請求,快取代理處理請求可能是同步也可能是非同步。
要解決這個需要,就用到了之前的特性:可以在返回Deferred前就啟用。
兩套協議/工廠,一套實現伺服器角色,另一套實現客戶端角色。
這裡主要的問題是返回值不確定,
maybeDeferred函式解決了這個問題。如果返回值不是defer,它會把返回值打包為一個已經啟用的defer傳入回撥鏈中。

d = maybeDeferred(self.factory.service.get_poem)

或者直接用succeed手動打包

return succeed(self.poem)  # 這裡直接返回一個deferred

Deferred可以在啟用後新增新的回撥也間接說明了我們在第九部分twisted-deferred/defer-unhandled.py(提到的,deferred中會在最後一個回撥中遇到未處理異常,並在此deferred被垃圾回收(即其已經沒有任何外界引用)時才將該異常的情況打印出來。即deferred會在其銷燬前一直持有異常,等待可能還會新增進來的回撥來處理。

15、16測試和程序守護跳過,考慮到這兩章對理解deferred並沒有幫助。

17.構造"回撥"的另一種方法
為什麼generators 是建立回撥的候選方法.
以生成器本身的角度看問題:

生成器函式在被迴圈呼叫之前並沒有執行(使用 next 方法)
一旦生成器開始執行,它將一直執行直到返回"迴圈"(使用 yield)
生成器與迴圈總是交錯進行,一個執行,一個就不執行:
當迴圈中執行其他程式碼時(如 print 語句),生成器則沒有執行
當生成器執行時, 則迴圈沒有執行(等待生成器返回前它被"阻滯"了)
一旦生成器將控制交還到迴圈,再啟動可能需要等待任意時間(其間任意量的程式碼可能被執行)

這與非同步系統中的回撥工作方式非常類似.
把 while 迴圈視作 reactor, 把生成器視作一系列由 yield 語句分隔的回撥函式.
注意( 所有的回撥分享相同的區域性變數名空間, 而且名空間在不同回撥中保持一致.)

一次啟用多個生成器:
twisted-intro/inline-callbacks/gen-3.py
理解生成器的send和throw方法

對比defer,如果我們需要使用內外層的defer的時候, 可以用生成器丟擲一個deferred的方式來解決。
在等被丟擲的(內層deferred)執行完畢後將結果傳回生成器內繼續執行。參見inline-callbacks/inline-callbacks-1.py的程式碼

使用 deferred或者inllineCallbacks,我們的回撥仍然一次呼叫一個回撥
inlineCallbacks特性:
當我們呼叫一個用 inlineCallbacks 修飾的函式時,不需要自己呼叫 send 或 throw 方法.修飾符會幫助我們處理細節,並確保生成器執行到結束(假設它不丟擲異常).
如果我們從生成器yield一個非deferred值,它將以 yield 生成的值立即重啟.
如果我們從生成器yield一個 deferred,它不會重啟除非此 deferred 執行結束.如果 deferred 成功返回,則 yield 的結果就是 deferred 的結果.如果 deferred 失敗了,則 yield 會丟擲異常. 注意這個異常僅僅是一個普通的 Exception 物件,而不是 Failure,我們可以在 yield 外面用 try/except 塊捕獲它們.
(此例中用callLater 在一小段時間之後去激發 deferred。雖然這是一種將非阻塞延遲放入回撥鏈的實用方法,但通常我們會生成一個 deferred,它是被生成器中其他的非同步操作(如 get_poetry)返回的.這句沒有看懂)
當我們實際呼叫經過inline裝飾器裝飾的函式時,返回一個deferred,這個deferred在生成器完全結束(或丟擲異常)後才被激發.

閱讀inline-callbacks/inline-callbacks-2.py,最終裝飾過的函式通過returnValue返回值或者一個異常返回。

客戶端7.0:

12.7的一些看法
需要注意javascript也是單執行緒的事件驅動
defer和promise之間的關係
由於python的很多庫都是同步的,在用twisted寫程式的時候存在很多坑,比如讀取mysql資料庫這種操作。
事實上inlinecallback裝飾器已經接近協程的寫法了,scrapy中大部分的寫法都是如此。

生成器的惰性特徵:所謂惰性計算,是函數語言程式設計語言的一種特性,簡單來說就是對於一個值,只有當用到它的時候才去計算,這個思想來自代數運算(對於很多未知數的方程組,事實上可以消掉一些不用求的未知數)。惰性運算的缺點是執行速度緩慢(每次用到一個值,編譯器都會迴圈往上問:你有沒有被求出來。。)
所以,考慮生成器表示式和列表表示式的不同行為,使用生成器是用空間換時間的一種策略。進一步參考後文的 惰性不是遲緩: Twisted和Haskell