048 Python里面yield的實現原理


python 生成器yield的總結

yeild的原理:一個帶有yield的函數就是一個generator生成器(python 的 generator 只保留棧幀上下文,不保留調用棧,而且 generator 函數不允許 return;),和普通的函數不一樣。只有調用next()函數的時候才會執行函數語句,在for循環中會自動調用next()方法。函數執行過程中遇到一個yield會中斷一次,返回一個迭代值,函數保存自己的變量和狀態,下次迭代時(也就是下次next()的時候)從yield下一條語句繼續執行(這個性質很重要),函數恢復之前狀態,直到遇到下一個yield返回迭代值,這樣循環。
(1):通常的for..in...循環中,in后面是一個數組,這個數組就是一個可迭代對象,類似的還有鏈表,字符串,文件。他可以是a = [1,2,3],也可以是a = [x*x for x in range(3)]。
它的缺點也很明顯,就是所有數據都在內存里面,如果有海量的數據,將會非常耗內存。
(2)生成器是可以迭代的,但是只可以讀取它一次。因為用的時候才生成,比如a = (x*x for x in range(3))。!!!!注意這里是小括號而不是方括號。
(3)生成器(generator)能夠迭代的關鍵是他有next()方法,工作原理就是通過重復調用next()方法,直到捕獲一個異常。
(4)帶有yield的函數不再是一個普通的函數,而是一個生成器generator,可用於迭代
(5)yield是一個類似return 的關鍵字,迭代一次遇到yield的時候就返回yield后面或者右面的值。而且下一次迭代的時候,從上一次迭代遇到的yield后面的代碼開始執行
(6)yield就是return返回的一個值,並且記住這個返回的位置。下一次迭代就從這個位置開始。
(7)帶有yield的函數不僅僅是只用於for循環,而且可用於某個函數的參數,只要這個函數的參數也允許迭代參數。
(8)send()和next()的區別就在於send可傳遞參數給yield表達式,這時候傳遞的參數就會作為yield表達式的值,而yield的參數是返回給調用者的值,也就是說send可以強行修改上一個yield表達式值。
(9)send()和next()都有返回值,他們的返回值是當前迭代遇到的yield的時候,yield后面表達式的值,其實就是當前迭代yield后面的參數。
(10)第一次調用時候必須先next()或send(),否則會報錯,send后之所以為None是因為這時候沒有上一個yield,所以也可以認為next()等同於send(None)

深入理解 Python yield

https://blog.csdn.net/lftaoyuan/article/details/78915518
python2和python3是不兼容的,通篇環境都是python3.6

簡單的yield實例

以前只是粗略的知道yield可以用來為一個函數返回值塞數據,比如下面的例子:

def addlist(alist):
    for i in alist:
        yield i + 1

取出alist的每一項,然后把i + 1塞進去。然后通過調用取出每一項:

alist = [1, 2, 3, 4]
for x in addlist(alist):
    print(x)

這的確是yield應用的一個例子,但是,看過很多東西,並自己反復體驗后,對yield有了一個全新的理解,其中這篇算是精品了。

包含yield的函數

假如你看到某個函數包含了yield,這意味着這個函數已經是一個Generator,它的執行會和其他普通的函數有很多不同。比如下面的簡單的函數:

def h():
    print('study yield')
    yield 5
    print('go on!')

h()

可以看到,調用h()之后,print 語句並沒有執行!這就是yield。具體的內容后面會越來越清晰,包括yield的工作原理。

yield是一個表達式

python 2.5以前,yield是一個語句,我也沒有考證,因為早都不用了,現在yield是一個表達式:

m = yield 5

表達式(yield 5)的返回值將賦值給m,所以,m = 5 肯定是錯的。
那么如何獲取(yield 5)的返回值呢?需要用到send(msg)

yield工作原理

揭曉yield的工作原理,需要配合next()函數。上面的h()被調用后並沒有執行,因為它有yield表達式,通過next()可以恢復Generator執行,直到下一個yield

def h():
    print('study yield')
    yield 5
    print('go on!')
c = h()
d1 = next(c)  # study yield
d2 = next(c)
"""
study yield
go on!
Traceback (most recent call last):
  File "D:/idea/workspace/pythonSpace/PythonDemo/static/yield_demo.py", line 35, in <module>
    d2 = next(c)
StopIteration
"""
  • 1
  • 2
  • 3
  • 4
next()`被調用后,`h()`開始執行,直到遇到`yield 5

因此輸出結果是:study yield
當我們再次調用next()時,會繼續執行,直到找到下一個yield。由於后面沒有yield了,因此會拋出異常:

study yield
go on!
Traceback (most recent call last):
  File "D:/idea/workspace/pythonSpace/PythonDemo/static/yield_demo.py", line 35, in <module>
    d2 = next(c)
StopIteration

send(msg) 與 next()

了解了next()如何讓包含yield的函數執行后,我們再來看另外一個非常重要的函數send(msg)
其實next()send()在一定意義上作用是相似的
區別
send()可以傳遞yield的值
next()只能傳遞None
所以next()send(None)作用是一樣的。

def s():
    print('study yield')
    m = yield 5
    print(m)
    d = yield 16
    print('go on!')
c = s()
s_d = next(c)  # 相當於send(None)
c.send('Fighting!')  # (yield 5)表達式被賦予了'Fighting!'

輸出的結果為:

study yield
Fighting!

注意 生成器剛啟動時(第一次調用),請使用next()語句或是send(None),不能直接發送一個非None的值,否則會報TypeError,因為沒有yield語句來接收這個值。

send(msg) 與 next()的返回值

send(msg)next() 的返回值比較特殊,是下一個yield表達式的參數(yield 5,則返回 5)。
到這里,第一個例子中,通過for i in alist 遍歷 Generator,其實是每次都調用了next(),而每次next()的返回值正是yield的參數:

def s():
    print('study yield')
    m = yield 5
    print(m)
    d = yield 16
    print('go on!')
c = s()
s_d1 = next(c)  # 相當於send(None)
s_d2 = c.send('Fighting!')  # (yield 5)表達式被賦予了'Fighting!'
print('My Birth Day:', s_d1, '.', s_d2)

輸出結果:

study yield
Fighting!
My Birth Day: 5 . 16

中斷Generator

上面的例子中,當沒有可執行程序的時候,會拋出一個StopIteration, 開發過程中,中斷Generator是一個非常靈活的技巧

throw

通過拋出一個GeneratorExit異常來終止Generator。

close

close的作用和throw是一樣的,看它的源碼,可以發現,它和raise一球樣

def throw(self, type, value=None, traceback=None):
    '''Used to raise an exception inside the generator.'''
    # 用於在生成器中拋出一個異常。
    pass
def close(self):
    '''Raises new GeneratorExit exception inside the generator to terminate the iteration.'''
    # 在生成器中生成新的GeneratorExit異常來終止迭代。
    pass

其實最后一個中斷生成器可以忽略的,在開發過程中,不可避免的要用到這些,但是Python3內部已經做得很好了,一般不太需要手動去做這件事情。

demo地址

https://github.com/seeways/PythonDemo/blob/master/static/yield_demo.py


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM