Skip to content

3.2 with文

Pythonの with 文は、ファイルの開閉など、ある処理の「前」と「後」に行うべき定型処理をカプセル化して再利用可能にしてくれる非常に便利な機能です。もともとは try-finally の利用パターンを再利用するために追加された機能ですが、それにとどまらず安全かつ簡潔なコードを書くための強力なツールとなります。

本記事では、with 文の基本から、内部の仕組みである「コンテキストマネージャー」、さらに実践的な使い方までを詳しく解説します。

with 文の代表的な使われ方は、open() 関数を使ったファイルの読み書きです。

# withを使用する例
with open('python.txt') as f:
print(f.read())

上記の処理を、with 文を使わずに書くと以下のようになります。

# withを使用しない例(try-finallyで実装)
f = None
try:
f = open('python.txt')
print(f.read())
finally:
if f:
f.close()

このように、ファイルを開いたあとは、処理中に例外が発生したとしても必ず最後にファイルを閉じてリソースを確実に解放する必要があります。with 文を使うと、同じことを try-finally ブロックを使って書くよりも圧倒的に簡潔に書くことができます。

また、as キーワードは、戻り値(この場合はファイルオブジェクト)を with ブロックの中で利用したい場合に指定します。

2. コンテキストマネージャーの仕組み

Section titled “2. コンテキストマネージャーの仕組み”

with 文に渡すオブジェクト(open() が返すファイルオブジェクトなど)を「コンテキストマネージャー」と呼びます。 コンテキストマネージャーとは、具体的には __enter__()__exit__() という2つの特殊メソッドを実装したクラスのインスタンスのことです。

  • __enter__() メソッド: with ブロック内の処理が開始される前に実行されます。このメソッドの戻り値が、as キーワードで指定した変数に代入されます。
  • __exit__() メソッド: with ブロック内の処理が終了したあとに実行されます。with ブロック内で例外が発生した場合、例外の情報(type, value, traceback)を引数として受け取ります。なお、__exit__() メソッド内で例外を捕捉(処理)しない限り、例外は外側へ再送出されます。

3. 独自のコンテキストマネージャーを簡潔に作る(@contextlib.contextmanager)

Section titled “3. 独自のコンテキストマネージャーを簡潔に作る(@contextlib.contextmanager)”

クラスを定義して2つの特殊メソッドを実装しなくても、標準ライブラリの contextlib モジュールを使えば、関数として簡潔にコンテキストマネージャーを定義できます。

@contextlib.contextmanager というデコレーターを使用し、ジェネレーター関数として記述します。

import contextlib
@contextlib.contextmanager
def my_open_context_manager(file_name):
file_obj = open(file_name, 'r')
try:
print('__enter__ : ファイルをopenします')
yield file_obj # asキーワードに渡される値
finally:
file_obj.close()
print('__exit__ : ファイルをcloseします')

【処理の流れのポイント】

  • yield 文より前に書かれたコードが、with ブロックの開始前に実行されます。
  • as キーワードに値を渡したい場合は、yield 文で値を返します。
  • yield 文よりあとに書かれたコードが、with ブロックの終了後に実行されます。
  • with ブロック内で発生した例外は yield 文を実行している箇所で送出されるため、後処理(close など)を確実に行うためには関数内に try-finally ブロックを記述する必要があります。

with 文は以下のようなケースで特によく利用されます。

  • try-finally の再利用
  • ファイルやネットワーク、データベースコネクションの open / close
  • 限られた範囲でのみ特別な処理を行いたい場合(一時的に組み込み関数を書き換える、処理の実行時間を計測するなど)

また、concurrent.futures.ThreadPoolExecutor などは with 文と組み合わせることで、ブロックを抜けるときに内部処理の終了を自動的に待機してくれるため、処理の終了を意識せずに利用できます。

5. 役立つ周辺知識:contextlibの便利なコンテキストマネージャー

Section titled “5. 役立つ周辺知識:contextlibの便利なコンテキストマネージャー”

contextlib モジュールには、自分で定義しなくてもすぐに使える便利なコンテキストマネージャーが用意されています。

  • contextlib.suppress(*exceptions): 指定した例外を無視(キャッチして何もしない)します。os.remove() でファイルが存在しない際のエラーを無視したい場合などに便利です。
  • contextlib.redirect_stdout(new_target): with ブロック内の標準出力(sys.stdout)先を指定のターゲット(ファイルオブジェクトなど)に変更します。
  • contextlib.redirect_stderr(new_target): with ブロック内の標準エラー出力(sys.stderr)先を指定のターゲットに変更します。

with 文を使いこなすことで、リソースの解放漏れを防ぎ、Pythonらしいシンプルで可読性の高いコードを書くことができるようになります。