# 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()
```