python `contextlib.contextmanager`でカスタムコンテキストマネージャを作る

## はじめに

Pythonのコンテキストマネージャは、リソースの確保と解放を効率的に管理するための強力なツールです。`with`文と組み合わせて使用することで、ファイルの操作やデータベース接続など、様々なシナリオで便利に使えます。

この記事では、`contextlib.contextmanager`デコレータを使用して、カスタムコンテキストマネージャを簡単に作成する方法を紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/f0b23762-4f22-7839-9e87-3e8d599af22e.png)

## コンテキストマネージャの基本

まず、コンテキストマネージャの基本的な使い方を確認しましょう。

```python
with open('example.txt', 'w') as f:
    f.write('Hello, World!')
```

この例では、`open()`関数がコンテキストマネージャとして機能し、ファイルを自動的に閉じてくれます。

withキーワード：

コンテキストマネージャを使用することを示します。


コンテキストマネージャの役割：

リソースの獲得と解放を自動的に管理します。


open()関数の動作：

コンテキストマネージャとして機能し、以下の処理を行います：
a. ファイルを開く（リソースの獲得）
b. withブロック内のコードを実行
c. ブロック終了時に自動的にファイルを閉じる（リソースの解放）


withの利点：

ファイルの明示的なクローズ処理（f.close()）が不要になります。
例外が発生した場合でも、確実にファイルが閉じられるため、リソースリークを防ぐことができます。

withを使用しない場合の同等のコード
```python
pythonCopyf = open('example.txt', 'w')
try:
    f.write('Hello, World!')
finally:
    f.close()
```

この方法では、ファイルの開閉を明示的に管理する必要があり、例外処理も自分で実装しなければなりません。

## `contextlib.contextmanager`の使い方

`contextlib.contextmanager`デコレータを使うと、ジェネレータ関数を使ってカスタムコンテキストマネージャを簡単に作成できます。

基本的な構造は以下の通りです：

```python
from contextlib import contextmanager

@contextmanager
def custom_context_manager():
    # セットアップコード
    try:
        yield  # リソースを yield
    finally:
        # クリーンアップコード
```

## 実践例：タイミング測定コンテキストマネージャ

それでは、実際に`contextlib.contextmanager`を使って、コードブロックの実行時間を測定するコンテキストマネージャを作成してみましょう。

```python
import time
from contextlib import contextmanager

@contextmanager
def timer():
    start_time = time.time()
    try:
        yield
    finally:
        end_time = time.time()
        print(f"実行時間: {end_time - start_time:.4f}秒")

# 使用例
with timer():
    # 時間を測定したいコード
    time.sleep(2)  # 2秒間スリープ
```

このコードを実行すると、以下のような出力が得られます：

```
実行時間: 2.0021秒
```

## 値を返すコンテキストマネージャ

コンテキストマネージャから値を返すこともできます。以下は、一時的にディレクトリを変更し、元のディレクトリを返すコンテキストマネージャの例です。

```python
import os
from contextlib import contextmanager

@contextmanager
def change_directory(new_dir):
    old_dir = os.getcwd()
    os.chdir(new_dir)
    try:
        yield old_dir
    finally:
        os.chdir(old_dir)

# 使用例
with change_directory('/tmp') as old_dir:
    print(f"現在のディレクトリ: {os.getcwd()}")
    print(f"元のディレクトリ: {old_dir}")

print(f"元のディレクトリに戻りました: {os.getcwd()}")
```

## まとめ

`contextlib.contextmanager`デコレータを使用することで、カスタムコンテキストマネージャを簡単に作成できます。これにより、コードの可読性が向上し、リソース管理が容易になります。

コンテキストマネージャは以下のような場面で特に有用です：

- ファイルやデータベース接続などのリソース管理
- 一時的な状態変更（設定の変更、ディレクトリの変更など）
- パフォーマンス測定やロギング
- トランザクション管理

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/c5e8a524-778f-5430-60c6-5970236e0ecc.png)

## 参考情報

より詳細な情報や高度な使用方法については、以下の公式ドキュメントを参照してください：

1. [contextlib — コンテキスト上で使用されるユーティリティ](https://docs.python.org/ja/3/library/contextlib.html)
   - `contextlib`モジュールの公式ドキュメント（日本語）です。`contextmanager`デコレータの詳細な説明や他の関連機能についても記載されています。

2. [PEP 343 -- The "with" Statement](https://www.python.org/dev/peps/pep-0343/)
   - `with`文の導入を提案したPython Enhancement Proposal（PEP）です。コンテキストマネージャの背景や設計思想について詳しく知ることができます。

これらの公式リソースを参照することで、コンテキストマネージャについてより深く理解し、より効果的に活用することができるでしょう。

Pythonの`contextlib`モジュールには他にも便利な機能がありますので、これらの公式ドキュメントを参照してさらに学んでみてください。

コンテキストマネージャを適切に使用することで、よりクリーンで管理しやすいコードを書くことができます。ぜひ、自分のプロジェクトでも活用してみてください！

## 参考記事

https://qiita.com/Tadataka_Takahashi/items/9a9813da93a526803794

https://qiita.com/Tadataka_Takahashi/items/9eb59225511844d8d8ac