こんにちは、吉政創成 菱沼です。
今回もPythonエンジニア育成推進協会のPython 3 エンジニア認定実践試験の主教材「Python実践レシピ/技術評論社」を使って学習中です。
前回は、with文の概要とコンテキストマネージャーについて学びました。
今回は、コンテキストマネージャー周辺について学びます。
コンテキストマネージャーを定義してみよう。
まずは引用文から。
—————————
P.49
@contextlib.contextmanagerというデコレーターを使用してジェネレーター関数を記述することで、コンテキストマネージャーを定義できます。__enter__()と__exit__()メソッドを別々に定義したクラスを書く必要がないので、簡潔に定義できます。
—————————
デコレーターとジェネレーター関数とは何なのか…。
- デコレーター
デコレーターは書籍の3.7でより深く学習することになります。その該当のページを読んでみると、「関数やメソッド、クラスをデコレート(装飾)する機能です。デコレーターを使用すると、関数やメソッド、クラスそのものの中身を変えずに共通のロジックを適用できる」と書いてありました。
つまり、、、関数・メソッド・クラスを(処理で)飾りつけする=関数・メソッド・クラスの前後に特定の処理を後付けしたり、別の用途で使える形に変換したりできる機能だということのようです。
- ジェネレーター関数
ジェネレーター関数は、yield を使うことで、処理を一時停止・再開できる特殊な関数です。
関数は呼び出しごとに最初から最後まで処理が実行開始されますが、ジェネレーター関数の場合はyieldで処理を一時的に中断し、実行状況を保存できるので、再開したいタイミングで処理を中断したところから再開させることができます。
参考:公式ドキュメントhttps://docs.python.org/ja/3/glossary.html#term-generator
つまり…ジェネレーター関数をwith文用のコンテキストマネージャー(処理の前後で必ず実行したい処理)として利用するために、デコレーターを使って、with文で使える形に変換(ラップ)することができる、ということのようです。
サンプルコードを確認してみる
では続けてサンプルコードを確認していきます。
このサンプルコードではpython.txtの中身を表示するための処理を行う際、「ファイルを開く」、「ファイルを閉じる」という処理がどのタイミングで行われているかが分かるように表示するというものになっています。

これを実行した結果がこちらです。

冒頭で確認した通り、デコレーターを記述する際に始まる、@contextlib.contextmanagerから始まり、my_opecontext_managerというジェネレーター関数が作成されています。
この後は
1.with文実行(ジェネレーター関数呼び出し&yieldの前の処理が実行)
2.withブロックの内容が実行(ここではpython.txtの中身を表示)
3.yieldの後ろの処理(finally)が実行され、ファイルが閉じられる
という流れで処理が進みます。
withブロック内で例外が発生していないため、except 節は実行されていません。
もし、withブロック実行中に例外が発生してしまった場合、例外を抱えたままyield直後に戻るそうです。上記のコードでは、発生したexceptで捕捉した例外の内容を表示するような処理になっています。
この処理が終わり次第、finallyの処理が実行されるので、適切に終了の処理が実行されています。
try-finally を使用していない場合、withブロック内で例外が発生すると、yield の後ろに書かれた処理が実行されないまま終了してしまう可能性があります。
そのため、ジェネレーター関数を用いてコンテキストマネージャーを定義する際は、後処理を確実に実行するために try-finally 構造を用いることが重要なようです。
with文のまとめ
よくwith文が利用されるケースにはどんなものがあるのか。引用文です。
—————————
P.50
- try-finallyの再利用
- ファイルやネットワーク、データベースコネクションのopen/close
- 限られた範囲でのみ特別な処理を行いたい(組み込み関数を書き換えるなど)
また、contextlibモジュールでは、withブロック内でのみ指定した例外を無視するcontextlib.suppressなどのコンテキストマネージャーをいくつか提供しています。非同期処理を行うconcurrent.futures.ThreadPoolExcecutorは、with文と組み合わせることで、with文を抜ける時に安全に終了してくれます。
—————————
引用文中にあるcontextlib.suppressはwithブロック内でのみ指定した例外を無視するためのコンテキストマネージャーです。try/except と異なり、例外を無視する範囲を with ブロックに限定できるため、処理全体に影響を与えることなく、安全に例外処理を行うことができるとのこと。
また、concurrent.futures.ThreadPoolExecutorは非同期処理を行うということですが、スレッドや内部リソースの後始末が重要になります。with 文と組み合わせることで、with ブロックを抜ける際に shutdown() が自動的に呼び出され、スレッドプールが安全に終了させることができるということで、終了処理を確実に行いたいオブジェクトの場合には with 文と非常に相性が良いんだとか。
他にもcontextlibにはwith文で使うための便利なコンテキストマネージャーがいくつか用意されている、ということで、どんなものが用意されているのか、興味がある方は公式ドキュメントをご確認ください。
https://docs.python.org/ja/3.13/library/contextlib.html
書籍では上記に加え、以下の2点も紹介されていました。
contextlib.redirect_stdout(new_target):標準出力(sys.stdout)をnew_targetに変更する
contextlib.redirect_stderr(new_target):標準エラー(sys.stderr)をnew_targetに変更する
今回はこちらで終了です。次回は3.3 関数の引数に入ります。
お付き合いいただきありがとうございました。
実践試験について知りたい方は以下をご覧ください。
●Python3エンジニア認定実践試験
