Python必知必會之yield關鍵字詳解與三種用法[生成器、協程、上下文管理器]
本篇文章比較硬核, 適合有一定Python基礎的讀者閱讀, 如果您對Python還不甚瞭解可以先關注我哦, 我會持續更新Python技術文章
yield詳解
yield與return相同每次調用都會返回一個值, 不同的是return返回值後會直接結束函數執行, 而yeild則是返回值後凍結函數執行, 下次調用此函數會從凍結處開始執行
(凍結就是保存函數狀態, 所有的局部變量與執行狀態都會被保留)
yield語法
send參數 yield 返回值
語法分爲三個部分, 除yield關鍵字外其它兩個部分全部可空
第一部分: yield表達式返回值 (send的參數), 從此處開始解除凍結
第二部分: yield關鍵字
第三部分: 返回值, 返回後從此處開始凍結
執行到yield時yield會把右邊的表達式當做本次函數的返回值返回給調用者, 然後函數進入凍結狀態, 再次調用被凍結函數時yield解除凍結, 如果本次調用使用的是send方法則yield會把調用send函數時傳遞的參數當做yield表達式的返回值
調用yield函數的四種方式 (執行迭代的四種方式)
首先先來了解幾個概念
可迭代對象: 實現了 __iter__ 方法的對象就是可迭代對象 (生成迭代器的對象)
迭代器: __iter__ 方法返回的, 同時實現了 __iter__ 方法與 __next__ 方法的對象就是迭代器 (雖然只實現了 __next__ 方法就可以迭代, 但是它不是迭代器也不是可迭代對象也不是生成器…整個一三無產品, 所以最好不要使用這種語法)
生成器: 函數內使用了yield關鍵字, 調用這個函數返回的對象就是生成器, 生成器是一種特殊的迭代器 (元組推導式也可以創建生成器)
迭代器耗盡: 迭代器主動拋出了StopIteration異常, 就表示迭代器耗盡, Python會自動處理這個異常
(對可迭代對象調用iter內置函數將會創建一個迭代器)
(對迭代器執行next內置函數或調用send方法將執行一次迭代)
(生成器內置實現了 __iter__ 方法與 __next__ 方法)
1. next(生成器對象) – 內置函數, 執行一次迭代 (恢復凍結的生成器對象)
2. 生成器對象.send(yield表達式返回值) – 生成器方法, 傳遞參數並執行一次迭代 (恢復凍結的生成器對象) (必須先調用一次next全局函數後才能調用)
3. for循環 – 自動調用next全局函數進行迭代, 直到迭代器耗盡
4. list、tuple、str、set等序列對象會自動調用next全局函數進行迭代, 直到迭代器耗盡
1. 生成器
什麼是生成器?
生成器是一種特殊的迭代器, 可以理解爲生成元素的算法, 每次調用都會生成一個元素, 特點是不會佔用很大的內存空間, 而是在需要時動態生成
使用yield實現range函數功能
# 使用yield實現range函數功能
def my_range(start, end=None, step=1):
if end is None:
start, end = 0, start
# 生成元素的算法
while start < end:
yield start
start += step
# 函數結束自動拋出StopIteration異常, 表示迭代器耗盡
# 使用list自動迭代
print(list(my_range(10)))
# 使用for循環迭代
for i in my_range(10):
print(i)
# 創建生成器對象
gen = my_range(10)
print(next(gen)) # 使用next內置函數執行一次迭代
print(gen.send(None)) # 使用send方法執行一次迭代
使用send方法改變生成器行爲
# 定義一個無限生成器
def infinite():
i = 0
while True:
set_i = yield i # 使用set_i變量接收send傳遞的參數
if set_i is not None: # 沒有send參數則yield表達式返回None
i = set_i
else:
i += 1
# 函數結束自動拋出StopIteration異常, 表示迭代器耗盡
# 創建生成器對象
gen = infinite()
print(next(gen)) # 執行一次迭代 打印0
print(next(gen)) # 執行一次迭代 打印1
print(gen.send(10)) # 傳遞參數並執行一次迭代 打印10
print(next(gen)) # 執行一次迭代 打印11
2. 協程
什麼是協程?
協程就是利用yield凍結特性來使多個函數交替執行, 使之呈現出併發執行效果 (多個函數同時執行)
(協程可以看做輕量級的線程, 優點是沒有多線程的開銷, 適用於IO密集型程序)
(這是最原始的協程實現方式, 實際開發中一般不會使用這種方式)
協程實現
# 函數1
def func_1():
while True:
print('--func1--') # 執行一部分代碼
yield # 凍結, 等待下一次迭代喚醒
# 函數2
def func_2():
while True:
print('--func2--') # 執行一部分代碼
yield # 凍結, 等待下一次迭代喚醒
# 創建生成器對象
func1 = func_1()
# 創建生成器對象
func2 = func_2()
# 循環執行
while True:
next(func1) # 函數1解除凍結, 執行一段代碼凍結後返回
next(func2) # 函數2解除凍結, 執行一段代碼凍結後返回
# 兩個生成器交替凍結-解除凍結就完成了併發
# 效果就是單線程程序可以呈現出多線程效果
3. 上下文管理器
什麼是上下文管理器?
上下文管理器保證了代碼無論是否出現異常, 資源都會被正確回收
上文: 資源創建
下文: 資源釋放 (無論是否出現異常都會被執行)
管理器: 上下文調度
上下文管理器可以通過實現 __enter__ 方法與 __exit__ 方法實現, 也可以使用函數+裝飾器+yield實現, 本篇文章講的就是函數+裝飾器+yield的實現方式
上文由with語句開始時自動執行, 返回結果使用as關鍵字接收
下文由with語句結束時自動執行 (無論是否出現異常都會被執行)
函數+裝飾器+yield 實現上下文管理器
# 導入上下文管理器裝飾器
from contextlib import contextmanager
# 實現上下文管理器的文件對象
@contextmanager # 必須要使用contextmanager裝飾器進行裝飾
def m_file(filename, mode='r'):
# 上文 -- 創建文件對象打開文件 -- with開始時自動執行
f = open(filename, mode)
yield f # 返回上文對象 -- 對應__enter__方法 -- 返回值使用as關鍵字接收
# 下文 -- 關閉文件對象 -- with語句結束時自動執行
f.close() # 對應__exit__方法
# 使用with打開上下文管理器
with m_file('test.txt', 'w') as f:
f.write('test')
如果您覺得此文章對您有所幫助, 請幫我點贊哦~