本文和大家分享的主要是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é)果都是生成器對象g 在內(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.yield與return的不同點(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)
來源:博客園