自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Python Yield Generator詳解

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

Python Yield Generator詳解

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

generator基礎(chǔ)

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

  1. def gen_generator(): 
  2.  
  3.     yield 1 
  4.  
  5.   
  6.  
  7. def gen_value(): 
  8.  
  9.     return 1 
  10.  
  11.      
  12.  
  13. if __name__ == '__main__'
  14.  
  15.     ret = gen_generator() 
  16.  
  17.     print ret, type(ret)    #<generator object gen_generator at 0x02645648> <type 'generator'
  18.  
  19.     ret = gen_value() 
  20.  
  21.     print ret, type(ret)    # 1 <type 'int' 

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

  • 遵循迭代器(iterator)協(xié)議,迭代器協(xié)議需要實(shí)現(xiàn)__iter__、next接口
  • 能過多次進(jìn)入、多次返回,能夠暫停函數(shù)體中代碼的執(zhí)行

下面看一下測試代碼:

  1. >>> def gen_example(): 
  2.  
  3. ...     print 'before any yield' 
  4.  
  5. ...     yield 'first yield' 
  6.  
  7. ...     print 'between yields' 
  8.  
  9. ...     yield 'second yield' 
  10.  
  11. ...     print 'no yield anymore' 
  12.  
  13. ... 
  14.  
  15. >>> gen = gen_example() 
  16.  
  17. >>> gen.next()    # ***次調(diào)用next 
  18.  
  19. before any yield 
  20.  
  21. 'first yield' 
  22.  
  23. >>> gen.next()   ?。?nbsp;第二次調(diào)用next 
  24.  
  25. between yields 
  26.  
  27. 'second yield' 
  28.  
  29. >>> gen.next()    # 第三次調(diào)用next 
  30.  
  31. no yield anymore 
  32.  
  33. Traceback (most recent call last): 
  34.  
  35.   File "<stdin>", line 1, in <module> 
  36.  
  37. StopIteratio  

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

因?yàn)閒or語句能自動捕獲StopIteration異常,所以generator(本質(zhì)上是任何iterator)較為常用的方法是在循環(huán)中使用:

  1. def generator_example(): 
  2.  
  3.     yield 1 
  4.  
  5.     yield 2 
  6.  
  7.   
  8.  
  9. if __name__ == '__main__'
  10.  
  11.     for e in generator_example(): 
  12.  
  13.         print e 
  14.  
  15.         # output 1 2  

generator function產(chǎn)生的generator與普通的function有什么區(qū)別呢?

  1. function每次都是從***行開始運(yùn)行,而generator從上一次yield開始的地方運(yùn)行
  2. function調(diào)用一次返回一個(一組)值,而generator可以多次返回
  3. function可以被無數(shù)次重復(fù)調(diào)用,而一個generator實(shí)例在yield***一個值 或者return之后就不能繼續(xù)調(diào)用了

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

  1. >>> gen = (x * x for x in xrange(5)) 
  2.  
  3. >>> print gen 
  4.  
  5. <generator object <genexpr> at 0x02655710>  

generator應(yīng)用

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

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

  1. RANGE_NUM = 100 
  2.  
  3.     for i in [x*x for x in range(RANGE_NUM)]: # ***種方法:對列表進(jìn)行迭代 
  4.  
  5.         # do sth for example 
  6.  
  7.         print i 
  8.  
  9.   
  10.  
  11.     for i in (x*x for x in range(RANGE_NUM)): # 第二種方法:對generator進(jìn)行迭代 
  12.  
  13.         # do sth for example 
  14.  
  15.         print i  

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

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

  1. def fib(): 
  2.  
  3.     a, b = 1, 1 
  4.  
  5.     while True
  6.  
  7.         yield a 
  8.  
  9.         a, b = b, a+b  

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

generator高級應(yīng)用

使用場景一:

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

  1. def gen_data_from_file(file_name): 
  2.  
  3.     for line in file(file_name): 
  4.  
  5.         yield line 
  6.  
  7.   
  8.  
  9. def gen_words(line): 
  10.  
  11.     for word in (w for w in line.split() if w.strip()): 
  12.  
  13.         yield word 
  14.  
  15.   
  16.  
  17. def count_words(file_name): 
  18.  
  19.     word_map = {} 
  20.  
  21.     for line in gen_data_from_file(file_name): 
  22.  
  23.         for word in gen_words(line): 
  24.  
  25.             if word not in word_map: 
  26.  
  27.                 word_map[word] = 0 
  28.  
  29.             word_map[word] += 1 
  30.  
  31.     return word_map 
  32.  
  33.   
  34.  
  35. def count_total_chars(file_name): 
  36.  
  37.     total = 0 
  38.  
  39.     for line in gen_data_from_file(file_name): 
  40.  
  41.         total += len(line) 
  42.  
  43.     return total 
  44.  
  45.      
  46.  
  47. if __name__ == '__main__'
  48.  
  49.     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ù)的消費(fèi)者。可以看到,數(shù)據(jù)只有在需要的時(shí)候去拉取的,而不是提前準(zhǔn)備好。另外gen_words中 (w for w in line.split() if w.strip()) 也是產(chǎn)生了一個generator。

使用場景二:

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

  1. def do(a): 
  2.  
  3.      print 'do', a 
  4.  
  5.      CallBackMgr.callback(5, lambda a = a: post_do(a)) 
  6.  
  7. def post_do(a): 
  8.  
  9.     print 'post_do', a  

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

  1. @yield_dec 
  2.  
  3. def do(a): 
  4.  
  5.      print 'do', a 
  6.  
  7.      yield 5 
  8.  
  9.      print 'post_do', a  

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

  1. # -*- coding:utf-8 -*- 
  2.  
  3. import sys 
  4.  
  5. # import Timer 
  6.  
  7. import types 
  8.  
  9. import time 
  10.  
  11.   
  12.  
  13. class YieldManager(object): 
  14.  
  15.     def __init__(self, tick_delta = 0.01): 
  16.  
  17.         self.generator_dict = {} 
  18.  
  19.         # self._tick_timer = Timer.addRepeatTimer(tick_delta, lambda: self.tick()) 
  20.  
  21.   
  22.  
  23.     def tick(self): 
  24.  
  25.         cur = time.time() 
  26.  
  27.         for gene, t in self.generator_dict.items(): 
  28.  
  29.             if cur >= t: 
  30.  
  31.                 self._do_resume_genetator(gene,cur) 
  32.  
  33.   
  34.  
  35.     def _do_resume_genetator(self,gene, cur ): 
  36.  
  37.         try: 
  38.  
  39.             self.on_generator_excute(gene, cur) 
  40.  
  41.         except StopIteration,e: 
  42.  
  43.             self.remove_generator(gene) 
  44.  
  45.         except Exception, e: 
  46.  
  47.             print 'unexcepet error', type(e) 
  48.  
  49.             self.remove_generator(gene) 
  50.  
  51.   
  52.  
  53.     def add_generator(self, gen, deadline): 
  54.  
  55.         self.generator_dict[gen] = deadline 
  56.  
  57.   
  58.  
  59.     def remove_generator(self, gene): 
  60.  
  61.         del self.generator_dict[gene] 
  62.  
  63.   
  64.  
  65.     def on_generator_excute(self, gen, cur_time = None): 
  66.  
  67.         t = gen.next() 
  68.  
  69.         cur_time = cur_time or time.time() 
  70.  
  71.         self.add_generator(gen, t + cur_time) 
  72.  
  73.   
  74.  
  75. g_yield_mgr = YieldManager() 
  76.  
  77.   
  78.  
  79. def yield_dec(func): 
  80.  
  81.     def _inner_func(*args, **kwargs): 
  82.  
  83.         gen = func(*args, **kwargs) 
  84.  
  85.         if type(gen) is types.GeneratorType: 
  86.  
  87.             g_yield_mgr.on_generator_excute(gen) 
  88.  
  89.   
  90.  
  91.         return gen 
  92.  
  93.     return _inner_func 
  94.  
  95.   
  96.  
  97. @yield_dec 
  98.  
  99. def do(a): 
  100.  
  101.     print 'do', a 
  102.  
  103.     yield 2.5 
  104.  
  105.     print 'post_do', a 
  106.  
  107.     yield 3 
  108.  
  109.     print 'post_do again', a 
  110.  
  111.   
  112.  
  113. if __name__ == '__main__'
  114.  
  115.     do(1) 
  116.  
  117.     for i in range(1, 10): 
  118.  
  119.         print 'simulate a timer, %s seconds passed' % i 
  120.  
  121.         time.sleep(1) 
  122.  
  123.         g_yield_mgr.tick()  

注意事項(xiàng):

(1)Yield是不能嵌套的!

  1. def visit(data): 
  2.  
  3.     for elem in data: 
  4.  
  5.         if isinstance(elem, tuple) or isinstance(elem, list): 
  6.  
  7.             visit(elem) # here value retuened is generator 
  8.  
  9.         else
  10.  
  11.             yield elem 
  12.  
  13.              
  14.  
  15. if __name__ == '__main__'
  16.  
  17.     for e in visit([1, 2, (3, 4), 5]): 
  18.  
  19.         print e  

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

  1. def visit(data): 
  2.  
  3.     for elem in data: 
  4.  
  5.         if isinstance(elem, tuple) or isinstance(elem, list): 
  6.  
  7.             for e in visit(elem): 
  8.  
  9.                 yield e 
  10.  
  11.         else
  12.  
  13.             yield elem  

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

  1. def visit(data): 
  2.  
  3.      for elem in data: 
  4.  
  5.          if isinstance(elem, tuple) or isinstance(elem, list): 
  6.  
  7.              yield from visit(elem) 
  8.  
  9.          else
  10.  
  11.              yield elem  

(2)generator function中使用return

在python doc中,明確提到是可以使用return的,當(dāng)generator執(zhí)行到這里的時(shí)候拋出StopIteration異常。

  1. def gen_with_return(range_num): 
  2.  
  3.     if range_num < 0: 
  4.  
  5.         return 
  6.  
  7.     else
  8.  
  9.         for i in xrange(range_num): 
  10.  
  11.             yield i 
  12.  
  13.   
  14.  
  15. if __name__ == '__main__'
  16.  
  17.     print list(gen_with_return(-1)) 
  18.  
  19.     print list(gen_with_return(1))  

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

  1. def gen_with_return(range_num): 
  2.  
  3.      if range_num < 0: 
  4.  
  5.          return 0 
  6.  
  7.      else
  8.  
  9.          for i in xrange(range_num): 
  10.  
  11.              yield i  

上面的代碼會報(bào)錯: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 
責(zé)任編輯:龐桂玉 來源: Python開發(fā)者
相關(guān)推薦

2023-12-25 14:50:39

Python迭代器

2013-01-30 10:12:14

Pythonyield

2012-11-23 14:25:10

IBMdW

2021-03-15 12:23:24

Pythonyield代碼

2010-03-04 13:37:20

Python yiel

2021-05-13 09:11:11

PythonGo編程

2020-10-25 20:05:29

Pythonyield開發(fā)

2023-12-11 13:59:00

YieldPython生成器函數(shù)

2021-04-22 21:15:38

Generator函數(shù)生成器

2022-03-03 08:30:41

GeneratorES6函數(shù)

2024-12-13 08:02:10

PythonGenerator懶加載

2024-11-19 13:20:55

2009-06-29 08:59:05

hbm的generat

2009-06-29 08:58:06

Hibernate的g

2019-08-29 09:11:38

Pythonyield語法

2009-07-02 09:32:47

generator子元Hibernate

2009-12-18 11:37:54

Ruby關(guān)鍵字yiel

2024-03-01 19:35:54

Mybatis開發(fā)

2010-03-17 18:38:53

Java編程語言

2022-10-27 13:58:32

Python編程生成器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號