順番に読み進めながら学べます

contextlib — リソース管理と with の自作

@contextmanageryieldwithをクラスなしに自作する書き方、try/finallyでの確実な後処理、suppressでの特定例外の握りつぶしを実例で学べます。

with文を自作するときに使うcontextlibを整理します。@contextmanagerによるシンプルな書き方と、suppressによる特定例外の握りつぶしの 2 つを順に見ていきます。

@contextmanager — with を簡単に自作する

Python のwith文は、ファイルopen / DB 接続 / ロック取得など「使い終わったら必ず後始末する」処理を安全に書ける構文です。自分でwith対応のクラスを作るには__enter__ / __exit__を定義する必要があり、ボイラープレートが多くなります。@contextmanagerデコレータは、ジェネレータyieldを含む関数)を 1 つ書くだけでwith 対応の context manager を作れる簡略記法で、yieldの前後で「処理開始」と「後始末」を表現します。

@contextmanager の構造
@contextmanagerdef section(name):前処理(yield の前)yield後処理(yield の後)
ジェネレータ関数 + @contextmanagerで、yield前が __enter__ 相当(前処理)yield 後が __exit__ 相当(後処理)になる。yieldwith の as に渡す値を返せる。
from contextlib import contextmanager

# --- 参考: クラスで with 対応を作る場合 -------------------
# class Section:
#     def __init__(self, name):
#         self.name = name
#     def __enter__(self):
#         print(f"--- {self.name} 開始 ---")
#         return self                 # with の as に渡す値
#     def __exit__(self, exc_type, exc, tb):
#         print(f"--- {self.name} 終了 ---")
# ----------------------------------------------------------

# @contextmanager 版: ジェネレータ関数 1 つで同じことが書ける
@contextmanager
def section(name):
    print(f"--- {name} 開始 ---")
    yield                       # ここで with ブロックの中身が動く
    print(f"--- {name} 終了 ---")

# 使う側
with section("集計"):
    total = sum(range(100))
    print("合計:", total)

# 出力:
# --- 集計 開始 ---
# 合計: 4950
# --- 集計 終了 ---
クラス版から @contextmanager 版への簡略化
クラス版__init__ / __enter__ /__exit__ の 3 メソッド@contextmanagerで簡略化ジェネレータ関数版yield の前=前処理yield の後=後処理
__init__ / __enter__ / __exit__の 3 メソッドを書くクラスを、@contextmanager付きジェネレータ関数 1 つに置き換えると、yieldの前後がそのまま前処理・後処理になり、コード量が大幅に減る。

例外でも後処理を確実に走らせるなら try/finally を入れる

yield後の処理は、with ブロック内で例外が起きると実行されないことがあります。ファイル close / ロック解放など必ず動かしたい後処理は、ジェネレータ関数の中でtry: yield finally: cleanup()の形で囲んでおくのが安全です。

DB トランザクションを模したtransaction(name)を@contextmanagerで作ります。BEGIN と COMMIT のログを前後に出し、ブロック内で実行する SQL を観察できる形にします。

① contextlib からcontextmanagerを読み込んでください

@contextmanagerを付けたtransaction(name)関数を定義してください — [name] BEGINを表示してからyieldyieldの後に[name] COMMITを表示します

with transaction("注文登録"):のブロック内で、SQL を 2 行表示してください — INSERT INTO orders (id, total) VALUES (1, 1980)UPDATE inventory SET stock = stock - 1

(正しく実行できれば解説が表示されます)

Python エディタ

コードを実行してください

suppress — 特定例外を握りつぶす

contextlib.suppress(例外型, ...)「指定した例外を握りつぶして、後続の処理を続ける」ための context manager です。try: ... except KeyError: passのような「特定の例外だけ無視して続行」パターンを 1 行で書けます。複数の例外型をまとめて指定でき、指定外の例外は通常通り伝播します。

from contextlib import suppress
import os

# 「ファイルが無くてもデフォルト値で動かす」例
config = {"theme": "light", "font_size": 14}
with suppress(FileNotFoundError):
    with open("user_config.txt") as f:
        config["theme"] = f.read().strip()
# user_config.txt が無くても止まらず、config はデフォルトのまま続行
print(config)
# → {'theme': 'light', 'font_size': 14}

# 「すでに削除済みなら無視して進む」例
with suppress(FileNotFoundError):
    os.remove("old.tmp")
print("削除完了 (元から無くても OK)")

辞書から欠損キーを取り出しても止まらず処理を続ける例を suppress で書きます。

① contextlib からsuppressを読み込んでください

② 辞書user = {"name": "Alice", "email": "alice@example.com"}を用意してください(ageキーは存在しない)

with suppress(KeyError):のブロック内でuser["age"]にアクセスしてみてください — 存在しないキーですが、エラーで止まらないことを確認します

④ ブロックを抜けた直後に継続: 処理は止まらないと表示してください

userの name と email をユーザー: ◯ <◯>の形で 1 行で表示してください

Python エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1@contextmanagerをジェネレータ関数に付けるとき、ちょうど 1 回 yield する必要があるのはなぜですか?

Q2with suppress(KeyError):のブロック内でKeyError が発生したときどうなりますか?