生成器

生成器指的是生成器對象,可以有生成器表達(dá)式獲得,也可以由yield關(guān)鍵字得到一個(gè)生成器,調(diào)用這個(gè)函數(shù)得到一個(gè)生成器對象。生成器對象是一個(gè)可迭代對象,是一個(gè)迭代器。生成器對象是延遲計(jì)算、惰性求值的。

生成器函數(shù)

函數(shù)體中包含yield語句的函數(shù),就是生成器函數(shù),調(diào)用該函數(shù)返回一個(gè)生成器對象。

m = (i for i in range(5)) # 生成器表達(dá)式
next(m)
next(m)

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# 生成器函數(shù):
def inc():
    for i in range(5):
        yield i
        
print(type(inc)) # <class 'function'>
print(type(inc())) # <class 'generator'>

g = inc()
print(type(g)) # <class 'generator'>
print(next(g)) # 0
for x in g:
    print(x) # 1 2 3 4
for y in g:
    print(y) 

普通函數(shù)調(diào)用,函數(shù)會立即執(zhí)行到執(zhí)行完畢。生成器函數(shù)調(diào)用,并不會立即執(zhí)行,而是需要使用next函數(shù)來驅(qū)動其執(zhí)行生成器函數(shù)調(diào)用后獲得的生成器對象。

生成器函數(shù)相對于生成器表達(dá)式的優(yōu)勢在于:生成器的代碼可以寫的更加復(fù)雜。

生成器的執(zhí)行

def gen():
    print('line 1')
    yield 1
    print('line 2')
    yield 2
    print('line 3')
    return 3
    yield 4

next(gen()) # line 1
next(gen()) # line 1
g = gen() # line 1
print(next(g)) # 1 & line 2 
print(next(g)) # 2 & line 3
# print(next(g)) # StopIteration: 3
print(next(g, 'end')) # 生成器沒有元素了,給個(gè)默認(rèn)值
  • 在生成器函數(shù)中,可以多次yield,每執(zhí)行一次yield后會暫停執(zhí)行,把yield表達(dá)式的值返回
  • 再次執(zhí)行會執(zhí)行到下一個(gè)yield又會暫停
  • return語句依然可以終止函數(shù)運(yùn)行,但return的值不可能被捕獲到
  • return會導(dǎo)致當(dāng)前函數(shù)返回,無法繼續(xù)執(zhí)行也無法繼續(xù)獲取下一個(gè)值,拋出StopIteration異常
  • 如果函數(shù)沒有顯示的return語句,如果生成器執(zhí)行到了結(jié)尾(相當(dāng)于return None),一樣會拋出StopIteration異常

總結(jié):

  1. 包含yield語句的生成器函數(shù)調(diào)用后,生成生成器對象的時(shí)候,函數(shù)體并不會執(zhí)行(也就是,g = gen()不會使得 print('line 1') 執(zhí)行)
  2. next(generator)會從函數(shù)的當(dāng)前位置向后執(zhí)行,直到碰到y(tǒng)ield語句,會彈出值,并暫停函數(shù)運(yùn)行
  3. 再次調(diào)用next(),和上面一樣
  4. 繼續(xù)調(diào)用next,如果生成器結(jié)束執(zhí)行了(顯示/隱式調(diào)用return語句),會拋出StopIteration異常

生成器應(yīng)用

1、無限循環(huán)

def counter():
    i = 0
    while True:
        i += 1
        yield i
        
c = counter()
next(c)
next(c)
next(c)
...

2、計(jì)數(shù)器

def inc():
    def counter():
        i = 0
        while True:
            i += 1
            yield i

    c = counter()
    return next(c)

print(inc()) # 1
print(inc()) # 1
print(inc()) # 1

思考:為什么都是返回1?-- 因?yàn)楹瘮?shù)的調(diào)用都是獨(dú)立的,每次的inc()都使用一個(gè)新的counter ,所以每次都能返回1。

修改:

def inc():
    def counter():
        i = 0
        while True:
            i += 1
            yield i
            
    c= counter()
    def inner():
        return next(c)
    return inner # 可以用lambda函數(shù)代替

foo = inc()
print(foo()) # 1
print(foo()) # 2 
print(foo()) # 3

思考:為什么能夠打印1,2,3? -- 因?yàn)閕nc()這次返回的是一個(gè)函數(shù)對象而非常量,且這個(gè)函數(shù)對象是在inc內(nèi)部定義的,所以inc函數(shù)不會在棧內(nèi)釋放,即counter函數(shù),c,inner函數(shù)都在inc函數(shù)的棧幀中;調(diào)用foo,就是調(diào)用inner函數(shù),inner函數(shù)返回 next(c),c 是 inner函數(shù)外部的自由變量 (閉包),c 是唯一的且被next()多次調(diào)用,所以能返回1 2 3。

3、斐波那契數(shù)列

def fib():
    x = 0
    y = 1
    while True:
        yield y
        x, y = y, x+y
        
foo = fib()
for i in range(100):
    next(foo)

4、生成器交互

python提供了一個(gè)和生成器對象交互的方法send,該方法可以和生成器溝通。

# 帶重置功能的計(jì)數(shù)器
def inc():
    def counter():
        i = 0
        while True:
            i += 1
            response = yield i 
            if response is not None: # 重置功能
                i = response
            
    c= counter()
    return lambda x=False: next(c) if not x else c.send(0) 
    
foo = inc()
print(foo())
print(foo())
print(foo())
print(foo(True))
print(foo())
print(foo())
  • 調(diào)用生成器的send方法,可以將send的實(shí)參傳給yield做結(jié)果,這個(gè)結(jié)果可以在等式右邊被賦值給其他變量
  • send 和 next 一樣,可以推動生成器執(zhí)行

5、協(xié)程Coroutine

  • 生成器的高級語法
  • 它比線程、進(jìn)程輕量級,實(shí)在用戶空間的調(diào)度函數(shù)的一種體現(xiàn)
  • Python3 asyncio 就是協(xié)程實(shí)現(xiàn),已經(jīng)加入到標(biāo)準(zhǔn)庫
  • python 3.5 使用 async、await 關(guān)鍵字支持原生協(xié)程
  • 協(xié)程調(diào)度器實(shí)現(xiàn)思路:
    • 有兩個(gè)生成器A、B
      • next(A) 之后,A執(zhí)行到了yield語句暫停,然后去執(zhí)行next(B),B執(zhí)行到y(tǒng)ield語句也暫停,然后再次調(diào)用next(A),再調(diào)用next(B),周而復(fù)始,就實(shí)現(xiàn)了調(diào)度的效果
      • 可以引入調(diào)度的策略來實(shí)現(xiàn)切換的方式
  • 協(xié)程是一種非搶占式調(diào)度,只要沒有遇到y(tǒng)ield,就能一直執(zhí)行下去,不會被搶占運(yùn)行的權(quán)力

yield from 語法

從python3.3開始增加了yield語法,使得 yield from iterable 等價(jià)于 for item in iterable: yield item

yield from 就是一種語法糖。本質(zhì)上就是在 iterable 中拿元素一個(gè)個(gè) yield 出去。

def inc():
    yield from range(1000)
    
foo = inc()
print(next(foo))
print(next(foo))
print(next(foo))
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容