Python __del__とは?デストラクタの役割と使うべきでない理由

__del__は、オブジェクトが破棄されるときに呼ばれる特殊メソッド——いわゆるデストラクタです。

ただし「いつ呼ばれるか保証されない」ため、後始末の手段としては推奨されません。理由を順に見ていきます。


__del__の基本

class Resource:
    def __init__(self, name):
        self.name = name

    def __del__(self):
        print(f"__del__: {self.name}")

r = Resource("A")
print("delする前")
del r
print("delした後")

実行結果:

delする前
__del__: A
delした後

この例ではdel rの直後に呼ばれています。

しかしこれは偶然に近い——次の節から、そうならない場合を見ます。


del文は__del__を呼ぶとは限らない

r2 = Resource("B")
r3 = r2          # 参照が2つになった

del r2
print("参照が残っているので呼ばれない")

del r3

実行結果:

参照が残っているので呼ばれない
__del__: B

del r2は「変数名を消す」だけです。オブジェクト自体はr3から参照されているので生きています。

__del__が呼ばれるのは参照が1つも無くなったときです。

書き方 意味
del obj 変数名を消す(__del__とは無関係)
del obj[key] __delitem__を呼ぶ
参照が0になる __del__が呼ばれる

循環参照では遅れる

import gc

class Node:
    def __init__(self, name):
        self.name = name
        self.other = None

    def __del__(self):
        print(f"__del__: {self.name}")

x = Node("X")
y = Node("Y")
x.other = y
y.other = x

del x, y
print("→ すぐには呼ばれない")

gc.collect()
print("gc.collect()後")

実行結果:

→ すぐには呼ばれない
__del__: X
__del__: Y
gc.collect()後

互いに参照し合っていると参照数が0にならないため、ガベージコレクタが動くまで破棄されません。

いつ動くかはプログラム側から制御できません。「ファイルを閉じるのが数分後」ということが起こり得ます。


例外が握りつぶされる

class E:
    def __del__(self):
        raise ValueError("これは外に出ない")

e = E()
del e
print("プログラムは継続")

実行結果:

Exception ignored in: <function E.__del__ at 0x102b99e50>
Traceback (most recent call last):
  ...
ValueError: これは外に出ない
プログラムは継続

__del__の中の例外はtryで捕まえられません。標準エラー出力に「Exception ignored in」と表示されるだけです。

後始末に失敗しても、呼び出し側は失敗に気づけません。


withを使うべき理由

ここまでの問題はwith文ならすべて解決します。

class Resource:
    def __enter__(self):
        print("open")
        return self

    def __exit__(self, exc_type, exc_value, tb):
        print("close")
        return False

with Resource():
    print("使用中")

実行結果:

open
使用中
close
__del__ with
呼ばれるタイミング 不定 ブロックを抜けた瞬間
例外時 握りつぶされる そのまま伝わる
循環参照の影響 受ける 受けない

ファイル・DB接続・ロックの解放は、必ずwithで書いてください。

「Python __enter__・__exit__とは?with文で使えるクラスを作る方法」で解説しています。


それでも__del__を書くなら

「閉じ忘れたときの最後の保険」として書くことはあります。その場合の作法です。

  • 本来はclose()やwithで解放する——__del__は保険に徹する
  • すでに解放済みかを確認してから処理する
  • 中で例外を出さない(tryで囲む)
  • 他のオブジェクトに触らない——終了処理中はすでに破棄されている場合がある
class Resource:
    def __init__(self):
        self.closed = False

    def close(self):
        if not self.closed:
            self.closed = True
            print("解放しました")

    def __del__(self):
        try:
            self.close()
        except Exception:
            pass

標準ライブラリではweakref.finalizeという、より安全な仕組みも用意されています。


関連記事

近いテーマの記事です。あわせてどうぞ。

__del__のまとめ

  • 参照が0になったときに呼ばれる
  • del objは名前を消すだけ——直接の関係はない
  • 循環参照ではGCが動くまで遅れる
  • 中の例外は握りつぶされる
  • 後始末はwith(__enter__/__exit__)を使う
  • 書くなら保険として、例外を出さずに

あわせて読みたい

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

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