標籤:send erro doc 迭代 缺點 根據 hash python函數 pattern
在前面的學習過程中,我們知道,迭代器有兩個好處:
一是不依賴索引的統一的迭代方法二是惰性計算,節省記憶體
但是迭代器也有自己的顯著的缺點,那就是
不如按照索引取值方便 一次性,只能向後取值,不能向前取值
所以我們還需要學習另外一種對象,那已經產生器
1.什麼是產生器
如果一個函數體內部包含yield關鍵字,該函數就是產生器函數,執行該函數就得到一個產生器對象
2.得到產生器
先來看下面的代碼
def foo(): print("first...") yield print("second...") yield print("third...")g=foo()print(g)
根據上面產生器的定義:函數體內部包含yield關鍵字,則該函數就是產生器函數,則上面的函數執行結果就是一個產生器對象
執行上面的代碼,查看程式執行結果
<generator object foo at 0x0000000001DF2BF8>
可以看出:上面的函數執行的結果g就是一個產生器對象,上面的函數foo就是一個產生器函數
3.產生器的內建方法
修改上面的代碼,調用dir方法查看產生器中包含的方法
def foo(): print("first...") yield print("second...") yield print("third...")g=foo()print(dir(g))
列印產生器內部的方法,可以看到列印的結果
[‘__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‘]
在這些結果中,可以看到有__iter__方法和__next__方法,由此可以判斷出產生器的本質就是迭代器
,產生器是迭代器的一種
4.判斷產生器是否是迭代器
修改上面的代碼
def foo(): print("first...") yield print("second...") yield print("third...")g=foo()from collections import Iterableprint(isinstance(g,Iterable))
查看列印結果
True
上面的兩個例子都可以證明:產生器的本質是迭代器,產生器就是迭代器的一種
5.產生器的
__iter__方法和
__next__方法
既然產生器的本質是迭代器,那麼調用產生器的 __iter__方法和__next__方法,得到的結果會是什麼呢
修改上面的代碼
def foo(): print("first...") yield print("second...") yield print("third...")g=foo()print(g)print(g.__iter__())g.__next__()
程式執行結果
<generator object foo at 0x0000000001DF2BF8><generator object foo at 0x0000000001DF2BF8>first...
從上面的程式的執行結果可以看出:直接列印產生器g和調用產生器g.__iter__方法,得到的結果都是產生器對象g 在記憶體中的地址
調用g.__next__方法,實際上就是從產生器g中取出一個值,執行一次g.__next__方法,觸發一次產生器的取值操作,這個過程在上面的代碼中表現為foo函數的向下執行過程
從上面的程式的執行結果中可以看到,只執行了foo函數的第一個print函數,並沒有執行第二個和第三個print函數。
通常對函數來說,函數開始執行以後直到return語句,函數才會停止執行
在這裡執行一次g.__next__方法,foo函數中執行了一行代碼,遇到yield就停止了,在這裡yield好像起到了return的作用。
實際上,yield關鍵字的功能之一就是起到返回的作用
上面的程式執行遇到yield,本次g.__next__方法執行完畢。
在函數的執行過程中,如果函數的return語句有傳回值,則函數的執行完成就得到return語句的傳回值,如果return沒有定義傳回值或者函數中沒有定義return語句,則函數的執行結果預設為None
修改上面的代碼,列印__next__方法的執行結果
def foo(): print("first...") yield print("second...") yield print("third...")g=foo()print(g.__next__())
程式執行結果
first...None
可以看到,調用__next__方法時,yield後沒接任何參數時,yield預設的傳回值也是None
6.yield後面接傳回值
那如果在yield關鍵字後接一個傳回值,程式執行結果會是怎麼樣的呢
修改上面的代碼,在yield關鍵字後接一個傳回值,看程式的執行結果
def foo(): print("first...") yield 1 print("second...") yield 2 print("third...")g=foo()print(g.__next__())
程式執行結果
first...1
從上面的程式的執行結果可以看出,yield會把其後面接的數返回,作為__next__方法的執行結果
7.yield與return的不同點
在函數中,不管一個函數中定義了多少個return語句,函數在執行到第一個return語句的時候就會中止,其後面的語句將不會被繼續執行
而對於yield來說,每調用一次__next__方法,程式會從開始向下執行,直到遇到yield語句,程式暫停,等到第二次調用__next__方法,程式會從上次暫停地方繼續向下執行,直到遇到下一個yield或者程式執行完成
在上面的例子裡,是使用yield把函數foo變成一個產生器,執行foo函數時,並不會立即執行foo函數,而是先得到產生器g,當調用一次`g.__next__`方法時,函數foo開始向下執行,遇到yield時,程式暫停,當下一次調用`g.__next__`方法時,函數foo繼續從上一次暫停地方開始向下執行,直到遇到yield暫停
8.產生器的StopIteration
修改程式,多次調用__next__方法,查看程式的執行結果
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__()
程式執行結果
<generator object foo at 0x0000000001DF2BF8><generator object foo at 0x0000000001DF2BF8>first...******************************second...##############################third...Traceback (most recent call last): File "E:/py_code/test.py", line 28, in <module> g.__next__()StopIteration
從上面程式的執行結果可以看出,每調用一次產生器的__next__方法,會得到一個傳回值,就相當於從迭代器中取一個值。
如果程式在執行過程中,沒有得到傳回值,這就說明迭代器的最後一個值已經被遍曆完成了,所以此時再調用__next__方法,程式就會拋出異常
9.產生器的for迴圈遍曆
在前面的學習中已經知道,產生器本質上就是一個迭代器。既然是迭代器,那麼當然可以使用for迴圈來遍曆產生器
修改上面的例子,使用for迴圈來遍曆產生器
def foo(): print("first...") yield 1 print("second...") yield 2 print("third...")g=foo()for i in g: print(i) print("*"*30)
查看程式的執行結果
first...1******************************second...2******************************third...
在上面的例子裡,每執行一次for迴圈,就相當於是執行一次g.__next__方法,yield會返回其後所接的數字,所以for迴圈前兩次的執行結果都是print函數和yield後接的數字
for迴圈執行到第三次的時候,執行完print函數,程式會拋出StopIteration異常,但是StopIteration的異常會被for迴圈捕捉到,所以for迴圈執行第三次只執行了print語句
10.總結:
yield關鍵字的功能:
與return的功能類似,都可以傳回值,但不一樣的地方在於一個函數中可以多次調用yield來傳回值為函數封裝好了`__iter__方法`和`__next__方法`,把函數的執行結果變成了迭代器`遵循迭代器的取值方式(obj.__next__())`,觸發的函數的執行,函數暫停與再繼續都由yield儲存
11.樣本:使用yield類比linux中的命令:tail -f | grep ‘error‘ | grep ‘404‘
代碼如下:
import timedef 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 lineg1 = tail(‘a.txt‘)g2 = grep(g1, ‘error‘)g3 = grep(g2, ‘404‘)for i in g3: print(i)
python函數式編程之產生器