# Python Context manager 如有錯誤歡迎糾正,小弟正在學習中 ## 上下文管理器(Context Manager) 在 Python 中,上下文管理器是一種自動管理資源的工具,能幫助我們確保在使用完資源後進行適當的清理。中文有一句成語叫「前因後果」,我們可以用這個成語來形象地解釋上下文管理器的作用。 例子 假設今天老闆要求你用財務上半季的營收 Excel 檔案視覺化為長條圖。要完成這個任務,我們首先需要讀取 Excel 檔案。這裡,我們可以使用上下文管理器來處理檔案的打開和關閉。 - **前因**:因為需要處理某個資源(如打開文件),所以我們寫了一段代碼來操作這個資源。 - **後果**:當代碼執行完畢後,需要進行一些清理工作(如關閉文件),這樣可以確保資源被正確地釋放。 例子1 ```python # 上下文管理器示例 excel_file = open('202401.csv') # 上文 (前因) try: excel_to_bar_chart(excel_file) finally: excel_file.close() # 下文 (後果) ``` 上面程式碼的確達到需求,但有沒有更簡潔的寫法呢? 在PEP-343中 with敘述句誕生了!! ### with實現上下文管理 with 是python常見的上下文管理實作方法,簡潔更pythonic,透過縮排範圍來區分上下文,以上面例子1來改寫 例子2 當檔案在with內執行完後到with外面時,檔案會自動關閉,讓程式更加簡潔與可控 ```python # 上下文管理器示例 with open('202401.csv') as excel_file: #上文 excel_to_bar_chart(excel_file) # 下文 print('done') todo_other() #... ``` 那with是如何運作的呢?那就需要介紹``__enter__``、``__exit__``magic method了 ### 透過`__enter__`、`__exit__` magic method來實現上下文 #### `__enter__` magic method **定義**: `__enter__` 用於進入上下文管理器。當 `with` 語句進入上下文時,Python 會自動調用 `__enter__` 方法。 ``` python def __enter__(self): pass ``` **用途**: `__enter__` 方法允許你執行在進入上下文時需要的初始化工作或設置。通常,這是用來開啟資源(如文件、網絡連接等)並返回一個可以在 `with` 語句內部使用的對象。`__enter__` 方法的返回值可以用於 `with` 語句內部的變量。 #### `__exit__` magic method **定義**: `__exit__` 用於退出上下文管理器。當 `with` 語句退出上下文時,Python 會自動調用 `__exit__` 方法。 - exc_type:例外型態 - exc_value:例外錯誤值 - traceback: 例外堆疊追蹤 ``` python def __exit__(self, exc_type, exc_value, traceback): pass ``` **用途**: `__exit__` 方法允許你執行在退出上下文時需要的清理工作或釋放資源。這包括關閉文件、釋放網絡連接等。`__exit__` 方法可以處理異常,如果異常被處理,方法應返回 `True`;如果不處理異常,方法應返回 `False` 或者不返回值。 > ⛔如果沒有特殊理由請不要返回True,錯誤是不能被掩蓋或隱藏,不然會引起錯誤追朔問題造成嚴重後果 直接看例子,建立pandas Context Manager Class,透過pandas讀取excel,當讀取得excel head不包含Q1(上半季為Q1與Q2)代表excel有問題跳出異常 例子3 ``` python import pandas as pd class ExcelContextManager: def __init__(self, file_path): self.file_path = file_path self.df = None def __enter__(self): self.df = pd.read_excel(self.file_path) return self.df def __exit__(self, exc_type, exc_value, traceback): if exc_type: print(f"Exception type: {exc_type}") print(f"Exception value: {exc_value}") return False file_path = '202401.xlsx' with ExcelContextManager(file_path) as df: print(df.head()) if 'Q1' not in list(df.keys()): raise ValueError("An error occurred") excel_to_bar_chart(df) ``` 當讀取excel我不在意pandas是如何讀取的我只希望回傳給我dataframe,及__enter__中分離關注點 ``` python def __enter__(self): self.df = pd.read_excel(self.file_path) return self.df ``` 而報表是否有誤與產生長條圖是我們關注的邏輯 ``` python print(df.head()) if 'Q1' not in list(df.keys()): raise ValueError("An error occurred") excel_to_bar_chart(df) ``` 如果__exit__ return True會發生甚麼事情? 正常處理另外情境 return False ``` python def __exit__(self, exc_type, exc_value, traceback): if exc_type: print(f"Exception type: {exc_type}") print(f"Exception value: {exc_value}") return False # 程式raise excpetion,並中斷程式 Traceback (most recent call last): File "example.py", line 25, in <module> raise ValueError("An error occurred") ValueError: An error occurred ``` 如果把False改成True會變成 ``` python def __exit__(self, exc_type, exc_value, traceback): if exc_type: print(f"Exception type: {exc_type}") print(f"Exception value: {exc_value}") return True # 下面是print出結果,但程式沒有終止仍然可以往下執行 Exception type: <class 'ValueError'> Exception value: An error occurred ``` 從上面例子中 with 在維護與職責上是有幫助的,以上面例子可以看到一些特性 - 分離關注點 - 專注關注邏輯 - 無須關注資源管理 ### `contextlib.contextmanager` 裝飾器實現上下文管理 除了使用 `with` 語句配合 `__enter__` 和 `__exit__` 方法之外,`contextlib` 模塊還提供了一種簡便的方法來實現上下文管理,即使用 `contextmanager` 裝飾器。這種方法常用於簡單的上下文管理,尤其適合以下情境: - **簡單上下文管理**:當上下文管理邏輯簡單時,使用 `contextmanager` 裝飾器可以避免編寫冗長的類和方法。 - **處理大數據集**:當需要處理的大量數據集時,生成器特性可以有效控制內存使用。例如,只需按需生成數據,而不是一次性加載所有數據。 > 💡 生成器不會一次性把所有數據存儲在內存中,而是按需生成數據。這樣可以處理大數據集而不會耗盡內存。 ### 示例 以下是使用 `contextlib.contextmanager` 裝飾器處理 CSV 文件的例子: ```python import csv from contextlib import contextmanager @contextmanager def csv_context_manager(file_path): try: file = open(file_path, mode='r', newline='', encoding='utf-8') reader = csv.DictReader(file) yield reader finally: file.close() file_path = '202401.csv' with csv_context_manager(file_path) as reader: first_row = next(reader) print(first_row) header = reader.fieldnames print("Header:", header) ``` ### `contextlib.ContextDecorator` 裝飾器實現上下文管理 `contextlib` 模塊提供了 `ContextDecorator` 類,這是一種實現上下文管理的便捷方法。與 `contextmanager` 不同,`ContextDecorator` 可以直接用於裝飾器,使函數無需關注上下文的存在。這樣的設計有助於降低代碼耦合度,提高可維護性。 #### 優勢 - **簡化上下文管理**:通過 `ContextDecorator`,上下文管理邏輯被封裝在裝飾器中,函數本身不需關心上下文管理的細節。 - **易於修改**:如果上下文管理的邏輯改變,只需更新裝飾器的實現,而無需修改函數本身。 #### 示例 以下是使用 `ContextDecorator` 來處理 CSV 文件的例子: ```python import csv from contextlib import ContextDecorator class CSVContextManager(ContextDecorator): def __init__(self, file_path): self.file_path = file_path self.file = None self.reader = None def __enter__(self): self.file = open(self.file_path, mode='r', newline='', encoding='utf-8') self.reader = csv.DictReader(self.file) return self.reader def __exit__(self, exc_type, exc_value, traceback): if self.file: self.file.close() return False @CSVContextManager('202401.csv') def process_csv(reader): first_row = next(reader) print(first_row) header = reader.fieldnames print("Header:", header) process_csv() ```