Python是如何實現生成器的原理

python中函數調用的實質原理:

  python解釋器(即python.exe)實際上是用C語言編寫的, 在執行python代碼時,其實是在用一個叫作Pyeval_EvalFramEx(C語言的函數)去執行代碼中的函數,(實際上python中的程序其實是運行在C語言之上的),運行此函數的時候,首先會在內存的堆區建立一個棧幀(stack frame),python中一切皆對象,在棧幀中間將要執行的代碼編譯成爲字節碼對象。 而後在棧幀的上下文中去運行字節碼,能夠用dis.dis()函數查看函數的字節碼。python

  

import dis
def foo():
    bar()
def bar():
    pass
print(dis.dis(foo))

結果:
 20           0 LOAD_GLOBAL              0 (bar)
              2 CALL_FUNCTION            0
              4 POP_TOP
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE
None

  

  字節碼解釋:當foo調用子函數bar時候,又會建立一個棧幀,而後將函數的控制權交給新的棧幀,而後去運行bar的字節碼,而後就有了兩個棧幀了。由於全部的棧幀都是分配在堆的內存上,(函數調用完畢不會被當即回收)這就決定了棧幀能夠獨立於調用者存在,(即foo即便不存在了退出了也沒有關係,只要有指針指向bar的棧幀,就能夠對其進行控制)具體看一下代碼:foo()調用完畢了以後,因爲全局變量指向了bar中的棧幀對象,因此print(frame.f_code.co_name)語句輸出產生當前棧幀對象的對象名,即bar,而後caller_frame = frame.f_back語句將調用者(foo)的棧幀對象獲取到,而後打印出來,即foo。函數

 

import inspect
import dis

frame = None
def foo():
    bar()
def bar():
    global frame
    frame = inspect.currentframe()
foo()
print(frame.f_code.co_name)
caller_frame = frame.f_back
print(caller_frame)

輸出結果:
bar
foo

 圖解以下:heap(堆區), recurse(遞歸,圖中意思即foo遞歸調用了bar)spa

堆區的PyFrameObject表示生成的棧幀對象,f_code表示執行函數的字節碼,f_back表示調用者函數的字節碼。指針

生成器的實現原理:


 

 

def gen_fun():
    yield 'a'
    name = 'bobby1'
    yield 'b'
    age = 30
    return 'frank'
import dis
gen = gen_fun()
print(dis.dis(gen)) #打印gen函數的字節碼
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)

輸出結果:
20           0 LOAD_CONST               1 ('a')
              2 YIELD_VALUE
              4 POP_TOP

 21           6 LOAD_CONST               2 ('bobby1')
              8 STORE_FAST               0 (name)

 22          10 LOAD_CONST               3 ('b')
             12 YIELD_VALUE
             14 POP_TOP

 23          16 LOAD_CONST               4 (30)
             18 STORE_FAST               1 (age)

 24          20 LOAD_CONST               5 ('frank')
             22 RETURN_VALUE
None
-1
{}
2
{}
12
{'name': 'bobby1'}

 

 真是由於有yield實現生成器函數,使得咱們能夠自由控制函數的運行於暫停,這個是協程實現的基礎。code

生成器的應用實例:用生成器函數讀取一行的超大文件。

def yield_str(file, spilt):
    buf = ''
    while True:

        while spilt in buf:
            pos = buf.index(spilt)
            yield buf[:pos]
            buf = buf[pos + len(spilt):]
        chunk = file.read(100)
        if not chunk:
            yield buf
            break
        buf += chunk

with open('txt.txt', encoding='utf-8') as file:
    for i in yield_str(file, ','):
        print(i)
相關文章
相關標籤/搜索