99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

Python學(xué)習(xí)之Yield與 Generator

發(fā)布時間:2017-07-26 09:30  回復(fù):0  查看:2260   最后回復(fù):2017-07-26 09:30  
本文將由淺入深詳細(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
來源: 伯樂在線
您還未登錄,請先登錄

熱門帖子

最新帖子

?