Pythonのwithとcontextmanagerについて調べてみた

Pythonを使っていてwith句とcontextmanagerをなんとなく使っていたので、
自身の理解を深めるためにまとめました。

contextmanagerを理解するにはwith句を理解する必要があるっぽい。
なのでまずwithから復習します。

## with句について

- with句は「メインで行いたい処理」の「前後」に処理を埋め込める記述の仕方的なイメージ。
    → 例えばファイルとかデータベースのデータを操作する処理を記述するときとかに役に立つ。
    → ファイルやデータベースのデータを処理する場合、決まってファイルを開いたり、DBとのconnectionを通す必要があり、データの処理が終わった後はファイルを閉じたり、DBとの接続を閉じる必要がある。
    → with句を使わない場合は、例えばファイルの操作とかだと、try/finallyを使って、

    1. ファイルを開く　
    2. ファイルを操作
    3. ファイルを閉じる
 
を記述する必要がある。
    
例えば以下ような感じ
    
 ```python
try:
    1. ファイルを開く処理（前処理）
    2. ファイルを編集する処理（メインでやりたい処理）
finally:
    3. ファイルを閉じる処理（後処理）
 ```
    
 → with句とは、そのようなtry/finallyを置き換えるもの
上の処理をwith句で書くと、
    
```python
with open(file_path) as f:
    f（開かれたファイル）を編集する処理（メインでやりたい処理）
```
    
普通のクラスに\_\_enter\_\_(), \_\_exit\_\_()をつけるだけでwith句で利用できるクラス（カスタムマネージャー）ができる
→ \_\_enter\_\_()は前処理、\_\_exit\_\_()は後処理を記述するメソッドのよう。
→ ファイル操作に当てると、\_\_enter\_\_()にファイルを開く処理を記述。\_\_exit\_\_()にファイルを閉じる処理を記述みたいに書くと、ファイル操作のソースコードがかなりシンプルに記述できる。
    
```python
class FileOpen:
    def __enter__():
        1. ファイルを開く処理
        return { 開いたファイル } ⇦ これでwith句の　as のところにファイルを開いたファイルを渡せる

    def __exit__():
        3. ファイルを閉じる処理

with FileOpen() as f:
    2. f（開かれたファイルを）編集する処理
```
    
→ プラスで実は\_\_exit\_\_()の書き方を工夫すると、エラーハンドリングもできる模様。
→ \_\_exit\_\_(self, exc_type, exc_value, traceback)みたいに書くと、エラーハンドリングができるっぽい。
    
```python
class A:
    def __enter__():
        前処理を記述

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            後処理を記述
        else:
            if exc_type == "xxxError":
                return { エラーメッセージとかエラーコードとか }
```
    
→ なのでwith句用のクラスを作れば、try/except/fanallyの記述をせずともそれと同じことができるようになる。
→ ただこのようなクラスを作るのも面倒だし、もっとシンプルに直感的に書きたいからcontexlibのcontextmanagerを使おうという感じ。
    

## contextmanagerについて

- メソッドの上に@contextmanagerをつけることで（デコレータという）、with句で利用できるメソッド簡単に作れるよ。※クラスではなくメソッド
→ ざっくりいうと、yieldの前に書いたものが前処理、yieldの後に書いたものが後処理といった感じ
→ yiledは、前処理が終わった後にwith句のブロックの中のメインの処理に一旦戻るよ的なもの
→ メインの処理が終わるとまた、@contextmanagerの関数に戻ってきて、yieldの後に書いた処理が実行される
→ なのでファイルの操作だと、1. yieldの前にファイル開く処理を書く、2. メインの処理でファイル操作処理を書く、3. yieldの後にファイルを閉じるの処理を書くみたいな感じ
    
```python
from contextlib import contextmanager

@contextmanager
def file_open():
    1. ファイルを開く処理
    2. yield {開いたファイル}
    3. ファイルを閉じる処理

with file_open() as f:
    2. f（開かれたファイル）を編集する処理（メインでやりたい処理）
```
    
→ ただし@contextmanagerの書き方だとメソッドとして記述するので、上の\_\_exit\_\_()みたいな関数でエラーハンドリングをするとかはないので、
try/exceptは自分で書きましょうね。でも直感的にシンプルに書けるのでこっちの方がいいよね。という感じと理解しました。
    
```python
from contextlib import contextmanager

@contextmanager
def file_open():
    try:
        1. ファイルを開く処理
        2. yield {開いたファイル}
        3. ファイルを閉じる処理
    except xxxError as e
        return { エラーメッセージとかエラーコードとか }

with file_open() as f:
    2. f（開かれたファイル）を編集する処理（メインでやりたい処理）
```
    
    

### 参考
    
[Pythonのコンテキストマネージャってなんなの？と思って調べた話](https://blog.mtb-production.info/entry/2018/04/10/183000)
[Python のコンテキストマネージャと with ブロック](https://python.keicode.com/lang/context-manager.php)
[Python : コンテキストマネージャは with 文の前後で処理を実行する](https://commte.net/7472#:~:text=%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%83%9E%E3%83%8D%E3%83%BC%E3%82%B8%E3%83%A3(context%20manager)%20%E3%81%A8,%E3%81%AE%E5%87%A6%E7%90%86%E3%82%92%E6%89%B1%E3%81%84%E3%81%BE%E3%81%99%E3%80%82)
[Python: contextlib.contextmanager を使うときの注意点](https://blog.amedama.jp/entry/2015/10/02/234946)