如下內容基於python3.4html
當一個python函數在執行時,它會在相應的python棧幀上運行,棧幀表示程序運行時函數調用棧中的某一幀。想要得到某個函數相關的棧幀,則必須在調用這個函數且這個函數還沒有返回時獲取,可能經過inspect模塊的currentframe()函數獲取當前棧幀。python
棧幀對象中的3個經常使用的屬性:web
好比:ubuntu
>>> 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就是一個棧幀對象。ssh
舉例說明函數是如何運行的?async
>>> 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
運行過程:spa
解釋器調用 C函數 PyEval_EvalFrameEx()運行foo()的字節碼,它的參數爲foo()對應的棧幀對象,運行位置爲foo()對應的棧幀; 在運行過程當中,遇到 CALL_FUNCTION 時,它會爲函數bar()生成新的棧幀,而後又調用一個 PyEval_EvalFrameEx() 運行bar()對應的字節碼,……,如此遞歸,而後一層層的返回;指針
在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'
#這是一個函數 >>> 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
生成器函數就就是這麼運行的。
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
這個是在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