搜尋
首頁後端開發Python教學詳細介紹python中的yield與generator

詳細介紹python中的yield與generator

Apr 27, 2017 am 11:55 AM
pythonyield

這篇文章主要由淺入深講解了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:

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 &#39;generator&#39;>
 ret = gen_value()
 print ret, type(ret) # 1 <type &#39;int&#39;>

從上面的程式碼可以看出,gen_generator函數傳回的是一個generator實例

#generator有以下特別:

     •遵循迭代器(iterator)協議,迭代器協定需要實作__iter__ 、next介面

#     •能過多次進入、多次返回,能夠暫停函數體中程式碼的執行

下面看一下測試程式碼: 

>>> def gen_example():
... print &#39;before any yield&#39;
... yield &#39;first yield&#39;
... print &#39;between yields&#39;
... yield &#39;second yield&#39;
... print &#39;no yield anymore&#39;
... 
>>> gen = gen_example()
>>> gen.next()    # 第一次调用next
before any yield
&#39;first yield&#39;
>>> gen.next()    # 第二次调用next
between yields
&#39;second yield&#39;
>>> gen.next()    # 第三次调用next
no yield anymore
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteratio

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

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

def generator_example():
 yield 1
 yield 2

if __name__ == &#39;__main__&#39;:
 for e in generator_example():
 print e
 # 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:

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

generator應用

## generator基礎應用

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

例如對於下面的程式碼

 

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語句輸出是一樣的,程式碼字面上看來也就是中括號與小括號的區別。但這點差異差異是很大的,第一種方法回傳值是一個列表,第二個方法回傳的是一個generator物件。隨著RANGE_NUM的變大,第一種方法傳回的清單也越大,佔用的記憶體也越大;但是對於第二種方法沒有任何差異。 我們再來看一個可以「返回」無窮多次的例子:
def fib():
 a, b = 1, 1
 while True:
 yield a
 a, b = b, a+b

這個generator擁有產生無數多「回傳值」的能力,使用者可以自己決定什麼時候停止迭代

generator進階應用 ################使用場景一:  #########Generator可用於產生資料流,generator不是立刻產生回傳值,而是等到被需要的時候才會產生回傳值,相當於一個主動拉取的過程(pull),例如現在有一個日誌文件,每行產生一筆記錄,對於每一筆記錄,不同部門的人可能處理方式不同,但是我們可以提供一個公用的、按需產生的資料流。 ###
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__ == &#39;__main__&#39;:
 print count_words(&#39;test.txt&#39;), count_total_chars(&#39;test.txt&#39;)
###上面的例子來自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)的方式。下面举一个简单的例子:

 def do(a):
 print &#39;do&#39;, a
 CallBackMgr.callback(5, lambda a = a: post_do(a))
 
 def post_do(a):
 print &#39;post_do&#39;, a

这里的CallBackMgr注册了一个5s后的时间,5s之后再调用lambda函数,可见一段逻辑被分裂到两个函数,而且还需要上下文的传递(如这里的参数a)。我们用yield来修改一下这个例子,yield返回值代表等待的时间。

 @yield_dec
 def do(a):
 print &#39;do&#39;, a
 yield 5
 print &#39;post_do&#39;, a

这里需要实现一个YieldManager, 通过yield_dec这个decrator将do这个generator注册到YieldManager,并在5s后调用next方法。Yield版本实现了和回调一样的功能,但是看起来要清晰许多。

下面给出一个简单的实现以供参考:

# -*- 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 &#39;unexcepet error&#39;, 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 &#39;do&#39;, a
 yield 2.5
 print &#39;post_do&#39;, a
 yield 3
 print &#39;post_do again&#39;, a

if __name__ == &#39;__main__&#39;:
 do(1)
 for i in range(1, 10):
 print &#39;simulate a timer, %s seconds passed&#39; % 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__ == &#39;__main__&#39;:
 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执行到这里的时候抛出StopIteration异常。

def gen_with_return(range_num):
 if range_num < 0:
 return
 else:
 for i in xrange(range_num):
  yield i

if __name__ == &#39;__main__&#39;:
 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

总结

以上是詳細介紹python中的yield與generator的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
如何使用numpy創建多維數組?如何使用numpy創建多維數組?Apr 29, 2025 am 12:27 AM

使用NumPy創建多維數組可以通過以下步驟實現:1)使用numpy.array()函數創建數組,例如np.array([[1,2,3],[4,5,6]])創建2D數組;2)使用np.zeros(),np.ones(),np.random.random()等函數創建特定值填充的數組;3)理解數組的shape和size屬性,確保子數組長度一致,避免錯誤;4)使用np.reshape()函數改變數組形狀;5)注意內存使用,確保代碼清晰高效。

說明Numpy陣列中'廣播”的概念。說明Numpy陣列中'廣播”的概念。Apr 29, 2025 am 12:23 AM

播放innumpyisamethodtoperformoperationsonArraySofDifferentsHapesbyAutapityallate AligningThem.itSimplifififiesCode,增強可讀性,和Boostsperformance.Shere'shore'showitworks:1)較小的ArraySaraySaraysAraySaraySaraySaraySarePaddedDedWiteWithOnestOmatchDimentions.2)

說明如何在列表,Array.Array和用於數據存儲的Numpy數組之間進行選擇。說明如何在列表,Array.Array和用於數據存儲的Numpy數組之間進行選擇。Apr 29, 2025 am 12:20 AM

forpythondataTastorage,choselistsforflexibilityWithMixedDatatypes,array.ArrayFormeMory-effficityHomogeneousnumericalData,andnumpyArraysForAdvancedNumericalComputing.listsareversareversareversareversArversatilebutlessEbutlesseftlesseftlesseftlessforefforefforefforefforefforefforefforefforefforlargenumerdataSets; arrayoffray.array.array.array.array.array.ersersamiddreddregro

舉一個場景的示例,其中使用Python列表比使用數組更合適。舉一個場景的示例,其中使用Python列表比使用數組更合適。Apr 29, 2025 am 12:17 AM

Pythonlistsarebetterthanarraysformanagingdiversedatatypes.1)Listscanholdelementsofdifferenttypes,2)theyaredynamic,allowingeasyadditionsandremovals,3)theyofferintuitiveoperationslikeslicing,but4)theyarelessmemory-efficientandslowerforlargedatasets.

您如何在Python數組中訪問元素?您如何在Python數組中訪問元素?Apr 29, 2025 am 12:11 AM

toAccesselementsInapyThonArray,useIndIndexing:my_array [2] accessEsthethEthErlement,returning.3.pythonosezero opitedEndexing.1)usepositiveandnegativeIndexing:my_list [0] fortefirstElment,fortefirstelement,my_list,my_list [-1] fornelast.2] forselast.2)

Python中有可能理解嗎?如果是,為什麼以及如果不是為什麼?Python中有可能理解嗎?如果是,為什麼以及如果不是為什麼?Apr 28, 2025 pm 04:34 PM

文章討論了由於語法歧義而導致的Python中元組理解的不可能。建議使用tuple()與發電機表達式使用tuple()有效地創建元組。 (159個字符)

Python中的模塊和包裝是什麼?Python中的模塊和包裝是什麼?Apr 28, 2025 pm 04:33 PM

本文解釋了Python中的模塊和包裝,它們的差異和用法。模塊是單個文件,而軟件包是帶有__init__.py文件的目錄,在層次上組織相關模塊。

Python中的Docstring是什麼?Python中的Docstring是什麼?Apr 28, 2025 pm 04:30 PM

文章討論了Python中的Docstrings,其用法和收益。主要問題:Docstrings對於代碼文檔和可訪問性的重要性。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具