什麼是上下文管理器
官方解釋...
- 上下文管理器是一個物件
- 它定義了在執行 with 語句時要建立的執行時上下文
- 上下文管理器處理進入和退出所需的執行時上下文以執行程式碼塊
- 上下文管理器通常使用 with 語句呼叫,但也可以通過直接呼叫它們的例項方法來使用
一頓花裡胡哨猛如虎,結果我也不太懂
簡單一句話
同時包含 __enter__() 和 __exit__() 方法的物件就是上下文管理器
__enter__(self)
- 進入上下文管理器自動呼叫的方法
- 該方法會在 with ... as ... 程式碼塊執行之前執行
- 如果 with 語句有 as 子句,且該方法有返回值,那麼該方法的返回值會被賦值給 as 子句後的變數,最常見的 with open('file_path', 'w') as file:
- 該方法可以返回多個值,因此在 as 子句後面也可以指定多個變數(多個變數必須由“()”括起來組成元組)
__exit__(self, exc_type, exc_value, exc_traceback)
- 退出上下文管理器自動呼叫的方法,會返回一個布林型別的值
- 該方法會在 with ... as ... 程式碼塊執行之後執行
- 如果 with ... as ... 程式碼塊成功執行結束,程式自動呼叫該方法,且三個引數都為 None
- 如果 with ... as ... 程式碼塊執行時發生異常,通過 sys.exc_info() 得到異常資訊,三個引數值分別是:異常型別、異常資訊、異常回溯資訊型別
有哪些常見上下文管理器?
開啟檔案
with open('file_path', 'w') as file: file.write('hello world !')
拆分了解
- 上下文表示式: with open('file_path', 'w') as file:
- 上下文管理器: open('file_path', 'w')
- file:可以理解為資源物件
執行順序
- 先執行 open() 的 __enter__() 方法,將返回值賦值給 file
- 執行 file.write('hello world !')
- 最後執行 open() 的 __exit__() 方法
自定義上下文管理器
其實有兩種方式
- 基於類實現
- 基於生成器實現
基於類實現上下文管理器
只需要給物件新增一個 __enter__ 和一個 __exit__ 方法
import sys class Resource: def __init__(self, name): self.name = name print("== 初始化方法 ==") def __enter__(self): print(f"** 進入上下文管理器自動呼叫:name is {self.name}") # 可以返回任意型別的值 return {"name": self.name} def __exit__(self, exc_type, exc_val, exc_tb): print(f"## 退出上下文管理器自動呼叫:", sys.exc_info(), exc_type, exc_val, exc_tb) if exc_tb is None: print("沒有異常時關閉資源") else: print("遇到異常時關閉資源")
通過 with 來呼叫該上下文管理器
也稱為:使用 with ... as ... 管理資源
with Resource("小菠蘿") as r: print(r)
console 輸出結果
== 初始化方法 == ** 進入上下文管理器自動呼叫:name is 小菠蘿 {'name': '小菠蘿'} ## 退出上下文管理器自動呼叫: (None, None, None) None None None 沒有異常時關閉資源
__exit__() 方法的三個引數值都是 None
with 程式碼塊丟擲異常
with Resource("異常小菠蘿") as r: print('[with程式碼塊] 異常之前的程式碼') raise Exception("丟擲了 Exception") print('[with程式碼塊] ~~~~~~~~異常之後的程式碼')
console 輸出結果
== 初始化方法 == ** 進入上下文管理器自動呼叫:name is 異常小菠蘿 [with程式碼塊] 異常之前的程式碼 ## 退出上下文管理器自動呼叫: (<class 'Exception'>, Exception('丟擲了 Exception'), <traceback object at 0x10e203200>) <class 'Exception'> 丟擲了 Exception <traceback object at 0x10e203200> 遇到異常時關閉資源 Traceback (most recent call last): File "/Users/polo/Documents/pylearn/第七章:檔案相關/1_上下文管理器.py", line 36, in <module> raise Exception("丟擲了 Exception") Exception: 丟擲了 Exception
程式碼塊丟擲異常的時候,可以看到 __exit__() 方法的三個引數值的確來源於 sys.exc_info()
總結
- 無論 with 程式碼塊是否有異常,最終都會自動呼叫 __exit__() 方法
- 當丟擲異常時,__exit__() 預設返回 None,會重新丟擲異常到外面,讓 with ... as ... 以外的程式碼來處理異常
- 反之,如果返回 True,就會忽略異常,不再對異常進行處理
__exit__() 返回 True
def __exit__(self, exc_type, exc_val, exc_tb): print(f"## 退出上下文管理器自動呼叫:", sys.exc_info(), exc_type, exc_val, exc_tb) if exc_tb is None: print("沒有異常時關閉資源") else: print("遇到異常時關閉資源") return True # 再次執行 with Resource("異常小菠蘿") as r: print('[with程式碼塊] 丟擲異常之前的程式碼') raise Exception print('[with程式碼塊] 丟擲異常之後的程式碼')
console 輸出結果
== 初始化方法 == ** 進入上下文管理器自動呼叫:name is 異常小菠蘿 [with程式碼塊] 異常之前的程式碼 ## 退出上下文管理器自動呼叫: (<class 'Exception'>, Exception('丟擲了 Exception'), <traceback object at 0x100e29200>) <class 'Exception'> 丟擲了 Exception <traceback object at 0x100e29200> 遇到異常時關閉資源
不再丟擲異常
基於生成器實現上下文管理器
通過裝飾器 contextlib.contextmanager,來定義自己所需的基於生成器的上下文管理器
from contextlib import contextmanager @contextmanager def file_manager(name, mode): try: # 1、開啟檔案 file = open(name, mode) # 2、返回檔案資源物件 yield file finally: # 3、關閉檔案 file.close() with file_manager('a.txt', 'w') as file: print(file) file.write('hello world')
- 函式 file_manager() 就是一個生成器
- 當執行 with as 語句時,獲取檔案資源物件,生成器暫停執行,返回檔案資源物件並賦值給 file
- 當 with 語句執行完後,生成器繼續執行剩餘程式碼,關閉檔案,釋放資源
總結
- 基於生成器的上下文管理器時,不再用定義 __enter__() 和 __exit__() 方法
- 但需要加上裝飾器 @contextmanager