以下內容基於python3.4
1. python中的普通函數是怎么運行的?
當一個python函數在執行時,它會在相應的python棧幀上運行,棧幀表示程序運行時函數調用棧中的某一幀。想要獲得某個函數相關的棧幀,則必須在調用這個函數且這個函數尚未返回時獲取,可能通過inspect模塊的currentframe()函數獲取當前棧幀。
棧幀對象中的3個常用的屬性:
- f_back : 調用棧的上一級棧幀
- f_code: 棧幀對應的c
- f_locals: 用在當前棧幀時的局部變量;
比如:
>>> import inspect >>> def func(): ... global x ... x = inspect.currentframe() ... >>> x = None >>> func() >>> x <frame object at 0x7f50f3ee2868>
更進一步講, 標准的python解釋器是用C語言寫的,通常稱作CPython, 當執行一個python函數時,解釋器中的C函數 PyEval_EvalFrameEx() 就會被調用,它來處理python 代碼的字節碼, 它的參數為對於python函數的棧幀 object,即上面例子中的 x就是一個棧幀對象。
舉例說明函數是如何運行的?
>>> def foo(): ... x = 12 ... y = bar() ... return y ... >>> def bar(): ... return 'hello' ...
使用dis模塊查看一下函數foo()的字節碼(看不懂內容沒事,其它有規律):
>>> import dis >>> dis.dis(foo) 2 0 LOAD_CONST 1 (12) 3 STORE_FAST 0 (x) 3 6 LOAD_GLOBAL 0 (bar) 9 CALL_FUNCTION 0 (0 positional, 0 keyword pair) 12 STORE_FAST 1 (y) 4 15 LOAD_FAST 1 (y) 18 RETURN_VALUE
運行過程:
解釋器調用 C函數 PyEval_EvalFrameEx()運行foo()的字節碼,它的參數為foo()對應的棧幀對象,運行位置為foo()對應的棧幀; 在運行過程中,遇到 CALL_FUNCTION 時,它會為函數bar()生成新的棧幀,然后又調用一個 PyEval_EvalFrameEx() 運行bar()對應的字節碼,……,如此遞歸,然后一層層的返回;
2. 對於python中棧幀:
在python中的棧幀其實是在解釋器的堆上分配內存的,所以,在一個python函數運行完成后,它的棧幀的仍然存在,並沒有消失,下面例子說明了(當func函數運行完成后,我們然后可以訪問到它對應的棧幀):
>>> import inspect >>> def func(): ... global x ... x = inspect.currentframe() ... >>> x = None >>> func() >>> x <frame object at 0x7f50f3ee2868> >>> x.f_code.co_name 'func'
3. python中的生成器函數是怎么運行的?
#這是一個函數 >>> def func(): ... print('You are SB') ... #這是一個生成器
>>> def gen(): ... yield 'You are SB' ... return 'ni gei wo gun'
對於函數與生成器函數的區別在於生成器中有yield表達式, 它們的co_flags是不相同的:
function沒有*args或**kw時,func.__code__.co_flags=67; function有*args沒有**kw時,func.__code__.co_flags=71;
function沒有*args有**kw時,func.__code__.co_flags=75; function既有*args也有**kw時,func.__code__.co_flags=79;
function是一個generator時,func.__code__.co_flags=99.
>>> func.__code__.co_flags 67 >>> gen.__code__.co_flags 99
當運行一個生成器函數時,它會生成一個生成器:
>>> a = gen() >>> type(a) <class 'generator'> >>> b= gen() >>> b <generator object gen at 0x7f50f4a7a3f0>
上面例子中生成了兩個生成器a與b, 每一個生成器都有兩個常用的屬性,分別為gi_frame與gi_code, 不同的生成器的gi_code是相同的,對應生成器函數的字節碼,然而它們的gi_frame是不相同的,所以,不同的生成器可以分別運行,並且互不干擾;
對於每一個棧幀又都有一個指針f_lasti,它指向了最后執行的命令,在一開始沒有執行時,它的值為-1;
>>> a.gi_frame.f_lasti -1 >>> a.send(None) 'You are SB' >>> a.gi_frame.f_lasti 3 >>> b.gi_frame.f_lasti -1
當生成器執行到最后時,它就產生一個 StopIteration
異常,然后就停止了,當生成器函數中有return時, 這個異常的值就是return的值,如果沒有return,異常的值為空;
>>> next(b) 'You are SB' >>> next(b) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration: ni gei wo gun
生成器函數就就是這么運行的。
4.生成器相關操作:
1. X.__next__()方法和next()內置函數
當我們調用一個生成器函數時來生成一個生成器X時,這個生成器對象就會自帶一個X.__next__()方法,它可以開始或繼續函數並運行到下一個yield結果的返回或引發一個StopIteration異常(這個異常是在運行到了函數末尾或着遇到了return語句的時候引起)。也可以通過python的內置函數next()來調用X.__next__()方法,結果都是一樣的;
>>> def gen(): ... yield 'NI' ... return 'hahahaha' ... yield 'HAO' ... >>> x = gen() #查看一下x的屬性,我們發現了__next__方法 >>> dir(x) ['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'send', 'throw'] #使用__next__方法運行函數; >>> x.__next__() 'NI' >>> x.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration: hahahaha #使用內置的next()函數運行函數(重新生成一個生成器x) >>> x = gen() >>> next(x) 'NI' >>> next(x) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration: hahahaha
2. 生成器函數協議中的send()方法
在講send()方法的時候,有必要了解一下next()或__next__()或send()語句執行時,生成器內的程序執行到了哪里暫停了。寫一個很簡單的函數,使用pdb調試一下:
#定義一個gen.py文件 1 def gen(): 2 a = yield 1 3 b = yield 2 4 return 100 5 6 x = gen() 7 n1 = next(x) 8 n2 = next(x) #使用pdb調試一下這個文件 yinheyi@ubuntu:~/play$ python3.4 -m pdb gen.py > /home/yinheyi/play/gen.py(1)<module>() -> def gen(): 在第7行設置一個斷點 (Pdb) b 7 Breakpoint 1 at /home/yinheyi/play/gen.py:7 #運行到斷點前 (Pdb) r > /home/yinheyi/play/gen.py(7)<module>() -> n1 = next(x) #此時,可以使用 l 查看一下狀態,顯示運行第7行了; (Pdb) l 2 a = yield 1 3 b = yield 2 4 return 100 5 6 x = gen() 7 B-> n1 = next(x) 8 n2 = next(x) # 查看一下變量 n1的值,應該還沒有定義,因為還沒有運行到; (Pdb) p n1 *** NameError: name 'n1' is not defined # 查看一下生成器x的棧幀中的局部變量,應該是空,因為還沒有開始執行生成器x (Pdb)p x.gi_frame.f_locals {} # 執行第7行,使用next()開始執行了生成器x (Pdb) n > /home/yinheyi/play/gen.py(8)<module>() -> n2 = next(x) #再一次查看一個n1的值,它的值為1,即next( )的返回值,它的返回值就是第一個yield出來的值:1 (Pdb) p n1 1 # 再一次 查看一下生成器x的棧幀中的局部變量,競然還為空,說明了什么??已經執行了yield 1的表達式,但是這個表達式執行到 yield出來1就暫停了,並沒有執行到生成表達式“yiled 1” 的返回值 為None;所以,局部變量里面沒有值; (Pdb) p x.gi_frame.f_locals {} #那就再執行第8行語句,看看會怎么樣? (Pdb) n --Return-- > /home/yinheyi/play/gen.py(8)<module>()->None -> n2 = next(x) #打印 n2的值為2; (Pdb) p n2 2 #查看一下生成器x的棧幀中的局部變量,這時,發現有了變量a, 沒有變量b, 明白了,原來如此 (Pdb) p x.gi_frame.f_locals {'a': None}
通過看上面的程序,我們知道,當next()或__next__()或send()語句執行時,在生成器里面的程序中它執行到 yiled value 這條語句, 它yield出來了一個value值,但是沒有執行yiled value表達式 的返回值它就暫停了;
現在說說send()方法:從技術上講,yield是一個表達式,它是有返回值的,當我們使用內置的next()函數或__next__方法時,默認yield表達式的返回值為 None,它使用send(value)方法時,它可以把一個值傳遞給生成器,使得yield表達式的返回值為send()方法傳入的值; 當我們第一次執行send()方法時,我們必須傳入None值,因為第一次執行時,還沒有等待返回值的yield表達式(雖然 send()方法會執行下一條yield語句,但是上面已經說明了它在還沒有來得及執行yiled value表達式 的返回值時它就暫停了)
定義一個gen.py文件,里面的內容為: 1 def gen(): 2 a = yield 1 3 print('a的值為:', a) 4 b = yield 2 5 print('b的值為:', b) 6 return '我要結束了' 7 8 9 x = gen() 10 print('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') 11 n1 = x.send(None) 12 print('第一個yield表達式yield出來的值為:', n1) 13 print('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') 14 n2 = x.send('love love love') 15 print('第二個yield表達式yield出來的值為:', n2) 16 print('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') 17 try: 18 n3 = x.send('TMDTMD') 19 except StopIteration: 20 print('我已經運行到末尾了,沒有yield語句供我繼續運行了') 21 finally: 22 print('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') #運行結果: yinheyi@ubuntu:~/play$ python3.4 gen.py >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 第一個yield表達式yield出來的值為: 1 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> a的值為: love love love 第二個yield表達式yield出來的值為: 2 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> b的值為: TMDTMD 我已經運行到末尾了,沒有yield語句供我繼續運行了 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
3. 生成器函數中的return 語句:
當生成器運行到了return語句時,會拋出StopIteration的異常,異常的值就是return的值; 另外,即使return后面有yield語句,也不會被執行;
>>> def gen(): ... yield 'NI' ... return 'hahahaha' ... yield 'HAO' ... >>> x = gen() >>> x.__next__() 'NI' >>> x.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration: hahahaha
4. 另外,一個生成器對象也有close方法與throw方法,可以使用它們提前關閉一個生成器或拋出一個異常;使用close方法時,它本質上是在生成器內部產生了一個終止迭代的GeneratorExit的異常;
# 使用 close方法提前關閉異常; >>> x = gen() >>> x.close() >>> x.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration #使用throw方法拋出異常 >>> x = gen() >>> x.throw(StopIteration) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 1, in gen
5. 最后一個要講的內容:yield from
這個是在python3.0以后新增加的內容,可以讓生成器delegate另一個生成器;
1. 舉一個例子看看它是怎么往外 yield數據的???
#生成器函數1 >>> def fun(): ... yield 1 ... yield 2 ... return 'hello' ... yield 3 #生成器函數2 >>> def call_fun(): ... yield 'a' ... result = yield from fun() ... print(result) ... yield 'b' ... yield 'c' #運行; >>> caller = call_fun() >>> caller.send(None) 'a' >>> caller.send(None) 1 >>> caller.gi_frame.f_lasti #此時,查看一下caller的指針指向14 14 >>> caller.send(None) 2 >>> caller.gi_frame.f_lasti #此時caller的指針仍然是指向14,說明caller生成器遇到yield from時被阻塞了; 14 >>> caller.send(None) hello #說明了 yield from 表達式的返回值為生成器fun()中return的返回值; 'b' >>> caller.gi_frame.f_lasti 22 >>> caller.send(None) 'c' >>> caller.send(None) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
這個例子我們明白了兩點:1. 當我們調用主生成器caller時,遇到yield from 時,它就會停下來,運行子生成器的程序, yield出來的數據就是子生成器里的數據;2. yield from 表達式的返回值為子生成器的return的值;
2. 舉個例子看看它是怎么通過 send()方法往里傳遞數據的?
>>> def fun(): ... a = yield 1 ... print('yield 1的值為', a) ... b = yield 2 ... print('yield 2 的值為', b) ... return '子生成器完成,我要返回了' ... >>> def call_fun(): ... x1 = yield 'a' ... print('yield a 的值為', x1) ... result = yield from fun() ... print(result) ... x2 = yield 'b' ... print('yield b 的值為', x2) #一步步運行; >>> caller = call_fun() >>> caller.send(None) 'a' >>> caller.send('xiaoming') yield a 的值為 xiaoming 1 >>> caller.send('xiao') yield 1的值為 xiao 2 >>> caller.send('ming') yield 2 的值為 ming 子生成器完成,我要返回了 'b' >>> caller.send('hahahha') yield b 的值為 hahahha Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
通過這個例子,我們明白了1點:當主生成器遇到yield from以后,我們通過 send()方法傳入值最終傳給了子生成器;
3. 通過 yield from ,可以嵌套調用生成器,比如:
>>> def fun1(): ... yield 1 ... yield 2 ... >>> def fun2(): ... yield from fun1() ... >>> def fun3(): ... yield from fun2() ... >>> def fun4(): ... yield 'hello' ... yield from fun3() ... yield 'world' ... #運行 >>> a = fun4() >>> next(a) 'hello' >>> next(a) 1 >>> next(a) 2 >>> next(a) 'world' >>> next(a) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
部分內容參考:A Web Crawler With asyncio Coroutines中的內容;
想要也了解更多,請參考python手冊:https://docs.python.org/3/index.html