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で自作する



