最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

由淺入深講解python中的yield與generator

 更新時(shí)間:2017年04月05日 08:37:20   作者:xybaby  
這篇文章主要由淺入深講解了python中yield與generator的相關(guān)資料,文中介紹的非常詳細(xì),對大家具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。

前言

本文將由淺入深詳細(xì)介紹yield以及generator,包括以下內(nèi)容:什么generator,生成generator的方法,generator的特點(diǎn),generator基礎(chǔ)及高級應(yīng)用場景,generator使用中的注意事項(xiàng)。本文不包括enhanced generator即pep342相關(guān)內(nèi)容,這部分內(nèi)容在之后介紹。

generator基礎(chǔ)

在python的函數(shù)(function)定義中,只要出現(xiàn)了yield表達(dá)式(Yield expression),那么事實(shí)上定義的是一個(gè)generator function, 調(diào)用這個(gè)generator function返回值是一個(gè)generator。這根普通的函數(shù)調(diào)用有所區(qū)別,F(xiàn)or 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函數(shù)返回的是一個(gè)generator實(shí)例

generator有以下特別:

     •遵循迭代器(iterator)協(xié)議,迭代器協(xié)議需要實(shí)現(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()   ?。?第一次調(diào)用next
before any yield
'first yield'
>>> gen.next()   ?。?第二次調(diào)用next
between yields
'second yield'
>>> gen.next()   ?。?第三次調(diào)用next
no yield anymore
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteratio

調(diào)用gen example方法并沒有輸出任何內(nèi)容,說明函數(shù)體的代碼尚未開始執(zhí)行。當(dāng)調(diào)用generator的next方法,generator會(huì)執(zhí)行到y(tǒng)ield 表達(dá)式處,返回yield表達(dá)式的內(nèi)容,然后暫停(掛起)在這個(gè)地方,所以第一次調(diào)用next打印第一句并返回“first yield”。 暫停意味著方法的局部變量,指針信息,運(yùn)行環(huán)境都保存起來,直到下一次調(diào)用next方法恢復(fù)。第二次調(diào)用next之后就暫停在最后一個(gè)yield,再次調(diào)用next()方法,則會(huì)拋出StopIteration異常?!?/p>

因?yàn)閒or語句能自動(dòng)捕獲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ū)別呢

 ?。?)function每次都是從第一行開始運(yùn)行,而generator從上一次yield開始的地方運(yùn)行

 ?。?)function調(diào)用一次返回一個(gè)(一組)值,而generator可以多次返回

 ?。?)function可以被無數(shù)次重復(fù)調(diào)用,而一個(gè)generator實(shí)例在yield最后一個(gè)值 或者return之后就不能繼續(xù)調(diào)用了

在函數(shù)中使用Yield,然后調(diào)用該函數(shù)是生成generator的一種方式。另一種常見的方式是使用generator expression,F(xiàn)or example:

  >>> gen = (x * x for x in xrange(5))
  >>> print gen
  <generator object <genexpr> at 0x02655710>

generator應(yīng)用

generator基礎(chǔ)應(yīng)用  

  為什么使用generator呢,最重要的原因是可以按需生成并“返回”結(jié)果,而不是一次性產(chǎn)生所有的返回值,況且有時(shí)候根本就不知道“所有的返回值”。

比如對于下面的代碼  

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

在上面的代碼中,兩個(gè)for語句輸出是一樣的,代碼字面上看來也就是中括號與小括號的區(qū)別。但這點(diǎn)區(qū)別差異是很大的,第一種方法返回值是一個(gè)列表,第二個(gè)方法返回的是一個(gè)generator對象。隨著RANGE_NUM的變大,第一種方法返回的列表也越大,占用的內(nèi)存也越大;但是對于第二種方法沒有任何區(qū)別。

我們再來看一個(gè)可以“返回”無窮多次的例子:

def fib():
 a, b = 1, 1
 while True:
 yield a
 a, b = b, a+b 

這個(gè)generator擁有生成無數(shù)多“返回值”的能力,使用者可以自己決定什么時(shí)候停止迭代

generator高級應(yīng)用

使用場景一:  

Generator可用于產(chǎn)生數(shù)據(jù)流, generator并不立刻產(chǎn)生返回值,而是等到被需要的時(shí)候才會(huì)產(chǎn)生返回值,相當(dāng)于一個(gè)主動(dòng)拉取的過程(pull),比如現(xiàn)在有一個(gè)日志文件,每行產(chǎn)生一條記錄,對于每一條記錄,不同部門的人可能處理方式不同,但是我們可以提供一個(gè)公用的、按需生成的數(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一個(gè)講座。gen_words gen_data_from_file是數(shù)據(jù)生產(chǎn)者,而count_words count_total_chars是數(shù)據(jù)的消費(fèi)者。可以看到,數(shù)據(jù)只有在需要的時(shí)候去拉取的,而不是提前準(zhǔn)備好。另外gen_words中 (w for w in line.split() if w.strip()) 也是產(chǎn)生了一個(gè)generator

使用場景二:

一些編程場景中,一件事情可能需要執(zhí)行一部分邏輯,然后等待一段時(shí)間、或者等待某個(gè)異步的結(jié)果、或者等待某個(gè)狀態(tài),然后繼續(xù)執(zhí)行另一部分邏輯。比如微服務(wù)架構(gòu)中,服務(wù)A執(zhí)行了一段邏輯之后,去服務(wù)B請求一些數(shù)據(jù),然后在服務(wù)A上繼續(xù)執(zhí)行。或者在游戲編程中,一個(gè)技能分成分多段,先執(zhí)行一部分動(dòng)作(效果),然后等待一段時(shí)間,然后再繼續(xù)。對于這種需要等待、而又不希望阻塞的情況,我們一般使用回調(diào)(callback)的方式。下面舉一個(gè)簡單的例子:

 def do(a):
 print 'do', a
 CallBackMgr.callback(5, lambda a = a: post_do(a))
 
 def post_do(a):
 print 'post_do', a

這里的CallBackMgr注冊了一個(gè)5s后的時(shí)間,5s之后再調(diào)用lambda函數(shù),可見一段邏輯被分到兩個(gè)函數(shù),而且還需要上下文的傳遞(如這里的參數(shù)a)。我們用yield來修改一下這個(gè)例子,yield返回值代表等待的時(shí)間。

 @yield_dec
 def do(a):
 print 'do', a
 yield 5
 print 'post_do', a

這里需要實(shí)現(xiàn)一個(gè)YieldManager, 通過yield_dec這個(gè)decrator將do這個(gè)generator注冊到Y(jié)ieldManager,并在5s后調(diào)用next方法。Yield版本實(shí)現(xiàn)了和回調(diào)一樣的功能,但是看起來要清晰許多。

下面給出一個(gè)簡單的實(shí)現(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()

注意事項(xiàng):

(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

上面的代碼訪問嵌套序列里面的每一個(gè)元素,我們期望的輸出是1 2 3 4 5,而實(shí)際輸出是1  2  5 。為什么呢,如注釋所示,visit是一個(gè)generator function,所以第4行返回的是generator object,而代碼也沒這個(gè)generator實(shí)例迭代。那么改改代碼,對這個(gè)臨時(shí)的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,這個(gè)語法是在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的,當(dāng)generator執(zhí)行到這里的時(shí)候拋出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

上面的代碼報(bào)錯(cuò):SyntaxError: 'return' with argument inside generator

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

最新評論

浑源县| 清镇市| 曲麻莱县| 永丰县| 奉贤区| 英超| 沙湾县| 静乐县| 达州市| 伊春市| 乌审旗| 宜黄县| 湟中县| 淮南市| 县级市| 晋城| 莲花县| 运城市| 资阳市| 夏河县| 兴文县| 西藏| 澄城县| 增城市| 从化市| 理塘县| 古田县| 遵化市| 台北县| 博罗县| 高雄县| 浏阳市| 秀山| 翼城县| 富源县| 阿坝县| 额济纳旗| 新绛县| 光山县| 南雄市| 灵丘县|