---
# System prepended metadata

title: Python Context manager

---

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