python函數式編程之產生器

來源:互聯網
上載者:User

標籤: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函數式編程之產生器

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.