Python必知必會之yield關鍵字詳解與三種用法[生成器、協程、上下文管理器]

Python必知必會之yield關鍵字詳解與三種用法[生成器、協程、上下文管理器]


本篇文章比較硬核, 適合有一定Python基礎的讀者閱讀, 如果您對Python還不甚瞭解可以先關注我哦, 我會持續更新Python技術文章

yield詳解

yieldreturn相同每次調用都會返回一個值, 不同的是return返回值後會直接結束函數執行, 而yeild則是返回值後凍結函數執行, 下次調用此函數會從凍結處開始執行

(凍結就是保存函數狀態, 所有的局部變量與執行狀態都會被保留)

yield語法

send參數 yield 返回值
語法分爲三個部分, 除yield關鍵字外其它兩個部分全部可空
第一部分: yield表達式返回值 (send的參數), 從此處開始解除凍結
第二部分: yield關鍵字
第三部分: 返回值, 返回後從此處開始凍結

執行到yieldyield會把右邊的表達式當做本次函數的返回值返回給調用者, 然後函數進入凍結狀態, 再次調用被凍結函數時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')

如果您覺得此文章對您有所幫助, 請幫我點贊哦~



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章