第一部分
在掌握python生成器之前,你需要理解普通的python函式是如何工作的。通常,當乙個python函式呼叫乙個子程式時,子程式保留控制權直到它返回,或者丟擲乙個異常。然後控制權被交還給呼叫者(父程式)。
>>> def foo():
... bar()
>>> def bar():
... pass
python的標準直譯器是由c寫成的。自然美妙地,乙個python函式被呼叫時,由c函式pyeval_evalframeex來執行這個python函式。它接受python的乙個棧幀物件,然後在幀的上下文中評估python的位元組碼。下面是foo的位元組碼:
>>> import dis
>>> dis.dis(foo)
2 0 load_global 0 (bar)
3 call_function 0 (0 positional, 0 keyword pair)
6 pop_top
7 load_const 0 (none)
10 return_value
foo函式載入bar到它的棧上然後呼叫它,然後從棧中彈出它的返回值,再在foo函式中載入返回值none到棧上,然後返回none。
當pyeval_evalframeex遇到乙個call_function位元組碼時,它建立乙個新的python棧幀然後遞迴:這意味著它遞迴呼叫了pyeval_evalframeex,傳入新的棧幀物件,該物件被用來執行bar函式。
關鍵的是,要明白python的棧幀是在堆記憶體中分配的!python的直譯器是乙個普通的c程式,所以它的棧幀都是常規的棧幀。但是它操縱的python棧幀都是在堆上的。這意味著python的棧幀能夠比它的函式呼叫活得更久。要互動式地看到這一點,在bar函式中儲存當前棧幀即可:
>>> import inspect
>>> frame = none
>>> def foo():
... bar()
>>> def bar():
... global frame
... frame = inspect.currentframe()
>>> foo()
>>> # the frame was executing the code for 'bar'.
>>> frame.f_code.co_name
'bar'
>>> # its back pointer refers to the frame for 'foo'.
>>> caller_frame = frame.f_back
>>> caller_frame.f_code.co_name
'foo'
函式呼叫
第二部分
現在暖場結束,舞台交給python的生成器,它利用了同樣的建築模組——**物件和棧幀——來達到奇妙的效果。
下面是乙個生成器函式:
>>> def gen_fn():
... result = yield 1
... print('result of yield: {}'.format(result))
... result2 = yield 2
... print('result of 2nd yield: {}'.format(result2))
... return 'done'
當python編譯gen_fn為位元組碼時,它看到yield語句並且知道gen_fn為乙個生成器函式,而不是乙個普通函式。它設定了乙個標誌來記住這一事實:
>>> # the generator flag is bit position 5.
>>> generator_bit = 1 << 5
>>> bool(gen_fn.__code__.co_flags & generator_bit)
true
當你呼叫乙個生成器函式,python看到生成器標誌,然後它實際上並沒有去執行函式而是建立了乙個生成器物件:
>>> gen = gen_fn()
>>> type(gen)
乙個python生成器物件將乙個棧幀,一些**的引用即gen_fn函式體包裹在一起:
>>> gen.gi_code.co_name
'gen_fn'
所有呼叫gen_fn函式得到的生成器物件都指向這同樣的**。但是每乙個都有它自己的棧幀。這個棧幀並不在任何真正的棧上,它坐在堆記憶體中等待被使用:
生成器該幀有乙個「最後的指令」的指標,指向它最近一次執行的命令。在一開始,這個指標的值是-1,意味著生成器並沒有開始:
>>> gen.gi_frame.f_lasti
-1當我們呼叫send方法時,生成器抵達它第乙個yield,然後暫停。send的返回值是1。
>>> gen.send(none)
現在生成器物件的指令指標的位置距離開始隔了3位元組碼長度,完成了編譯好的56位元組的python**的一部分。
>>> gen.gi_frame.f_lasti
>>> len(gen.gi_code.co_code)
該生成器能在任何時間被喚醒,被任何函式使用,因為它的棧幀並沒有真正在棧上:它是在堆上的。它在呼叫層級中的位置並不固定,而且它不需要遵守先入後出的執行順序(常規函式就需要)。它是解放的,自由得像空中漂浮的雲朵。
我們可以傳入值"hello"到生成器中,然後它就成了yield表示式的值,然後生成器持續執行到它生成2:
>>> gen.send('hello')
result of yield: hello
它的棧幀現在擁有本地變數result:
>>> gen.gi_frame.f_locals
其它用gen_fn 建立出的生成器將會擁有他們自己的棧幀和本地變數。
當我們再次呼叫send函式時,該生成器從它第二個yield開始執行,執行完之後的**,最後以丟擲乙個stopiteration異常結束:
>>> gen.send('goodbye')
result of 2nd yield: goodbye
traceback (most recent call last):
file "", line 1, in
stopiteration: done
這個異常也是有值的,那就是生成器的返回值:字串"done"。
附錄:
古文生成器python python(生成器)
生成器 先從列表生成式說起 可以通過簡單的式子,生成有規律的列表 如果把 換為 會發生什麼呢?看到 x 存的不再是列表,而是乙個位址,而這個位址就是我們的生成器物件的位址 這東西有什麼用呢?當然時,節省記憶體啦 假設現在有很龐大的一組資料要處理,貌似不可能把它一次性載入記憶體再進行處理,這時候就體現...
遞迴呼叫python python生成器,遞迴呼叫
生成器 什麼是生成器 只要在函式體內出現yield關鍵字,那麼再執行函式就不會執行函式 會得到乙個結果,該結果就是生成器 生成器就是迭代器 yield的功能 yield為我們提供了一種自定義迭代器物件的方法 yield與return的區別 1.yield可以返回多個值 2.函式暫停和再繼續是由yie...
python 生成器作用 Python生成器
生成器介紹 在函式內部包含yield關鍵字,那麼該函式執行的結果是生成器,生成器就是迭代器。生成器的功能 把函式結果做成迭代器 以一種優雅的方式封裝好iter,next 提供了一種自己定義迭代器的方式。使用生成器建立乙個迭代器 def a print a yield 11 使用yield,執行後返回...