python中的生成器函數是如何工做的?

如下內容基於python3.4html

 

1. python中的普通函數是怎麼運行的?

當一個python函數在執行時,它會在相應的python棧幀上運行,棧幀表示程序運行時函數調用棧中的某一幀。想要得到某個函數相關的棧幀,則必須在調用這個函數且這個函數還沒有返回時獲取,可能經過inspect模塊的currentframe()函數獲取當前棧幀。python

棧幀對象中的3個經常使用的屬性:web

    • f_back : 調用棧的上一級棧幀
    • f_code: 棧幀對應的c
    • f_locals: 用在當前棧幀時的局部變量;

好比: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()對應的字節碼,……,如此遞歸,而後一層層的返回;指針

 

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

相關文章
相關標籤/搜索