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

Python學(xué)習(xí)之生成器詳解

發(fā)布時間:2018-04-01 23:41  回復(fù):0  查看:2918   最后回復(fù):2018-04-01 23:41  

本文和大家分享的主要是python函數(shù)式編程中的生成器相關(guān)內(nèi)容,希望通過本文的介紹,能幫助大家更好的學(xué)習(xí)python。

   1.什么是生成器

  如果一個函數(shù)體內(nèi)部包含yield關(guān)鍵字,該函數(shù)就是生成器函數(shù),執(zhí)行該函數(shù)就得到一個生成器對象

  2.得到生成器

  先來看下面的代碼

  def foo():

  print("first...")

  yield

  print("second...")

  yield

  print("third...")

  g=foo()

  print(g)

  根據(jù)上面生成器的定義: 函數(shù)體內(nèi)部包含yield關(guān)鍵字,則該函數(shù)就是生成器函數(shù) ,則上面的函數(shù)執(zhí)行結(jié)果就是一個生成器對象

  執(zhí)行上面的代碼,查看程序執(zhí)行結(jié)果

  <generator object foo at 0x0000000001DF2BF8>

  可以看出: 上面的函數(shù)執(zhí)行的結(jié)果g就是一個生成器對象,上面的函數(shù)foo就是一個生成器函數(shù)

  3.生成器的內(nèi)置方法

  修改上面的代碼,調(diào)用dir方法查看生成器中包含的方法

  def foo():

  print("first...")

  yield

  print("second...")

  yield

  print("third...")

  g=foo()

  print(dir(g))

  打印生成器內(nèi)部的方法,可以看到打印的結(jié)果

  ['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']

  在這些結(jié)果中,可以看到有 __iter__方法  __next__方法 ,由此可以判斷出 生成器的本質(zhì)就是迭代器

  , 生成器是迭代器的一種

  4.判斷生成器是否是迭代器

  修改上面的代碼

  def foo():

  print("first...")

  yield

  print("second...")

  yield

  print("third...")

  g=foo()

  from collections import Iterable

  print(isinstance(g,Iterable))

  查看打印結(jié)果

  True

  上面的兩個例子都可以證明: 生成器的本質(zhì)是迭代器,生成器就是迭代器的一種

  5.生成器的 __iter__方法 和 __next__方法

  既然生成器的本質(zhì)是迭代器,那么調(diào)用生成器的 __iter__方法  __next__方法 ,得到的結(jié)果會是什么呢

  修改上面的代碼

  def foo():

  print("first...")

  yield

  print("second...")

  yield

  print("third...")

  g=foo()

  print(g)

  print(g.__iter__())

  g.__next__()

  程序執(zhí)行結(jié)果

  <generator object foo at 0x0000000001DF2BF8>

  <generator object foo at 0x0000000001DF2BF8>

  first...

  從上面的程序的執(zhí)行結(jié)果可以看出: 直接打印生成器g和調(diào)用生成器g.__iter__方法,得到的結(jié)果都是生成器對象在內(nèi)存中的地址

  調(diào)用 g.__next__ 方法,實(shí)際上就是從生成器g中取出一個值,執(zhí)行一次 g.__next__ 方法,觸發(fā)一次生成器的取值操作,這個過程在上面的代碼中表現(xiàn)為foo函數(shù)的向下執(zhí)行過程

  從上面的程序的執(zhí)行結(jié)果中可以看到,只執(zhí)行了foo函數(shù)的第一個print函數(shù),并沒有執(zhí)行第二個和第三個print函數(shù)。

  通常對函數(shù)來說,函數(shù)開始執(zhí)行以后直到return語句,函數(shù)才會停止執(zhí)行

  在這里執(zhí)行一次 g.__next__ 方法,foo函數(shù)中執(zhí)行了一行代碼,遇到yield就停止了,在這里yield好像起到了return的作用。

  實(shí)際上,yield關(guān)鍵字的功能之一就是起到返回的作用

  上面的程序執(zhí)行遇到yield,本次 g.__next__ 方法執(zhí)行完畢。

  在函數(shù)的執(zhí)行過程中,如果函數(shù)的return語句有返回值,則函數(shù)的執(zhí)行完成就得到return語句的返回值,

  如果return沒有定義返回值或者函數(shù)中沒有定義return語句,則函數(shù)的執(zhí)行結(jié)果默認(rèn)為None

  修改上面的代碼,打印 __next__ 方法的執(zhí)行結(jié)果

  def foo():

  print("first...")

  yield

  print("second...")

  yield

  print("third...")

  g=foo()

  print(g.__next__())

  程序執(zhí)行結(jié)果

  first...None

  可以看到,調(diào)用 __next__ 方法時,yield后沒接任何參數(shù)時, yield默認(rèn)的返回值也是None

  6.yield后面接返回值

  那如果在yield關(guān)鍵字后接一個返回值,程序執(zhí)行結(jié)果會是怎么樣的呢

  修改上面的代碼,在yield關(guān)鍵字后接一個返回值,看程序的執(zhí)行結(jié)果

  def foo():

  print("first...")

  yield 1

  print("second...")

  yield 2

  print("third...")

  g=foo()

  print(g.__next__())

  程序執(zhí)行結(jié)果

  first...1

  從上面的程序的執(zhí)行結(jié)果可以看出,yield會把其后面接的數(shù)返回,作為 __next__ 方法的執(zhí)行結(jié)果

  7.yieldreturn的不同點(diǎn)

  在函數(shù)中,不管一個函數(shù)中定義了多少個return語句,函數(shù)在執(zhí)行到第一個return語句的時候就會中止,其后面的語句將不會被繼續(xù)執(zhí)行

  而對于yield來說,每調(diào)用一次 __next__ 方法,程序會從開始向下執(zhí)行,直到遇到yield語句,程序暫停,等到第二次調(diào)用 __next__ 方法,程序會從上次暫停的地方繼續(xù)向下執(zhí)行,直到遇到下一個yield或者程序執(zhí)行完成

  在上面的例子里,是使用yield把函數(shù)foo變成一個生成器,執(zhí)行foo函數(shù)時,并不會立即執(zhí)行foo函數(shù),而是先得到生成器g,當(dāng)調(diào)用一次`g.__next__`方法時,函數(shù)foo開始向下執(zhí)行,遇到yield時,程序暫停,當(dāng)下一次調(diào)用`g.__next__`方法時,函數(shù)foo繼續(xù)從上一次暫停的地方開始向下執(zhí)行,直到遇到yield暫停

  8.生成器的StopIteration

  修改程序,多次調(diào)用 __next__ 方法,查看程序的執(zhí)行結(jié)果

  def foo():

  print("first...")

  yield

  print("second...")

  yield

  print("third...")

  g=foo()

  print(g)

  print(g.__iter__())

  g.__next__()

  print('*'*30)

  g.__next__()

  print('#'*30)

  g.__next__()

  程序執(zhí)行結(jié)果

  

  

  first...

  ******************************

  second...##############################

  third...

  Traceback (most recent call last):

  File "E:/py_code/test.py", line 28, in 

  g.__next__()

  StopIteration

  從上面程序的執(zhí)行結(jié)果可以看出,每調(diào)用一次生成器的 __next__ 方法,會得到一個返回值,就相當(dāng)于從迭代器中取一個值。

  如果程序在執(zhí)行過程中,沒有得到返回值,這就說明迭代器的最后一個值已經(jīng)被遍歷完成了,所以此時再調(diào)用 __next__ 方法,程序就會拋出異常

  9.生成器的for循環(huán)遍歷

  在前面的學(xué)習(xí)中已經(jīng)知道, 生成器本質(zhì)上就是一個迭代器 。既然是迭代器,那么當(dāng)然可以使用for循環(huán)來遍歷生成器

  修改上面的例子,使用for循環(huán)來遍歷生成器

  def foo():

  print("first...")

  yield 1

  print("second...")

  yield 2

  print("third...")

  g=foo()

  for i in g:

  print(i)

  print("*"*30)

  查看程序的執(zhí)行結(jié)果

  first...

  1******************************

  second...

  2******************************

  third...

  在上面的例子里,每執(zhí)行一次for循環(huán),就相當(dāng)于是執(zhí)行一次 g.__next__ 方法,yield會返回其后所接的數(shù)字,所以for循環(huán)前兩次的執(zhí)行結(jié)果都是print函數(shù)和yield后接的數(shù)字

  for循環(huán)執(zhí)行到第三次的時候,執(zhí)行完print函數(shù),程序會拋出 StopIteration 異常,但是 StopIteration 的異常會被for循環(huán)捕捉到,所以for循環(huán)執(zhí)行第三次只執(zhí)行了print語句

  10.總結(jié):

  yield關(guān)鍵字的功能:

  與return的功能類似,都可以返回值,但不一樣的地方在于一個函數(shù)中可以多次調(diào)用yield來返回值

  為函數(shù)封裝好了`__iter__方法``__next__方法`,把函數(shù)的執(zhí)行結(jié)果變成了迭代器`遵循迭代器的取值方式(obj.__next__())`,觸發(fā)的函數(shù)的執(zhí)行,函數(shù)暫停與再繼續(xù)都由yield保存

  11.示例:使用yield模擬linux中的命令:tail -f | grep 'error' | grep '404'

  代碼如下:

  import time

  def tail(file_path, encoding='utf-8'):

  with open(file_path, encoding=encoding) as f:

  f.seek(0, 2)

  while True:

  line = f.readline()

  if line:

  yield line

  else:

  time.sleep(0.5)

  def grep(lines, pattern):

  for line in lines:

  if pattern in line:

  yield line

  g1 = tail('a.txt')

  g2 = grep(g1, 'error')

  g3 = grep(g2, '404')

  for i in g3:

  print(i)

 

來源:博客園

您還未登錄,請先登錄

熱門帖子

最新帖子

?