欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

前言

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

generator基礎

在python的函數(shù)(function)定義中,只要出現(xiàn)了yield表達式(Yield expression),那么事實上定義的是一個generator function, 調(diào)用這個generator function返回值是一個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ù)返回的是一個generator實例

generator有以下特別:

     •遵循迭代器(iterator)協(xié)議,迭代器協(xié)議需要實現(xiàn)__iter__ 、next接口

     •能過多次進入、多次返回,能夠暫停函數(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í)行。當調(diào)用generator的next方法,generator會執(zhí)行到y(tǒng)ield 表達式處,返回yield表達式的內(nèi)容,然后暫停(掛起)在這個地方,所以第一次調(diào)用next打印第一句并返回“first yield”。 暫停意味著方法的局部變量,指針信息,運行環(huán)境都保存起來,直到下一次調(diào)用next方法恢復。第二次調(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ū)別呢

  (1)function每次都是從第一行開始運行,而generator從上一次yield開始的地方運行

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

 ?。?)function可以被無數(shù)次重復調(diào)用,而一個generator實例在yield最后一個值 或者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應用

generator基礎應用  

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

比如對于下面的代碼  

RANGE_NUM = 100
 for i in [x*x for x in range(RANGE_NUM)]: # 第一種方法:對列表進行迭代
 # do sth for example
 print i

 for i in (x*x for x in range(RANGE_NUM)): # 第二種方法:對generator進行迭代
 # 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高級應用

使用場景一:  

Generator可用于產(chǎn)生數(shù)據(jù)流, generator并不立刻產(chǎn)生返回值,而是等到被需要的時候才會產(chǎn)生返回值,相當于一個主動拉取的過程(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ù)只有在需要的時候去拉取的,而不是提前準備好。另外gen_words中 (w for w in line.split() if w.strip()) 也是產(chǎn)生了一個generator

使用場景二:

一些編程場景中,一件事情可能需要執(zhí)行一部分邏輯,然后等待一段時間、或者等待某個異步的結果、或者等待某個狀態(tài),然后繼續(xù)執(zhí)行另一部分邏輯。比如微服務架構中,服務A執(zhí)行了一段邏輯之后,去服務B請求一些數(shù)據(jù),然后在服務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()

注意事項:

(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 進行迭代就行了。

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

(2)generator function中使用return

在python doc中,明確提到是可以使用return的,當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

總結

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

相關文章

  • jupyter notebook oepncv 顯示一張圖像的實現(xiàn)

    jupyter notebook oepncv 顯示一張圖像的實現(xiàn)

    這篇文章主要介紹了jupyter notebook oepncv 顯示一張圖像的實現(xiàn),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-04-04
  • Python批量實現(xiàn)Word/EXCEL/PPT轉(zhuǎn)PDF

    Python批量實現(xiàn)Word/EXCEL/PPT轉(zhuǎn)PDF

    在日常辦公和文檔處理中,有時我們需要將多個Word文檔、Excel表格或PPT演示文稿轉(zhuǎn)換為PDF文件,本文將介紹如何使用Python編程語言批量實現(xiàn)將多個Word、Excel和PPT文件轉(zhuǎn)換為PDF文件,需要的可以參考下
    2023-09-09
  • pytorch使用tensorboard報錯問題及解決

    pytorch使用tensorboard報錯問題及解決

    這篇文章主要介紹了pytorch使用tensorboard報錯問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • python利用蒙版摳圖(使用PIL.Image和cv2)輸出透明背景圖

    python利用蒙版摳圖(使用PIL.Image和cv2)輸出透明背景圖

    這篇文章主要介紹了python利用蒙版摳圖(使用PIL.Image和cv2)輸出透明背景圖,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-08-08
  • 一文深入了解Python中的繼承知識點

    一文深入了解Python中的繼承知識點

    Python?是面向?qū)ο蟮木幊陶Z言,因此支持面向?qū)ο蟮娜筇匦灾唬豪^承。本文就帶大家了解了解Python中繼承的相關知識點,感興趣的可以了解一下
    2022-11-11
  • python heic后綴圖片文件轉(zhuǎn)換成jpg格式的操作

    python heic后綴圖片文件轉(zhuǎn)換成jpg格式的操作

    這篇文章主要介紹了python heic后綴圖片文件轉(zhuǎn)換成jpg格式的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • 深入了解Python?Flask框架之藍圖

    深入了解Python?Flask框架之藍圖

    這篇文章主要為大家介紹了Python?Flask框架之藍圖,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-12-12
  • Python編程使用有限狀態(tài)機識別地址有效性

    Python編程使用有限狀態(tài)機識別地址有效性

    這篇文章主要介紹了Python編程中如何使用有限狀態(tài)機識別地址有效性,如何識別一個地址是否有效,確切的講,如何編程識別一個中國地址是否有效
    2021-09-09
  • python使用KNN算法手寫體識別

    python使用KNN算法手寫體識別

    這篇文章主要為大家詳細介紹了python使用KNN算法手寫體識別,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • Python數(shù)據(jù)結構之循環(huán)鏈表詳解

    Python數(shù)據(jù)結構之循環(huán)鏈表詳解

    循環(huán)鏈表 (Circular Linked List) 是鏈式存儲結構的另一種形式,它將鏈表中最后一個結點的指針指向鏈表的頭結點,使整個鏈表頭尾相接形成一個環(huán)形,使鏈表的操作更加方便靈活。本文將詳細介紹一下循環(huán)鏈表的相關知識,需要的可以參考一下
    2022-01-01

最新評論