Python contextlib.closing()とは?close()を確実に呼ぶ方法

Pythonのcontextlib.closing()は、close()メソッドしか持たないオブジェクトをwith文で使えるようにする関数です。

ブロックを抜けるときに自動でclose()を呼んでくれます。


contextlib.closing()の基本的な使い方

from contextlib import closing

class Conn:
    def close(self):
        print("  closeされた")
    def query(self):
        return "結果"

with closing(Conn()) as c:
    print("  ", c.query())

実行結果:

   結果
  closeされた

asで受け取れるのは元のオブジェクトそのものです。closing()が包んでいても、中身は変わりません。


なぜ必要なのか

withが使えるのは__enter__と__exit__を持つオブジェクトだけです。

print("Connは__enter__を持たない:", hasattr(Conn(), "__enter__"))

with Conn():
    pass

実行結果:

Connは__enter__を持たない: False
AttributeError: __enter__

close()はあるのにwithが使えない——この隙間を埋めるのがclosing()です。


例外が起きても閉じられる

from contextlib import closing

try:
    with closing(Conn()):
        raise ValueError("失敗")
except ValueError:
    print("  → closeは実行済み")

実行結果:

  closeされた
  → closeは実行済み

これがclosing()を使う理由です。手で書くとtry-finallyが必要になります。

c = Conn()
try:
    c.query()
finally:
    c.close()

1〜2箇所ならこれでも構いませんが、数が増えるとネストが深くなります。


どんなときに使うか

対象 例
with非対応のライブラリ 古いDBドライバ・自作クラス
close()だけ持つ標準ライブラリ sqlite3.Connection・urlopen()
自作クラスに__enter__を足したくない 使う側だけで解決したい

open()のように最初からwith対応しているものには不要です。

with closing(open("a.txt")) as f:   # 冗長
with open("a.txt") as f:            # こちらで十分

実践例|sqlite3の接続

紛らわしい代表例です。sqlite3のConnectionはwithに渡せますが、閉じてはくれません。

import sqlite3

conn = sqlite3.connect(":memory:")

with conn:
    conn.execute("CREATE TABLE t (x)")

print("with後も使える:", conn.execute("SELECT 1").fetchone())
conn.close()

実行結果:

with後も使える: (1,)

with conn:がするのはコミットまたはロールバックだけです。切断まで自動化するにはclosing()と組み合わせます。

import sqlite3
from contextlib import closing

with closing(sqlite3.connect(":memory:")) as conn:
    with conn:
        conn.execute("CREATE TABLE t (x)")
        conn.execute("INSERT INTO t VALUES (1)")
    print("中:", conn.execute("SELECT * FROM t").fetchall())

conn.execute("SELECT 1")

実行結果:

中: [(1,)]
ProgrammingError: Cannot operate on a closed database.
書き方 コミット クローズ
with conn: する しない
with closing(conn): しない する
入れ子にする する する

外側が切断、内側がコミット——これがsqlite3の推奨形です。詳しくは「Python Connection.close()とは?接続を閉じる方法」で解説しています。


実践例|urlopen

from contextlib import closing
from urllib.request import urlopen

with closing(urlopen("https://example.com")) as res:
    print(res.status)

現在のurlopen()はwithに直接対応していますが、古いコードではclosing()を使った書き方をよく見かけます。読むときに戸惑わないよう、形を知っておくと役立ちます。


closingの中身は単純

やっていることは、次のクラスとほぼ同じです。

class closing:
    def __init__(self, thing):
        self.thing = thing
    def __enter__(self):
        return self.thing
    def __exit__(self, *exc):
        self.thing.close()

受け取ったものをそのまま返し、抜けるときにclose()を呼ぶだけです。仕組みが分かれば、close()以外の後始末が必要な場合に@contextmanagerで自作する判断もつきます。

from contextlib import contextmanager

@contextmanager
def released(lock):
    try:
        yield lock
    finally:
        lock.release()

contextlib.closing()のまとめ

  • closing()はclose()だけのオブジェクトをwithで使えるようにする
  • asで受け取るのは元のオブジェクト
  • 例外が起きても必ずclose()が呼ばれる
  • open()などwith対応済みのものには不要
  • sqlite3ではwith conn:と入れ子にする(役割が違う)
  • close()以外の後始末は@contextmanagerで自作する

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら