with文(コンテキストマネージャ)
『Python』のwith文はコンテキストマネージャを使ってリソースを安全に管理するための構文です。ファイルのクローズ・ロックの解放・ネットワーク接続の切断など、使い終わったら必ず後処理が必要なリソースを、例外が発生した場合でも確実に解放します。
構文
# 基本構文: with文のブロックを抜けると自動で後処理が実行されます。
with 式 as 変数:
# リソースを使った処理
# 複数リソースを同時に管理します(Python 3.1以降)。
with 式A as 変数A, 式B as 変数B:
# 複数のリソースを使った処理
# 括弧記法(Python 3.10以降): 複数リソースを括弧でまとめて書けます。
with (
式A as 変数A,
式B as 変数B,
):
# 複数のリソースを使った処理
キーワード・メソッド一覧
| キーワード / メソッド | 概要 |
|---|---|
| with 式 as 変数: | コンテキストマネージャを起動し、__enter__の戻り値を変数に束縛します。ブロック終了時に__exit__が自動で呼ばれます。 |
| as 変数 | __enter__が返したオブジェクトを受け取る変数を指定します。不要な場合は省略できます。 |
| __enter__(self) | withブロックに入るときに呼ばれるメソッドです。as節に渡すオブジェクトを返します。 |
| __exit__(self, exc_type, exc_val, exc_tb) | withブロックを抜けるときに呼ばれるメソッドです。例外の有無に関わらず実行されます。Trueを返すと例外を握りつぶします。 |
| contextlib.contextmanager | ジェネレータ関数をコンテキストマネージャに変換するデコレータです。クラスを書かずに__enter__/__exit__を定義できます。 |
サンプルコード
ファイルの読み書きをwith文で行います。withブロックを抜けると自動でファイルが閉じられます。
jujutsu_log.py
# 呪術師の記録をファイルに書き込みます。
# with ブロックを抜けると自動で f.close() が呼ばれます。
with open("jujutsu_log.txt", "w", encoding="utf-8") as f:
f.write("虎杖悠仁 - 宿儺の器\n")
f.write("伏黒恵 - 十種影法術\n")
f.write("釘崎野薔薇 - 芻霊呪法\n")
# ファイルを読み込みます。例外が発生しても確実にクローズされます。
with open("jujutsu_log.txt", "r", encoding="utf-8") as f:
for line in f:
print(line.rstrip())
python3 jujutsu_log.py 虎杖悠仁 - 宿儺の器 伏黒恵 - 十種影法術 釘崎野薔薇 - 芻霊呪法
複数のファイルを同時にwith文で開きます。入力ファイルを読み込みながら出力ファイルに書き込むパターンです。
copy_log.py
# まず書き込みファイルを準備します。
with open("source.txt", "w", encoding="utf-8") as f:
f.write("五条悟 - 無下限呪術\n")
f.write("夏油傑 - 呪霊操術\n")
# 2つのファイルを同時に開いてコピーします。
# 両方のファイルがブロック終了時に確実に閉じられます。
with open("source.txt", "r", encoding="utf-8") as src, \
open("dest.txt", "w", encoding="utf-8") as dst:
for line in src:
dst.write(line)
print("コピーが完了しました。")
# Python 3.10 以降は括弧記法で書けます(バックスラッシュ不要)。
# with (
# open("source.txt", "r", encoding="utf-8") as src,
# open("dest.txt", "w", encoding="utf-8") as dst,
# ):
# for line in src:
# dst.write(line)
python3 copy_log.py コピーが完了しました。
__enter__と__exit__を実装したクラスを作り、with文で使えるコンテキストマネージャを自作します。呪術戦闘のロック管理に見立てたサンプルです。
barrier_context.py
# コンテキストマネージャを自作するクラスです。
class BarrierContext:
def __init__(self, name):
self.name = name
def __enter__(self):
# with ブロックに入るときに呼ばれます。
# as 節に渡すオブジェクトを return します。
print(f"【{self.name}】結界を展開しました。")
return self # as 節で受け取るオブジェクト
def __exit__(self, exc_type, exc_val, exc_tb):
# with ブロックを抜けるときに必ず呼ばれます。
# exc_type が None なら例外なし、それ以外なら例外あり。
if exc_type is not None:
print(f"【{self.name}】例外が発生しました: {exc_val}")
print(f"【{self.name}】結界を解除しました。")
return False # False を返すと例外を伝播させます。
def check(self, target):
print(f" {target} を結界内で確認しました。")
# with 文でコンテキストマネージャを使います。
with BarrierContext("領域展開") as barrier:
barrier.check("宿儺")
barrier.check("漏瑚")
print("---")
# 例外が発生した場合も __exit__ は確実に呼ばれます。
try:
with BarrierContext("嵌合暗翳庭") as barrier:
barrier.check("真人")
raise RuntimeError("呪力が尽きました")
except RuntimeError:
print("例外をキャッチしました。")
python3 barrier_context.py 【領域展開】結界を展開しました。 宿儺 を結界内で確認しました。 漏瑚 を結界内で確認しました。 【領域展開】結界を解除しました。 --- 【嵌合暗翳庭】結界を展開しました。 真人 を結界内で確認しました。 【嵌合暗翳庭】例外が発生しました: 呪力が尽きました 【嵌合暗翳庭】結界を解除しました。 例外をキャッチしました。
contextlib.contextmanagerデコレータを使うと、クラスを書かずにジェネレータ関数でコンテキストマネージャを定義できます。
contextlib_sample.py
from contextlib import contextmanager
# @contextmanager デコレータで関数をコンテキストマネージャにします。
# yield の前が __enter__、yield の後が __exit__ に相当します。
@contextmanager
def curse_session(user_name):
print(f"{user_name} の呪術セッションを開始します。")
try:
yield user_name # as 節で受け取る値を yield します。
finally:
# finally なので例外が発生しても必ず実行されます。
print(f"{user_name} の呪術セッションを終了します。")
# with 文で使います。
with curse_session("虎杖悠仁") as user:
print(f" {user} が黒閃を発動します。")
print("---")
# 複数のコンテキストマネージャを組み合わせます。
with curse_session("伏黒恵") as user1, curse_session("釘崎野薔薇") as user2:
print(f" {user1} と {user2} が連携します。")
python3 contextlib_sample.py 虎杖悠仁 の呪術セッションを開始します。 虎杖悠仁 が黒閃を発動します。 虎杖悠仁 の呪術セッションを終了します。 --- 伏黒恵 の呪術セッションを開始します。 釘崎野薔薇 の呪術セッションを開始します。 伏黒恵 と 釘崎野薔薇 が連携します。 釘崎野薔薇 の呪術セッションを終了します。 伏黒恵 の呪術セッションを終了します。
概要
with文の仕組みはコンテキストマネージャプロトコルに基づいています。『with 式 as 変数:』と書くと、まず式の『__enter__』メソッドが呼ばれ、その戻り値が変数に代入されます。ブロックを抜けるときには、例外の有無に関わらず必ず『__exit__』メソッドが呼ばれます。これはtry/finallyと同等ですが、withを使う方が簡潔でPythonicです。
『__exit__』の引数『exc_type, exc_val, exc_tb』は、例外が発生していなければすべてNoneになります。例外を握りつぶしたい場合は『return True』を返します。通常は『return False』(または何も返さない)として例外を伝播させるのが一般的です。例外処理の詳細はtry / exceptを参照してください。
複数リソースを同時に管理する場合、Python 3.1以降はカンマ区切りで『with A as a, B as b:』と書けます。Python 3.10以降は括弧でまとめる記法も使えるため、リソースが多い場合でも可読性を保てます。ファイル操作の詳細はファイルの読み書きやopen()も参照してください。
よくあるミス
__enter__ の戻り値を as で受け取り忘れる
with open(...) as f: の as f がないと、ファイルオブジェクトを変数に代入できず、ファイルの読み書きができません。
with_as_ng.py
# NGパターン: as を書かないとファイルオブジェクトを参照できない
with open("members.txt", "w"):
pass # ここでファイルへの参照が取れない
with_as_ok.py
# OKパターン: as で受け取る
with open("members.txt", "w") as f:
f.write("Itadori Yuji\n")
f.write("Megumi Fushiguro\n")
with open("members.txt", "r") as f:
print(f.read())
Itadori Yuji Megumi Fushiguro
__exit__ で True を返して例外を握りつぶす
コンテキストマネージャの __exit__ メソッドが True を返すと、発生した例外が握りつぶされます。意図せず True を返していると、エラーが発生しても気づかなくなります。
with_exit_ng.py
class ResourceManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return True # 例外を握りつぶす(NGパターン)
with ResourceManager():
raise ValueError("Something went wrong")
print("This line runs because the exception was suppressed")
This line runs because the exception was suppressed
with_exit_ok.py
class ResourceManager:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
print(f"Error: {exc_val}")
return False # 例外を伝播させる(OKパターン)
with ResourceManager() as r:
raise ValueError("Something went wrong")
Error: Something went wrong Traceback (most recent call last): ... ValueError: Something went wrong
with 文の外でリソースにアクセスしようとする
with ブロックを抜けた後はリソースがクローズされます。ブロックの外でリソースを使おうとするとエラーになります。
with_scope.py
# NGパターン: with ブロックの外でファイルを読もうとする
with open("members.txt", "w") as f:
f.write("Itadori Yuji\n")
f.read() # ValueError: I/O operation on closed file
with_scope_ok.py
# OKパターン: with ブロックの中でアクセスする
with open("members.txt", "r") as f:
content = f.read()
# ブロックを出てからも変数は使える(ファイルオブジェクトではなくデータを保持)
print(content)
Itadori Yuji
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。