成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Python Yield Generator詳解

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

Python Yield Generator詳解

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

generator基礎

在python的函數(function)定義中,只要出現了yield表達式(Yield expression),那么事實上定義的是一個generator function, 調用這個generator function返回值是一個generator。這根普通的函數調用有所區別,For 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函數返回的是一個generator實例,generator有以下特別:

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

下面看一下測試代碼:

  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()    # ***次調用next 
  18.  
  19. before any yield 
  20.  
  21. 'first yield' 
  22.  
  23. >>> gen.next()    # 第二次調用next 
  24.  
  25. between yields 
  26.  
  27. 'second yield' 
  28.  
  29. >>> gen.next()    # 第三次調用next 
  30.  
  31. no yield anymore 
  32.  
  33. Traceback (most recent call last): 
  34.  
  35.   File "<stdin>", line 1, in <module> 
  36.  
  37. StopIteratio  

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

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

  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產生的generator與普通的function有什么區別呢?

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

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

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

generator應用

generator基礎應用

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

  1. RANGE_NUM = 100 
  2.  
  3.     for i in [x*x for x in range(RANGE_NUM)]: # ***種方法:對列表進行迭代 
  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進行迭代 
  12.  
  13.         # do sth for example 
  14.  
  15.         print i  

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

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

  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擁有生成無數多“返回值”的能力,使用者可以自己決定什么時候停止迭代。

generator高級應用

使用場景一:

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

  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是數據生產者,而count_words count_total_chars是數據的消費者。可以看到,數據只有在需要的時候去拉取的,而不是提前準備好。另外gen_words中 (w for w in line.split() if w.strip()) 也是產生了一個generator。

使用場景二:

一些編程場景中,一件事情可能需要執行一部分邏輯,然后等待一段時間、或者等待某個異步的結果、或者等待某個狀態,然后繼續執行另一部分邏輯。比如微服務架構中,服務A執行了一段邏輯之后,去服務B請求一些數據,然后在服務A上繼續執行。或者在游戲編程中,一個技能分成分多段,先執行一部分動作(效果),然后等待一段時間,然后再繼續。對于這種需要等待、而又不希望阻塞的情況,我們一般使用回調(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后的時間,5s之后再調用lambda函數,可見一段邏輯被分裂到兩個函數,而且還需要上下文的傳遞(如這里的參數a)。我們用yield來修改一下這個例子,yield返回值代表等待的時間。

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

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

  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()  

注意事項:

(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,而實際輸出是1 2 5 。為什么呢,如注釋所示,visit是一個generator function,所以第4行返回的是generator object,而代碼也沒這個generator實例迭代。那么改改代碼,對這個臨時的generator 進行迭代就行了。

  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的,當generator執行到這里的時候拋出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  

上面的代碼會報錯: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 
責任編輯:龐桂玉 來源: Python開發者
相關推薦

2013-01-30 10:12:14

Pythonyield

2023-12-25 14:50:39

Python迭代器

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編程

2021-04-22 21:15:38

Generator函數生成器

2020-10-25 20:05:29

Pythonyield開發

2023-12-11 13:59:00

YieldPython生成器函數

2022-03-03 08:30:41

GeneratorES6函數

2024-11-19 13:20:55

2024-12-13 08:02:10

PythonGenerator懶加載

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關鍵字yiel

2024-03-01 19:35:54

Mybatis開發

2010-03-17 18:38:53

Java編程語言

2020-04-20 08:22:41

SOC安全工具網絡攻擊
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美成人a| 国产精品女人久久久 | 亚洲国产高清在线 | av在线天堂网 | 亚洲一区二区在线 | 婷婷亚洲综合 | 男女视频在线免费观看 | 日本a视频 | 日韩欧美一区二区三区免费观看 | 日本一二三区在线观看 | 亚洲国产成人av好男人在线观看 | 欧美成年黄网站色视频 | 欧美5区 | 成人欧美一区二区三区黑人孕妇 | 日本在线黄色 | 精品一区在线免费观看 | 欧美精品成人一区二区三区四区 | 久久久成人一区二区免费影院 | 成人免费看片 | 亚洲综合婷婷 | 久久久久亚洲av毛片大全 | 久久久国产精品入口麻豆 | 欧美黑人狂野猛交老妇 | 人人天天操 | 免费黄网站在线观看 | 欧美精品一区二区三区在线播放 | 天天操天天干天天透 | 亚洲国产一区二区三区 | 日日噜噜夜夜爽爽狠狠 | 99精品视频免费在线观看 | 四虎影音 | 亚洲一区二区av | 美女视频一区二区三区 | 9191成人精品久久 | www.日本在线播放 | 色噜噜色综合 | 精品欧美一区二区精品久久 | 国产一区二区免费 | 国产精品久久久亚洲 | 日韩最新网址 | 91精品国产91久久久久久丝袜 |