當前位置:
首頁 > 知識 > Python Yield Generator 詳解

Python Yield Generator 詳解

(點擊

上方藍字

,快速關注我們)




來源:xybaby


www.cnblogs.com/xybaby/p/6322376.html


如有好文章投稿,請點擊 → 這裡了解詳情




本文將由淺入深詳細介紹yield以及generator,包括以下內容:什麼generator,生成generator的方法,generator的特點,generator基礎及高級應用場景,generator使用中的注意事項。本文不包括enhanced generator即pep342相關內容。



generator基礎




在python的函數(function)定義中,只要出現了yield表達式(Yield expression),那麼事實上定義的是一個generator function, 調用這個generator function返回值是一個generator。這根普通的函數調用有所區別,For example:





def gen_generator

()

:

    

yield

1


 


def gen_value

()

:


    

return

1


    


if

__name__

==

"__main__"

:


    

ret

=

gen_generator

()


    

print

ret

,

type

(

ret

)

    

#<generator object gen_generator at 0x02645648> <type "generator">


    

ret

=

gen_value

()


    

print

ret

,

type

(

ret

)

    

# 1 <type "int">




從上面的代碼可以看出,gen_generator函數返回的是一個generator實例,generator有以下特別:






  • 遵循迭代器(iterator)協議,迭代器協議需要實現__iter__、next介面



  • 能過多次進入、多次返回,能夠暫停函數體中代碼的執行




下面看一下測試代碼:





>>>

def

gen_example

()

:


...

    

print

"before any yield"


...

    

yield

"first yield"


...

    

print

"between yields"


...

    

yield

"second yield"


...

    

print

"no yield anymore"


...


>>>

gen

=

gen_example

()


>>>

gen

.

next

()

    #

第一次調用

next


before any

yield


"first yield"


>>>

gen

.

next

()

    #

第二次調用

next


between

yields


"second yield"


>>>

gen

.

next

()

    #

第三次調用

next


no

yield

anymore


Traceback

(

most recent call

last

)

:


  

File

"<stdin>"

,

line

1

,

in

<

module

>


StopIteratio




調用gen example方法並沒有輸出任何內容,說明函數體的代碼尚未開始執行。當調用generator的next方法,generator會執行到yield 表達式處,返回yield表達式的內容,然後暫停(掛起)在這個地方,所以第一次調用next列印第一句並返回「first yield」。 暫停意味著方法的局部變數,指針信息,運行環境都保存起來,直到下一次調用next方法恢復。第二次調用next之後就暫停在最後一個yield,再次調用next()方法,則會拋出StopIteration異常。




因為for語句能自動捕獲StopIteration異常,所以generator(本質上是任何iterator)較為常用的方法是在循環中使用:





def generator_example

()

:


    

yield

1


    

yield

2


 


if

__name__

==

"__main__"

:


    

for

e

in

generator_example

()

:


        

print

e


        

# output 1 2




generator function產生的generator與普通的function有什麼區別呢?






  1. function每次都是從第一行開始運行,而generator從上一次yield開始的地方運行



  2. function調用一次返回一個(一組)值,而generator可以多次返回



  3. function可以被無數次重複調用,而一個generator實例在yield最後一個值 或者return之後就不能繼續調用了




在函數中使用Yield,然後調用該函數是生成generator的一種方式。另一種常見的方式是使用generator expression,For example:





>>>

gen

=

(

x

*

x

for

x

in

xrange

(

5

))


>>>

print

gen


<

generator object

<

genexpr

>

at

0x02655710

>




generator應用




generator基礎應用




為什麼使用generator呢,最重要的原因是可以按需生成並「返回」結果,而不是一次性產生所有的返回值,況且有時候根本就不知道「所有的返回值」。比如對於下面的代碼:





RANGE_NUM

=

100


    

for

i

in

[

x*

x

for

x

in

range

(

RANGE_NUM

)]

:

# 第一種方法:對列表進行迭代


        

# do sth for example


        

print

i


 


    

for

i

in

(

x*

x

for

x

in

range

(

RANGE_NUM

))

:

# 第二種方法:對generator進行迭代


        

# do sth for example


        

print

i




在上面的代碼中,兩個for語句輸出是一樣的,代碼字面上看來也就是中括弧與小括弧的區別。但這點區別差異是很大的,第一種方法返回值是一個列表,第二個方法返回的是一個generator對象。隨著RANGE_NUM的變大,第一種方法返回的列表也越大,佔用的內存也越大;但是對於第二種方法沒有任何區別。




我們再來看一個可以「返回」無窮多次的例子:





def fib

()

:


    

a

,

b

=

1

,

1


    

while

True

:


        

yield

a


        

a

,

b

=

b

,

a

+

b




這個generator擁有生成無數多「返回值」的能力,使用者可以自己決定什麼時候停止迭代。




generator高級應用




使用場景一:




Generator可用於產生數據流, generator並不立刻產生返回值,而是等到被需要的時候才會產生返回值,相當於一個主動拉取的過程(pull),比如現在有一個日誌文件,每行產生一條記錄,對於每一條記錄,不同部門的人可能處理方式不同,但是我們可以提供一個公用的、按需生成的數據流。





def gen_data_from_file

(

file_name

)

:


    

for

line

in

file

(

file_name

)

:


        

yield line


 


def gen_words

(

line

)

:


    

for

word

in

(

w

for

w

in

line

.

split

()

if

w

.

strip

())

:


        

yield

word


 


def count_words

(

file_name

)

:


    

word_map

=

{}


    

for

line

in

gen_data_from_file

(

file_name

)

:


        

for

word

in

gen_words

(

line

)

:


            

if

word

not

in

word_map

:


                

word_map

[

word

]

=

0


            

word_map

[

word

]

+=

1


    

return

word_map


 


def count_total_chars

(

file_name

)

:


    

total

=

0


    

for

line

in

gen_data_from_file

(

file_name

)

:


        

total

+=

len

(

line

)


    

return

total


    


if

__name__

==

"__main__"

:


    

print count_words

(

"test.txt"

),

count_total_chars

(

"test.txt"

)




上面的例子來自08年的PyCon一個講座。gen_words gen_data_from_file是數據生產者,而count_words count_total_chars是數據的消費者。可以看到,

數據只有在需要的時候去拉取的,而不是提前準備好。

另外gen_words中 (w for w in line.split() if w.strip()) 也是產生了一個generator。




使用場景二:




一些編程場景中,一件事情可能需要執行一部分邏輯,然後等待一段時間、或者等待某個非同步的結果、或者等待某個狀態,然後繼續執行另一部分邏輯。比如微服務架構中,服務A執行了一段邏輯之後,去服務B請求一些數據,然後在服務A上繼續執行。或者在遊戲編程中,一個技能分成分多段,先執行一部分動作(效果),然後等待一段時間,然後再繼續。對於這種需要等待、而又不希望阻塞的情況,我們一般使用回調(callback)的方式。下面舉一個簡單的例子:





def

do

(

a

)

:


    

print

"do"

,

a


    

CallBackMgr

.

callback

(

5

,

lambda

a

=

a

:

post_do

(

a

))



def post_do

(

a

)

:


    

print

"post_do"

,

a




這裡的CallBackMgr註冊了一個5s後的時間,5s之後再調用lambda函數,可見

一段邏輯被分裂到兩個函數,而且還需要上下文的傳遞(如這裡的參數a)

。我們用yield來修改一下這個例子,yield返回值代表等待的時間。





@

yield_dec


def

do

(

a

)

:


    

print

"do"

,

a


    

yield

5


    

print

"post_do"

,

a




這裡需要實現一個YieldManager, 通過yield_dec這個decrator將do這個generator註冊到YieldManager,並在5s後調用next方法。Yield版本實現了和回調一樣的功能,但是看起來要清晰許多。下面給出一個簡單的實現以供參考:





# -*- coding:utf-8 -*-


import

sys


# import Timer


import types


import time


 


class

YieldManager

(

object

)

:


    

def __init__

(

self

,

tick_delta

=

0.01

)

:


        

self

.

generator_dict

=

{}


        

# self._tick_timer = Timer.addRepeatTimer(tick_delta, lambda: self.tick())


 


    

def tick

(

self

)

:


        

cur

=

time

.

time

()


        

for

gene

,

t

in

self

.

generator_dict

.

items

()

:


            

if

cur

>=

t

:


                

self

.

_do_resume_genetator

(

gene

,

cur

)


 


    

def _do_resume_genetator

(

self

,

gene

,

cur

)

:


        

try

:


            

self

.

on_generator_excute

(

gene

,

cur

)


        

except

StopIteration

,

e

:


            

self

.

remove_generator

(

gene

)


        

except

Exception

,

e

:


            

print

"unexcepet error"

,

type

(

e

)


            

self

.

remove_generator

(

gene

)


 


    

def add_generator

(

self

,

gen

,

deadline

)

:


        

self

.

generator_dict

[

gen

]

=

deadline


 


    def remove_generator

(

self

,

gene

)

:


        

del

self

.

generator_dict

[

gene

]


 


    

def on_generator_excute

(

self

,

gen

,

cur_time

=

None

)

:


        

t

=

gen

.

next

()


        

cur_time

=

cur_time

or

time

.

time

()


        

self

.

add_generator

(

gen

,

t

+

cur_time

)


 


g_yield_mgr

=

YieldManager

()


 


def yield_dec

(

func

)

:


    

def _inner_func

(

*

args

,

**

kwargs

)

:


        

gen

=

func

(

*

args

,

**

kwargs

)


        

if

type

(

gen

)

is

types

.

GeneratorType

:


            

g_yield_mgr

.

on_generator_excute

(

gen

)


 


        

return

gen


    

return

_inner

_

func


 


@

yield_dec


def

do

(

a

)

:


    

print

"do"

,

a


    

yield

2.5


    

print

"post_do"

,

a


    

yield

3


    

print

"post_do again"

,

a


 


if

__name__

==

"__main__"

:


    

do

(

1

)


    

for

i

in

range

(

1

,

10

)

:


        

print

"simulate a timer, %s seconds passed"

%

i


        

time

.

sleep

(

1

)


        

g_yield_mgr

.

tick

()




注意事項:




(1)Yield是不能嵌套的!





def visit

(

data

)

:


    

for

elem

in

data

:


        

if

isinstance

(

elem

,

tuple

)

or

isinstance

(

elem

,

list

)

:


            

visit

(

elem

)

# here value retuened is generator


        

else

:


            

yield elem


            


if

__name__

==

"__main__"

:


    

for

e

in

visit

([

1

,

2

,

(

3

,

4

),

5

])

:


        

print

e




上面的代碼訪問嵌套序列裡面的每一個元素,我們期望的輸出是1 2 3 4 5,而實際輸出是1 2 5 。為什麼呢,如注釋所示,visit是一個generator function,所以第4行返回的是generator object,而代碼也沒這個generator實例迭代。那麼改改代碼,對這個臨時的generator 進行迭代就行了。





def visit

(

data

)

:


    

for

elem

in

data

:


        

if

isinstance

(

elem

,

tuple

)

or

isinstance

(

elem

,

list

)

:


            

for

e

in

visit

(

elem

)

:


                

yield

e


        

else

:


            

yield

elem




或者在python3.3中 可以使用yield from,這個語法是在pep380加入的:





def visit

(

data

)

:


    

for

elem

in

data

:


        

if

isinstance

(

elem

,

tuple

)

or

isinstance

(

elem

,

list

)

:


            

yield from visit

(

elem

)


        

else

:


            

yield

elem




(2)generator function中使用return




在python doc中,明確提到是可以使用return的,當generator執行到這裡的時候拋出StopIteration異常。





def gen_with_return

(

range_num

)

:


    

if

range_num

<

0

:


        

return


    

else

:


        

for

i

in

xrange

(

range_num

)

:


            

yield

i


 


if

__name__

==

"__main__"

:


    

print list

(

gen_with_return

(

-

1

))


    

print list

(

gen_with_return

(

1

))




但是,generator function中的return是不能帶任何返回值的。





def gen_with_return

(

range_num

)

:


    

if

range_num

<

0

:


        

return

0


    

else

:


        

for

i

in

xrange

(

range_num

)

:


            

yield

i




上面的代碼會報錯:SyntaxError: 『return』 with argument inside generator




參考






  • http://www.dabeaz.com/generators-uk/



  • https://www.python.org/dev/peps/pep-0380/



  • http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do



  • http://stackoverflow.com/questions/15809296/python-syntaxerror-return-with-argument-inside-generator




看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


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

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


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

Python Decorator 基礎
谷歌工程師的 TensorFlow 成長之路
基於協程的 Python 網路庫 gevent 介紹
從「猿」到「金剛」,機器學習讓你在職業生涯超進化!

TAG:Python開發者 |

您可能感興趣

Python super 詳解
Python中使用Type hinting 和 annotations
Bayesian Personalized Ranking 演算法解析及Python實現
Python之tworoutine
Python幫助Youtube打敗了Google Video
Python 標準庫精華: collections.Counter
可以拋棄 Python?Google 開源 Swift for TensorFlow 意味什麼
如何用 Python 寫 Alfred Workflow
在Python中使用Elasticsearch
使用 Python的urlliib.parse 庫解析 URL
Python 模塊 urllib.parse
為什麼Python如此火?Why Python is so popular?
selenium+python點擊 display:none元素解決方法匯總
基於Python Selenium Unittest PO設計模式詳解
可以拋棄 Python 了?Google 開源 Swift for TensorFlow 意味著什麼
Python 特殊函數(lambda,map,filter,reduce)
Python 的 ChatOps 庫:Opsdroid 和 Errbot
跳一跳工具py輔助 Python提示No module named sklearn 解決方法
Python模塊——contextlib和urllib
Python的"print「函數在」Hello World"之外的延伸