首頁 > 軟體

詳解Python中的上下文管理器原理

2023-03-13 06:00:31

with語句

在我們日常使用場景中,經常會操作一些資源,比如檔案物件、資料庫連線、Socket連線等,資源操作完了之後,不管操作的成功與否,最重要的事情就是關閉該資源,否則資源開啟太多而沒有關閉,程式會報錯,以檔案操作為例,通常我們會這樣寫:

f = open('file.txt', 'w')
try:
    f.write("Hello")
finally:
    f.close()

但既然close方法是必須的操作,那就沒必要顯式地呼叫,所以Python給我們提供了一種更優雅的方式,使用with語句:

with open('file.txt', 'w') as f:
    f.write("Hello")

在退出with語句下的程式碼塊之後,f 物件會自動執行自己的close方法,實現資源的釋放,簡潔優雅。

上下文管理器原理

上下文管理器實際是內部實現了__enter__和__exit__方法的物件。

當我們使用with語法時:

__enter__()方法:返回一個值,可以將它賦值給as後面的物件,例如上面的中的f;

__exit__()方法:with語句退出或者傳送異常時會執行這個方法。

1、__enter__方法說明

上下文管理器的__enter__方法是可以帶返回值的,預設返回None,這個返回值通過with…as…中的 as 賦給它後面的那個變數,所以 with EXPR as VAR 就是將EXPR物件__enter__方法的返回值賦給 VAR。

當然with...as...並非固定組合,單獨使用with...也是可以的,上下文管理器的__enter__方法還是正常執行,只是這個返回值並沒有賦給一個變數,with下面的程式碼塊也不能使用這個返回值。

2、__exit__方法說明

上下文管理器的__exit__方法接收3個引數exc_type、exc_val、exc_tb,如果程式碼塊BLOCK發生了異常e並退出,這3個引數分別為type(e)、str(e)、e.__traceback__,否則都為None。

同樣__exit__方法也是可以帶返回值的,這個返回值應該是一個布林型別True或False,預設為None(即False)。如果為False,異常會被丟擲,使用者需要進行例外處理。如果為True,則表示忽略該異常。

一個上下文管理器一般使用如下:

with EXPR as VAR:
    BLOCK

上述程式碼的執行過程等價於:

ContextManager = EXPR
VAR = ContextManager.__enter__()
try:
    BLOCK
finally:
    ContextManager.__exit__()

f 物件就是把自己的close方法定義在了它的__exit__方法內部,實現了程式碼塊BLOCK執行完之後自動關閉自身。

自定義上下文管理器

下面我們定義一個檔案類,內部實現了__enter__和__exit__兩個方法:

class File:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        print("進入")
        self.f = open(self.filename, self.mode)
        return self.f

    def __exit__(self, exc_type=None, exc_val=None, exc_tbs=None):
        print("退出")
        self.f.close()

這時候File類就是一個上下文管理器

我們分別通過 with語句 和 try/finally語句 使用File類對檔案進行寫入操作

通過with語句執行:

with File('file.txt', 'w') as f:
    print("正在寫入...")
    f.write('Hello')

控制檯輸出:

進入
正在寫入...
退出

並得到了一個寫了 Hello 的 file.txt 檔案

通過try/finally語句執行:

ContextManager = File('file.txt', 'w')
VAR = ContextManager .__enter__()
try:
    print("正在寫入...")
    VAR.write('Hello')
finally:
    ContextManager.__exit__()

控制檯輸出:

進入
正在寫入...
退出

並得到了一個寫了 Hello 的 file.txt 檔案

兩者輸出一致,所以驗證了二中執行過程的等價關係是正確的。

contextmanager 裝飾器

Python還提供了一個contextmanager裝飾器,允許使用者將一個生成器定義為上下文管理器,該裝飾器將生成器中的程式碼通過yield語句分成兩部分,yield之前的程式碼為__enter__方法,yield之後的程式碼為__exit__方法,yield的返回值即__enter__方法的返回值,用於賦給as後的變數。

下面我們通過contextmanager裝飾器也實現一個關於檔案的上下文管理器:

from contextlib import contextmanager

@contextmanager
def open_file(filename, mode):
    print('進入')
    f = open(filename, mode)
    try:
        yield f
    finally:
        print('退出')
        f.close()

說明:這裡使用 try/finally 是確保yield的過程中就算出現異常,檔案也能正常關閉,當然這裡也能處理異常,使用 try/except/finally 即可。

通過with語句執行:

with open_file('file.txt', 'w') as f:
    print("正在寫入...")
    f.write('Hello')

執行結果跟之前的上下文管理器執行結果一致,說明contextmanager裝飾器也能定義一個上下文管理器。

到此這篇關於詳解Python中的上下文管理器原理的文章就介紹到這了,更多相關Python上下文管理器內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com