本文將由淺入深詳細(xì)介紹yield
以及
generator
,包括以下內(nèi)容:什么
generator
,生成
generator
的方法,
generator
的特點,
generator
基礎(chǔ)及高級應(yīng)用場景,
generator
使用中的注意事項等等,一起來看看吧,希望對大家
學(xué)習(xí)python有所幫助
。
generator基礎(chǔ)
在python
的函數(shù)(
function
)定義中,只要出現(xiàn)了
yield
表達(dá)式(
Yield expression
),那么事實上定義的是一個
generator function
, 調(diào)用這個
generator function
返回值是一個
generator
。這根普通的函數(shù)調(diào)用有所區(qū)別,
For example
:
def
gen_generator():
yield 1
def
gen_value():
return 1
if __name__ == '__main__':
ret = gen_generator()
print ret, type(ret) #
ret = gen_value()
print ret, type(ret) # 1
從上面的代碼可以看出,gen_generator
函數(shù)返回的是一個
generator
實例,
generator
有以下特別:
·
遵循迭代器(
iterator
)協(xié)議,迭代器協(xié)議需要實現(xiàn)
__iter__
、
next
接口
·
能過多次進(jìn)入、多次返回,能夠暫停函數(shù)體中代碼的執(zhí)行
下面看一下測試代碼:
>>>
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()
?。?/span>
第一次調(diào)用next
before any
yield'first yield'>>> gen.next()
#
第二次調(diào)用next
between yields'second yield'>>> gen.next()
?。?/span>
第三次調(diào)用next
no
yield anymore
Traceback (most recent call last):
File "", line 1,
in
StopIteratio
調(diào)用gen example
方法并沒有輸出任何內(nèi)容,說明函數(shù)體的代碼尚未開始執(zhí)行。當(dāng)調(diào)用
generator
的
next
方法,
generator
會執(zhí)行到
yield
表達(dá)式處,返回
yield
表達(dá)式的內(nèi)容,然后暫停(掛起)在這個地方,所以第一次調(diào)用
next
打印第一句并返回
“first yield”
。
暫停
意味著方法的局部變量,指針信息,運行環(huán)境都保存起來,直到下一次調(diào)用next
方法恢復(fù)。第二次調(diào)用
next
之后就暫停在最后一個
yield
,再次調(diào)用
next()
方法,則會拋出
StopIteration
異常。
因為for
語句能自動捕獲
StopIteration
異常,所以
generator
(本質(zhì)上是任何
iterator
)較為常用的方法是在循環(huán)中使用:
def
generator_example():
yield 1
yield 2
if __name__ == '__main__':
for e
in generator_example():
print e
# output 1 2
generator function
產(chǎn)生的
generator
與普通的
function
有什么區(qū)別呢?
?。?/span>1
)
function
每次都是從第一行開始運行,而
generator
從上一次
yield
開始的地方運行
?。?/span>2
)
function
調(diào)用一次返回一個(一組)值,而
generator
可以多次返回
?。?/span>3
)
function
可以被無數(shù)次重復(fù)調(diào)用,而一個
generator
實例在
yield
最后一個值 或者
return
之后就不能繼續(xù)調(diào)用了
在函數(shù)中使用Yield
,然后調(diào)用該函數(shù)是生成
generator
的一種方式。另一種常見的方式是使用
generator expression
,
For example
:
>>> gen = (x * x
for x
in xrange(5))>>>
print gen
<generator objectat 0x02655710>
generator應(yīng)用
generator基礎(chǔ)應(yīng)用
為什么使用generator
呢,最重要的原因是可以
按需生成并“返回”結(jié)果
,而不是一次性產(chǎn)生所有的返回值,況且有時候根本就不知道“
所有的返回值
”
。比如對于下面的代碼:
RANGE_NUM = 100
for i
in [x*x
for x
in range(RANGE_NUM)]: #
第一種方法:對列表進(jìn)行迭代
#
do sth
for example
print i
for i
in (x*x
for x
in range(RANGE_NUM)): #
第二種方法:對
generator
進(jìn)行迭代
#
do sth
for example
print i
在上面的代碼中,兩個for
語句輸出是一樣的,代碼字面上看來也就是中括號與小括號的區(qū)別。但這點區(qū)別差異是很大的,第一種方法返回值是一個列表,第二個方法返回的是一個
generator
對象。隨著
RANGE_NUM
的變大,第一種方法返回的列表也越大,占用的內(nèi)存也越大;但是對于第二種方法沒有任何區(qū)別。
我們再來看一個可以“
返回
”
無窮多次的例子:
def
fib():
a, b = 1, 1
while
True:
yield a
a, b = b, a+b
這個generator
擁有生成無數(shù)多
“
返回值
”
的能力,使用者可以自己決定什么時候停止迭代。
generator高級應(yīng)用
使用場景一:
Generator
可用于產(chǎn)生數(shù)據(jù)流,
generator
并不立刻產(chǎn)生返回值,而是等到被需要的時候才會產(chǎn)生返回值,相當(dāng)于一個主動拉取的過程
(pull)
,比如現(xiàn)在有一個日志文件,每行產(chǎn)生一條記錄,對于每一條記錄,不同部門的人可能處理方式不同,但是我們可以提供一個公用的、按需生成的數(shù)據(jù)流。
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
是數(shù)據(jù)生產(chǎn)者,而
count_words count_total_chars
是數(shù)據(jù)的消費者??梢钥吹?,
數(shù)據(jù)只有在需要的時候去拉取的,而不是提前準(zhǔn)備好
。另外gen_words
中
(w for w in line.split() if w.strip())
也是產(chǎn)生了一個
generator
。
使用場景二:
一些編程場景中,一件事情可能需要執(zhí)行一部分邏輯,然后等待一段時間、或者等待某個異步的結(jié)果、或者等待某個狀態(tài),然后繼續(xù)執(zhí)行另一部分邏輯。比如微服務(wù)架構(gòu)中,服務(wù)A
執(zhí)行了一段邏輯之后,去服務(wù)
B
請求一些數(shù)據(jù),然后在服務(wù)
A
上繼續(xù)執(zhí)行?;蛘咴谟螒蚓幊讨?,一個技能分成分多段,先執(zhí)行一部分動作(效果),然后等待一段時間,然后再繼續(xù)。對于這種需要等待、而又不希望阻塞的情況,我們一般使用回調(diào)(
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
之后再調(diào)用
lambda
函數(shù),可見
一段邏輯被分裂到兩個函數(shù),而且還需要上下文的傳遞
(如這里的參數(shù)a
)。我們用
yield
來修改一下這個例子,
yield
返回值代表等待的時間。
@yield_dec
def
do(a):
print 'do', a
yield 5
print 'post_do', a
這里需要實現(xiàn)一個YieldManager
, 通過
yield_dec
這個
decrator
將
do
這個
generator
注冊到
YieldManager
,并在
5s
后調(diào)用
next
方法。
Yield
版本實現(xiàn)了和回調(diào)一樣的功能,但是看起來要清晰許多。下面給出一個簡單的實現(xiàn)以供參考:
# -*- 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()
注意事項:
?。?/span>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
進(jìn)行迭代就行了。
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
?。?/span>2
)
generator function
中使用
return
在python doc
中,明確提到是可以使用
return
的,當(dāng)
generator
執(zhí)行到這里的時候拋出
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
來源:
伯樂在線