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)