生成器
直接總結
- 創建生成器的方法
- 生成器表達式:
(i for i in [1, 2])
- yield: 函數中出現yield這個函數就是生成器,函數(生成器)執行到yield時會返回yield后面的值,并暫停,知道下次被喚醒后會從暫停處接著執行
- 生成器表達式:
- 迭代生成器: 生成器實現了python迭代協議,可以使用
next()
或for
迭代 - 向生成器發送消息:如果yield出現在表達式右邊,下一次生成器被send()喚醒時,上一個yield會接收send()發過來的消息賦值給上一個yield左邊的變量。
- 喚醒生成器:
- next():喚醒生成器,讓他運行到下一個yield處,返回yield出來的值
- send():喚醒生成器,并給生成器發送一個消息,同時讓他運行到下一個yield處,返回yield出來的值,不能直接一開始就發送具體的數據
- 停止生成器:close()
- 發送異常:throw(type, val)
- yield from
創建與使用生成器
直接使用生成器表達式就可以快速創建一個生成器
gen = (i for i in range(10))
print(type(gen))
for i in gen:print(i, end=" ")# <class 'generator'>
# 0 1 2 3 4 5 6 7 8 9
生成器實現了__next__
可以使用next()
來獲取下一個值,當然也可以使用for
循環遍歷
更加常見的創建生成器的方式是使用yield
關鍵字,一個函數如果出現yield
關鍵字這個函數就會變成生成器,當函數運行到yield
時會暫停下來,”返回“一個結果,下一次喚醒生成器時,函數會從停下來的地方繼續運行
def builder_demo():yield 0yield 1return 3if __name__ == '__main__':bd = builder_demo()print(type(bd))print(next(bd))print(next(bd))print(next(bd))print(next(bd))
<class 'generator'>
0
1
Traceback (most recent call last):File "E:/python/coroutine_test.py", line 12, in <module>print(next(bd))
StopIteration: 3
當沒有下一個元素時調用next會拋出StopIteration
異常,return的值會作為異常的值
if __name__ == '__main__':bd = builder_demo()while True:try:print(next(bd))except StopIteration as e:print(f'result is {e.value}')break
0
1
result is 3
yield不但可以“傳遞出值”,也可以接收值
def builder_demo():news = yield 0print(f'news: {news}')news1 = yield 1print(f'new1: {news1}')return 3if __name__ == '__main__':bd = builder_demo()print(next(bd))result1 = bd.send("hello")print(result1)result2 = bd.send("hello2")print(result2)
0
news: hello
1
new1: hello2
Traceback (most recent call last):File "E:python/coroutine_test.py", line 14, in <module>result2 = bd.send("hello2")
StopIteration: 3
往暫停處傳遞消息使用生成器的send()
方法,這個方法還可以自動迭代到生成器中的下一個對象(有next())的作用。
生成器是先yield出數據,等到下一次生成器被喚醒時,才會接收send()
的數據,然后再yield出下一個數據,所以不能一開始就直接調用send()
發送具體的值,會拋出TypeError
TypeError: can't send non-None value to a just-started generator
應該先執行一次next()
或執行一次generator.send(None)
,讓生成器yield出數據,send(None)
的作用與next()
基本一樣
生成器也可以停止,使用close()
方法
def builder_demo():news = yield 0print(f'news: {news}')news1 = yield 1print(f'new1: {news1}')yield 4yield 5return 3if __name__ == '__main__':bd = builder_demo()# print(next(bd))print(bd.send(None))result1 = bd.send("hello")print(result1)result2 = bd.send("hello2")print(result2)bd.close()print(next(bd))
0
news: hello
1
new1: hello2
4
Traceback (most recent call last):File "E:python/coroutine_test.py", line 20, in <module>print(next(bd))
StopIteration
在close()
之后再使用next()
,會拋出StopIteration異常
除此之外,還可以向生成器發送異常
if __name__ == '__main__':bd = builder_demo()print(bd.send(None))bd.throw(Exception, TypeError("throw new error"))print(next(bd))
0
Traceback (most recent call last):File "E:python/coroutine_test.py", line 19, in <module>bd.throw(Exception, TypeError("throw new error"))File "E:python/coroutine_test.py", line 2, in builder_demonews = yield 0
TypeError: throw new error
yield from
yield from 是python3.3 PEP380 新添加的特性,它允許將一個生成器的部分操作委派給另一個生成器,除了向子生成器委派任務,yield from也可以直接作用于迭代器,將迭代器中的每個對象逐一yield出來,如:
def demo(*args, **kwargs):for i in args:for j in i:yield j
# 等價于
def demo(*args, **kwargs):for i in args:yield from i
上面的函數其實就是itertools.chain() 作用是將多個迭代器中的元素迭代出來
生成器嵌套
1、
調用方
:調用委派生成器的客戶端(調用方)代碼
2、委托生成器
:包含yield from表達式的生成器函數
3、子生成器
:yield from后面加的生成器函數
yield from 可以架設一個調用方
到子生成器
之間的雙向橋梁
final_result = {}def calculate():total = 0nums = []while True:info = yieldif not info:breaktotal += infonums.append(info)# return 的值會被賦值給yield from 左邊的變量return total, numsdef middle(key: str, gen):while True:final_result[key] = yield from gen()print(final_result)def main():data = {"apple": [230, 569, 234, 776],"banana": [564, 213, 798, 327],"strawberry": [98, 76, 120, 436, 55],"orange": [78, 67, 345, 124]}for key, value in data.items():# 不要傳遞calculate()!mid = middle(key, calculate)mid.send(None) # 初激for v in value:mid.send(v)mid.send(None)if __name__ == '__main__':main()
為什么“多此一舉”架設一個“橋梁”?
- yield from 在內部處理了大量可能的異常,簡化開發,提高代碼安全性和可讀性
生成器的作用
- 適用于對大量數據的處理:如果要對產生的大量數據進一步處理時,使用容器就只能先得到所有要處理的數據,存到容器,再逐個對容器中的數據遍歷,再加工,最后得到目標數據,這樣第一步產生的“中間數據”只用一次,但仍需大量空間儲存;使用生成器可以產生一個,加工一個,節約內存,提高效率
- 用于協程