with
このページでは豊富な例を用いてPythonのwith文の使い方を学ぶことができます。
with文はコンテキストマネージャ(context manager)を簡潔かつ安全に利用できるPythonの構文です。
コンテキストマネージャとは、特定の処理を行う際に付随する入り口・出口処理を扱うオブジェクトです。 with文は与えられたコンテキストマネージャを起動し、対象になっているオブジェクトの入り口・出口処理を適切なタイミングで実行します。
with文を使用することにより、try・except・finallyを用いる処理を簡潔に記述することができ、 かつ、「ファイルを閉じる」処理に代表されるリソースの開放などの出口処理を確実に実行することによりコードの安全性に寄与します。
# 記述例
with コンテキスト式 as コンテキスト式が生成するオブジェクトを受ける変数:
# 入り口・出口処理の間に行う処理
TL;DR
基本
# ファイルに文字列を書き込む処理をする場合を考える
# with文を使用する場合
# ファイルオブジェクトはコンテキストマネージャであり、
# with文中の処理が完了すると自動的にcloseメソッドが呼び出される
with open('./sample.log', 'a', encoding='UTF-8') as file:
file.write('Python')
# with文を使用しない場合.1
# ファイルを開く
file = open('./sample.log', 'a', encoding='UTF-8')
# ファイルに書き込む
file.write('Python')
# ファイルを閉じる
file.close()
# with文を使用しない場合.2
try:
# ファイルを開く
file = open('./sample.log', 'a', encoding='UTF-8')
# ファイルに書き込む
file.write('Python')
except ValueError as err:
print(err)
finally:
# ファイルを閉じる
file.close()
関連情報:例外処理(try,except,else,finally)の使用方法
独自クラスを用いたwithの挙動チェック
# 独自クラスでコンテキストマネージャを実装する
# __enter__と__exit__が実装されていることが条件
class MyCtxManager:
def __enter__(self):
print('__enter__')
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__')
# with文とコンテキストマネージャの挙動
# 生成したオブジェクトをwith文で使用しない場合はasは指定しなくともよい
with MyCtxManager():
print('with suite')
# ファイルオブジェクトはcloseメソッドが__exit__メソッドで呼び出されている
==> __enter__
==> with suite
==> __exit__
# withブロック内で例外が発生した場合、__exit__が実行されてから例外のトレースバックが表示される
with MyCtxManager():
print('with suite')
raise Exception
------------------------------
__enter__
with suite
__exit__
Traceback (most recent call last):
...
Exception
------------------------------
関連情報:class(クラス定義)の使用方法
複数の要素の指定
# コンテキストマネージャA
class ACtxManager:
def __enter__(self):
print('A.__enter__')
def __exit__(self, exc_type, exc_val, exc_tb):
print('A.__exit__')
# コンテキストマネージャB
class BCtxManager:
def __enter__(self):
print('B.__enter__')
def __exit__(self, exc_type, exc_val, exc_tb):
print('B.__exit__')
# with文は複数のコンテキストマネージャを指定することができる
with ACtxManager() as a, BCtxManager() as b:
print('with suite')
==> A.__enter__
==> B.__enter__
==> with suite
==> B.__exit__
==> A.__exit__
# 上記は以下のように記述可能
with ACtxManager() as a:
with BCtxManager() as b:
print('with suite')
==> A.__enter__
==> B.__enter__
==> with suite
==> B.__exit__
==> A.__exit__
解説
# 記述例
with コンテキスト式 as コンテキスト式が生成するオブジェクトを受ける変数:
# 入り口・出口処理の間に行う処理
基本
with文はコンテキストマネージャ(context manager)を起動する構文です。
withの後ろにコンテキスト式(コンテキストマネージャを生成する式)を指定し、
続けてas
の後ろに変数を指定することで生成されたコンテキストマネージャをwith文内で扱うことができます。
コンテキストマネージャをwith文内で扱わない場合、asは省略することができます。
with文を使用することで特定のリソースを扱う際の入り口・出口処理の実行を担保することができます。
(入り口処理でエラーが起きる場合を除く)
例えば、コード例のようにopen関数を使用してファイルを編集する処理の場合、最後に「ファイルを閉じる」処理を実行することで安全にリソースを開放することができます。
これは実装者が明示的にファイルオブジェクトのcloseメソッドを呼び出すことでも可能です。
しかし、実装者任せになってしまう点、ファイルオブジェクトがコンテキストマネージャである点を考慮すると、
with文を使用してリソースの開放をPythonの言語機能に任せたほうが適切です。
上記のような一連の処理は try・except・finallyを使用した構文 を簡潔かつ安全なものに書き換えることができます。
コンテキストマネージャを扱う際は可能な限りwith文を用いて記述することを推奨します。
# with文を使用する場合
with open('./sample.log', 'a', encoding='UTF-8') as file:
# ファイルを閉じる処理は記述しなくともよい
file.write('Python')
# with文を使用しない場合.1
# ファイルを開く
file = open('./sample.log', 'a', encoding='UTF-8')
# ファイルに書き込む
file.write('Python')
# ファイルを閉じる
file.close()
# with文を使用しない場合.2
try:
# ファイルを開く
file = open('./sample.log', 'a', encoding='UTF-8')
# ファイルに書き込む
file.write('Python')
except ValueError as err:
print(err)
finally:
# ファイルを閉じる
file.close()
関連情報:例外処理(try,except,else,finally)の使用方法
独自クラスを用いたwithの挙動チェック
コンテキストマネージャは独自に実装することができ、
__enter__
と__exit__
を実装しているオブジェクトがコンテキストマネージャとみなされます。
正常系の挙動としては、コンテキストマネージャが生成された直後に__enter__メソッドが実行され、
with文内の最後の処理が終了したタイミングで__exit__メソッドが実行されます。
例えば、ファイルオブジェクトの「ファイルを閉じる」処理を行うcloseメソッドの呼び出しは__exit__メソッド内で行われます。
with文内で例外が発生した場合は、どの段階で例外が生じたかにより挙動が異なります。
より詳しく挙動を知りたい場合は公式ドキュメントの
コンテキストマネージャ型
の項を参照してください。
# 独自クラスのコンテキストマネージャ
class MyContext:
def __enter__(self):
print('__enter__')
def __exit__(self, exc_type, exc_val, exc_tb):
print('__exit__')
# with文とコンテキストマネージャの挙動
with MyContext():
print('with suite')
==> __enter__
==> with suite
==> __exit__
# withブロック内で例外が発生した場合
with MyContext():
print('with suite')
raise Exception
------------------------------
__enter__
with suite
__exit__
Traceback (most recent call last):
...
Exception
------------------------------
複数の要素の指定
with文には複数のコンテキストマネージャを指定することも可能です。
複数指定した場合、「後に指定したコンテキストマネージャ」が「先に指定したコンテキストマネージャ」のwith文内にネストされたように扱われます。 それぞれの入り口・出口処理の実行タイミングに気をつけて使用してください。
# コンテキストマネージャA
class ACtx:
def __enter__(self):
print('A.__enter__')
def __exit__(self, exc_type, exc_val, exc_tb):
print('A.__exit__')
# コンテキストマネージャB
class BCtx:
def __enter__(self):
print('B.__enter__')
def __exit__(self, exc_type, exc_val, exc_tb):
print('B.__exit__')
# with文は複数のコンテキストマネージャを指定することができる
with ACtx() as a, BCtx() as b:
print('with suite')
------------------------------
A.__enter__
B.__enter__
with suite
B.__exit__
A.__exit__
------------------------------
# 上記は以下の記述と同じ
with ACtx() as a:
with BCtx() as b:
print('with suite')
------------------------------
A.__enter__
B.__enter__
with suite
B.__exit__
A.__exit__
------------------------------