---
tags: Python
---
# Context Manager
[TOC]
以開檔來說,以往以 `try-except` 的寫法,在使用檔案過程中發生例外時,在 `finally` 處確保正確關檔。
這種寫法雖然不會有問題,但是缺點就是必須手動加入關閉檔案的程式碼,不是很方便,也很容易忘記,Python2.5 後提供 `with`,可以**自動地開始和結束一些事情**。
> This PEP adds a new statement "with" to the Python language to make it possible to factor out standard uses of try/finally statements.
> In this PEP, context managers provide `__enter__()` and `__exit__()` methods that are invoked on entry to and exit from the body of the with statement.
>
> [name=[PEP 343 -- The "with" Statement](https://www.python.org/dev/peps/pep-0343/)]
context manager 用到了 `__enter__` 和 `__exit__` 。用 `with` 會呼叫 `__enter__`,並將回傳值指派給 `as` 的變數。
## 純 Class
`__exit__` 像是 `finally`,就算 `with` 的 scope 內有出錯,一樣會執行。
```python
class DBHandler:
def __enter__(self):
print('start')
def __exit__(self, exception_type, exception_value, traceback):
print('end')
with DBHandler():
print('do something')
```
```python
class DBHandler:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f'start with {self.name}')
return f'db_{self.name}'
def __exit__(self, exception_type, exception_value, traceback):
print('end')
with DBHandler('PET') as db:
print(f'do something with {db}')
```
`__exit__` 內可以在判斷 `with` 是否為正常結束:
```python
def __exit__(self, exception_type, exception_value, traceback):
if exception_type is None:
print('success')
else:
print('error')
```
## [`contextlib.ContextDecorator`](https://docs.python.org/3/library/contextlib.html#contextlib.ContextDecorator)
可以以 decorator 或 with 的形式給予。以 decorator 的方式的缺點為:不能使用 `as` 來取得回傳物件。
```python
class DBHandler_decorator(contextlib.ContextDecorator):
def __enter__(self):
print('start')
def __exit__(self, exception_type, exception_value, traceback):
print('end')
@DBHandler_decorator()
def function():
print('do something')
@DBHandler_decorator()
def function2(name):
print('do something')
return f'db_{name}'
with DBHandler_decorator():
print('do something')
```
`ContextDecorator` 在 [`__call__`](https://github.com/python/cpython/blob/master/Lib/contextlib.py#L56) 包裝成 decorator。
## [`contextlib.contextmanager`](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager)
> This function is a decorator that can be used to define a factory function for with statement context managers, without needing to create a class or separate `__enter__()` and `__exit__()` methods.
>
> [name=[`contextlib.contextmanager` / contextlib](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager)]
```python
import contextlib
@contextlib.contextmanager
def db_handler():
print('start')
yield
print('end')
with db_handler():
print('do something')
```
```python
import contextlib
@contextlib.contextmanager
def db_handler(name):
print(f'start with {name}')
yield f'db_{name}'
print('end')
with db_handler('PET') as db:
print(f'do something with {db}')
```
注意上方的用法比較像是 `else` 而非 `finally`,當 `with` 的 scope 內有錯時,並不會執行 `yield` 後的程式,可以加上 `try-except` 修改。
```python
import contextlib
@contextlib.contextmanager
def db_handler(name):
print(f'start with {name}')
try:
yield f'db_{name}'
finally:
print('end')
with db_handler('PET') as db:
print(f'do something with {db}')
raise Exception()
```
## 應用
* openpyxl
```python
@contextlib.contextmanager
def open_excel(filename, *args, **kwargs):
wb = openpyxl.load_workbook(filename, *args, **kwargs)
try:
yield wb
finally:
wb.close()
with open_excel('test.xlsx') as wb:
print(wb)
```
* session
```python
@contextlib.contextmanager
def session_scope():
session = Session()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
```
* timer
```python
import time
class Timer:
def __enter__(self):
self.start = time.clock()
return self
def __exit__(self, *args):
self.end = time.clock()
self.interval = self.end - self.start
print(self.interval)
with Timer() as t:
print('do something')
```
## 參考資料
* [PEP 343 -- The "with" Statement](https://www.python.org/dev/peps/pep-0343/)
* [`contextlib.contextmanager` / contextlib](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager)
* [簡潔的Python|重構你的舊程式](https://www.books.com.tw/products/E050051199)