生成器python python生成器是怎樣工作的

2021-10-16 19:31:10 字數 3116 閱讀 4445

第一部分

在掌握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,執行後返回...