當前位置:
首頁 > 知識 > Python yield與實現

Python yield與實現

(點擊

上方公眾號

,可快速關注)




來源: cococo點點

http://www.cnblogs.com/coder2012/p/4990834.html




Python yield與實現



yield

的功能類似於

return

,但是不同之處在於它返回的是

生成器


生成器


生成器是通過一個或多個

yield

表達式構成的函數,每一個生成器都是一個迭代器(但是迭代器不一定是生成器)。


如果一個函數包含

yield

關鍵字,這個函數就會變為一個生成器。


生成器並不會一次返回所有結果,而是每次遇到

yield

關鍵字後返回相應結果,並保留函數當前的運行狀態,等待下一次的調用。


由於生成器也是一個迭代器,那麼它就應該支持

next

方法來獲取下一個值。


基本操作



# 通過`yield`來創建生成器


def func

()

:


  

for

i

in

xrange

(

10

);


        

yield

i


 


# 通過列表來創建生成器


[

i

for

i

in

xrange

(

10

)]





# 調用如下


>>>

f

=

func

()


>>>

f

# 此時生成器還沒有運行


<

generator object

func

at

0x7fe01a853820

>


>>>

f

.

next

()

# 當i=0時,遇到yield關鍵字,直接返回


0


>>>

f

.

next

()

# 繼續上一次執行的位置,進入下一層循環


1


...


>>>

f

.

next

()


9


>>>

f

.

next

()

# 當執行完最後一次循環後,結束yield語句,生成StopIteration異常


Traceback

(

most recent call

last

)

:


  

File

"<stdin>"

,

line

1

,

in

<

module

>


StopIteration


>>>




除了next函數,生成器還支持send函數。該函數可以向生成器傳遞參數。





>>>

def func

()

:


...

    

n

=

0


...

    

while

1

:


...

        

n

=

yield

n

#可以通過send函數向n賦值


...


>>>

f

=

func

()


>>>

f

.

next

()

# 默認情況下n為0


0


>>>

f

.

send

(

1

)

#n賦值1


1


>>>

f

.

send

(

2

)


2


>>>




應用


最經典的例子,生成無限序列。


常規的解決方法是,生成一個滿足要求的很大的列表,這個列表需要保存在內存中,很明顯內存限制了這個問題。



def get_primes

(

start

)

:


    

for

element

in

magical_infinite_range

(

start

)

:


        

if

is_prime

(

element

)

:


            

return

element




如果使用生成器就不需要返回整個列表,每次都只是返回一個數據,避免了內存的限制問題。





def get_primes

(

number

)

:


    

while

True

:


        

if

is_prime

(

number

)

:


            

yield number


        

number

+=

1




生成器源碼分析


生成器的源碼在Objects/genobject.c


調用棧


在解釋生成器之前,需要講解一下Python虛擬機的調用原理。


Python虛擬機有一個棧幀的調用棧,其中棧幀的是PyFrameObject,位於Include/frameobject.h



typedef

struct

_frame

{


    

PyObject_VAR_HEAD


    

struct

_frame *

f_back

;

  

/* previous frame, or NULL */


    

PyCodeObject *

f_code

;

  

/* code segment */


    

PyObject *

f_builtins

;

  

/* builtin symbol table (PyDictObject) */


    

PyObject *

f_globals

;

    

/* global symbol table (PyDictObject) */


    

PyObject *

f_locals

;

    

/* local symbol table (any mapping) */


    

PyObject *

*

f_valuestack

;

    

/* points after the last local */


    

/* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.


       Frame evaluation usually NULLs it, but a frame that yields sets it


       to the current stack top. */


    

PyObject *

*

f_stacktop

;


    

PyObject *

f_trace

;

      

/* Trace function */


 


    

/* If an exception is raised in this frame, the next three are used to


     * record the exception info (if any) originally in the thread state.  See


     * comments before set_exc_info() -- it"s not obvious.


     * Invariant:  if _type is NULL, then so are _value and _traceback.


     * Desired invariant:  all three are NULL, or all three are non-NULL.  That


     * one isn"t currently true, but "should be".


     */


    

PyObject *

f_exc_type

,

*

f_exc_value

,

*

f_exc_traceback

;


 


    

PyThreadState *

f_tstate

;


    

int

f_lasti

;

        

/* Last instruction if called */


    

/* Call PyFrame_GetLineNumber() instead of reading this field


       directly.  As of 2.3 f_lineno is only valid when tracing is


       active (i.e. when f_trace is set).  At other times we use


       PyCode_Addr2Line to calculate the line from the current


       bytecode index. */


    

int

f_lineno

;

      

/* Current line number */


    

int

f_iblock

;

      

/* index in f_blockstack */


    

PyTryBlock

f_blockstack

[

CO_MAXBLOCKS

];

/* for try and loop blocks */


    

PyObject *

f_localsplus

[

1

];

  

/* locals+stack, dynamically sized */


}

PyFrameObject

;




棧幀保存了給出代碼的的信息和上下文,其中包含最後執行的指令,全局和局部命名空間,異常狀態等信息。f_valueblock保存了數據,b_blockstack保存了異常和循環控制方法。


舉一個例子來說明,



def foo

()

:


    

x

=

1


    

def bar

(

y

)

:


        

z

=

y

+

2

  

#




那麼,相應的調用棧如下,一個py文件,一個類,一個函數都是一個代碼塊,對應者一個Frame,保存著上下文環境以及位元組碼指令。





c

   ---------------------------


a

  |

bar

Frame

                 | ->

block

stack

:

[]


l

  |    

(

newest

)

              | ->

data

stack

:

[

1

,

2

]


l

   ---------------------------


   |

foo

Frame

                 | ->

block

stack

:

[]


s

  |                           | ->

data

stack

:

[.

bar

at

0x10d389680

>

,

1

]


t

   ---------------------------


a

  |

main

(

module

)

Frame

       | ->

block

stack

:

[]


c

  |      

(

oldest

)

            | ->

data

stack

:

[]


k

   ---------------------------




每一個棧幀都擁有自己的數據棧和block棧,獨立的數據棧和block棧使得解釋器可以中斷和恢復棧幀(生成器正式利用這點)。


Python代碼首先被編譯為位元組碼,再由Python虛擬機來執行。一般來說,一條Python語句對應著多條位元組碼(由於每條位元組碼對應著一條C語句,而不是一個機器指令,所以不能按照位元組碼的數量來判斷代碼性能)。


調用dis模塊可以分析位元組碼,



from dis import dis


 


dis

(

foo

)


 


  

5

          

0

LOAD

_

CONST

              

1

(

1

)

# 載入常量1


              

3

STORE

_

FAST

              

0

(

x

)

# x賦值為1


 


  

6

          

6

LOAD

_

CONST

              

2

(

<

code

>

)

# 載入常量2


              

9

MAKE

_

FUNCTION

            

0

# 創建函數


            

12

STORE

_

FAST

              

1

(

bar

)


 


  

9

          

15

LOAD

_

FAST

                

1

(

bar

)


            

18

LOAD

_

FAST

                

0

(

x

)


            

21

CALL

_

FUNCTION

            

1

  

# 調用函數


            

24

RETURN_VALUE

        </

code

>




其中,






第一行為代碼行號;


第二行為偏移地址;


第三行為位元組碼指令;


第四行為指令參數;


第五行為參數解釋。





生成器源碼分析


由了上面對於調用棧的理解,就可以很容易的明白生成器的具體實現。


生成器的源碼位於object/genobject.c


生成器的創建




PyObject *


PyGen_New

(

PyFrameObject *

f

)


{


    

PyGenObject *

gen

=

PyObject_GC_New

(

PyGenObject

,

&

PyGen_Type

);

# 創建生成器對象


    

if

(

gen

==

NULL

)

{


        

Py_DECREF

(

f

);


        

return

NULL

;


    

}


    

gen

->

gi_frame

=

f

;

# 賦予代碼塊


    

Py_INCREF

(

f

->

f_code

);

# 引用計數+1


    

gen

->

gi_code

=

(

PyObject *

)(

f

->

f_code

);


    

gen

->

gi_running

=

0

;

# 0表示為執行,也就是生成器的初始狀態


    

gen

->

gi_weakreflist

=

NULL

;


    

_PyObject_GC_TRACK

(

gen

);

# GC跟蹤


    

return

(

PyObject *

)

gen

;


}




send與next


next

send

函數,如下




static

PyObject *


gen_iternext

(

PyGenObject *

gen

)


{


    

return

gen_send_ex

(

gen

,

NULL

,

0

);


}


 


 


static

PyObject *


gen_send

(

PyGenObject *

gen

,

PyObject *

arg

)


{


    

return

gen_send_ex

(

gen

,

arg

,

0

);


}



從上面的代碼中可以看到,

send

next

都是調用的同一函數

gen_send_ex

,區別在於是否帶有參數。






static

PyObject *


gen_send_ex

(

PyGenObject *

gen

,

PyObject *

arg

,

int

exc

)


{


    

PyThreadState *

tstate

=

PyThreadState_GET

();


    

PyFrameObject *

f

=

gen

->

gi_frame

;


    

PyObject *

result

;


 


    

if

(

gen

->

gi_running

)

{

# 判斷生成器是否已經運行


        

PyErr_SetString

(

PyExc_ValueError

,


                        

"generator already executing"

);


        

return

NULL

;


    

}


    

if

(

f

==

NULL

||

f

->

f_stacktop

==

NULL

)

{

# 如果代碼塊為空或調用棧為空,則拋出StopIteration異常


        

/* Only set exception if called from send() */


        

if

(

arg

&& !

exc

)


            

PyErr_SetNone

(

PyExc_StopIteration

);


        

return

NULL

;


    

}


 


    

if

(

f

->

f_lasti

== -

1

)

{

# f_lasti=1 代表首次執行


        

if

(

arg

&&

arg

!=

Py_None

)

{

# 首次執行不允許帶有參數


            

PyErr_SetString

(

PyExc_TypeError

,


                            

"can"t send non-None value to a "


                            

"just-started generator"

);


            

return

NULL

;


        

}


    

}

else

{


        

/* Push arg onto the frame"s value stack */


        

result

=

arg

?

arg

:

Py_None

;


        

Py_INCREF

(

result

);

# 該參數引用計數+1


        *

(

f

->

f_stacktop

++

)

=

result

;

# 參數壓棧


    

}


 


    

/* Generators always return to their most recent caller, not


     * necessarily their creator. */


    

f

->

f_tstate

=

tstate

;


    

Py_XINCREF

(

tstate

->

frame

);


    

assert

(

f

->

f_back

==

NULL

);


    

f

->

f_back

=

tstate

->

frame

;


 


    

gen

->

gi_running

=

1

;

# 修改生成器執行狀態


    

result

=

PyEval_EvalFrameEx

(

f

,

exc

);

# 執行位元組碼


    

gen

->

gi_running

=

0

;

# 恢復為未執行狀態


 


    

/* Don"t keep the reference to f_back any longer than necessary.  It


     * may keep a chain of frames alive or it could create a reference


     * cycle. */


    

assert

(

f

->

f_back

==

tstate

->

frame

);


    

Py_CLEAR

(

f

->

f_back

);


    

/* Clear the borrowed reference to the thread state */


    

f

->

f_tstate

=

NULL

;


 


    

/* If the generator just returned (as opposed to yielding), signal


     * that the generator is exhausted. */


    

if

(

result

==

Py_None

&&

f

->

f_stacktop

==

NULL

)

{


        

Py_DECREF

(

result

);


        

result

=

NULL

;


        

/* Set exception if not called by gen_iternext() */


        

if

(

arg

)


            

PyErr_SetNone

(

PyExc_StopIteration

);


    

}


 


    

if

(

!

result

||

f

->

f_stacktop

==

NULL

)

{


        

/* generator can"t be rerun, so release the frame */


        

Py_DECREF

(

f

);


        

gen

->

gi_frame

=

NULL

;


    

}


 


    

return

result

;


}





位元組碼的執行


PyEval_EvalFrameEx函數的功能為執行位元組碼並返回結果。



# 主要流程如下,


for

(;;)

{


  

switch

(

opcode

)

{

# opcode為操作碼,對應著各種操作


        

case

NOP

:


            

goto

  

fast_next_opcode

;


        

...


        

...


        

case

YIELD_VALUE

:

# 如果操作碼是yield


            

retval

=

POP

();


            

f

->

f_stacktop

=

stack_pointer

;


            

why

=

WHY_YIELD

;


            

goto

fast_yield

;

# 利用goto跳出循環


    

}


}


 


fast_yield

:


    

...


return

vetval

;

# 返回結果




舉一個例子,f_back上一個Frame,f_lasti上一次執行的指令的偏移量,






import sys


from dis import dis


 


 


def func

()

:


    

f

=

sys

.

_getframe

(

0

)


    

print

f

.

f_lasti


    

print

f

.

f_back


    

yield

1


 


    

print

f

.

f_lasti


    

print

f

.

f_back


    

yield

2


 


 


a

=

func

()


dis

(

func

)


a

.

next

()


a

.

next

()



結果如下,其中第三行的英文為操作碼,對應著上面的opcode,每次switch都是在不同的opcode之間進行選擇。






6

          

0

LOAD

_

GLOBAL

              

0

(

sys

)


              

3

LOAD

_

ATTR

                

1

(

_getframe

)


              

6

LOAD

_

CONST

              

1

(

0

)


              

9

CALL

_

FUNCTION

            

1


            

12

STORE

_

FAST

              

0

(

f

)


 


  

7

          

15

LOAD

_

FAST

                

0

(

f

)


            

18

LOAD

_

ATTR

                

2

(

f_lasti

)


            

21

PRINT

_

ITEM

          


            

22

PRINT

_

NEWLINE

      


 


  

8

          

23

LOAD

_

FAST

                

0

(

f

)


            

26

LOAD

_

ATTR

                

3

(

f_back

)


            

29

PRINT

_

ITEM

          


            

30

PRINT

_

NEWLINE

      


 


  

9

          

31

LOAD

_

CONST

              

2

(

1

)


            

34

YIELD_VALUE

    

# 此時操作碼為YIELD_VALUE,直接跳轉上述goto語句,此時f_lasti為當前指令,f_back為當前frame


            

35

POP

_

TOP

            


 


11

          

36

LOAD

_

FAST

                

0

(

f

)


            

39

LOAD

_

ATTR

                

2

(

f_lasti

)


            

42

PRINT

_

ITEM

          


            

43

PRINT

_

NEWLINE

      


 


12

          

44

LOAD

_

FAST

                

0

(

f

)


            

47

LOAD

_

ATTR

                

3

(

f_back

)


            

50

PRINT

_

ITEM

          


            

51

PRINT

_

NEWLINE

      


 


13

          

52

LOAD

_

CONST

              

3

(

2

)


            

55

YIELD

_

VALUE

        


            

56

POP

_

TOP

            


            

57

LOAD

_

CONST

              

0

(

None

)


            

60

RETURN

_

VALUE

        


18


<

frame object

at

0x7fa75fcebc20

>

#和下面的frame相同,屬於同一個frame,也就是說在同一個函數(命名空間)內,frame是同一個。


39


<

frame object

at

0x7fa75fcebc20

>







看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發者 的精彩文章:

面向對象:葉子的離開是風的追求還是樹的不挽留?我若是飄離的葉子,你是否是溫潤我的春泥?
面向對象:我相信,未來要和我共度一生的那個人,一定也懷著滿心的期待,擁著一腔孤勇,穿過茫茫人海 也要來與我相見

TAG:Python開發者 |