Pythonドキュメントから:
__del__()
インタプリタの終了時にまだ存在するオブジェクトに対してメソッドが呼び出されることは保証されていません。
なぜだめですか?この保証が行われた場合、どのような問題が発生しますか?
Pythonドキュメントから:
__del__()
インタプリタの終了時にまだ存在するオブジェクトに対してメソッドが呼び出されることは保証されていません。
なぜだめですか?この保証が行われた場合、どのような問題が発生しますか?
私はここでの以前の答えに納得していません。
まず、与えられた例は、終了時にメソッドが呼び出されるのを妨げないことに注意してください。__del__
実際、現在のCPythonは、指定されたメソッドを呼び出します。Python2.7__del__
の場合は2回、Python3.4の場合は1回です。したがって、これは保証が行われない理由を示す「キラーの例」にはなり得ません。
ドキュメントの記述は、デストラクタを呼び出すのは悪いという設計原則に動機付けられていないと思います。特に、CPython 3.4以降では、期待どおりに常に呼び出されているようであり、この警告は重要ではないようです。
代わりに、このステートメントは、CPython実装が終了時にすべてのデストラクタを呼び出さないことがあるという事実を単に反映していると思います(おそらく実装の理由で)。
状況は、CPython3.4および3.5がインタプリタの終了時に常にすべてのデストラクタを呼び出すということのようです。
対照的に、CPython2.7は常にこれを行うとは限りません。確か__del__
に、メソッドがある場合、それらのオブジェクトは削除できないため、通常、循環参照があるオブジェクトではメソッドは呼び出されません__del__
。ガベージコレクターはそれらを収集しません。インタプリタが終了するとオブジェクトは消えますが(もちろん)、オブジェクトはファイナライズされていないため、__del__
メソッドが呼び出されることはありません。これは、PEP442の実装後のPython3.4では当てはまりません。
ただし、Python 2.7は、デストラクタがなくても、インタプリタの終了時に到達できなくなった場合にのみ、循環参照を持つオブジェクトをファイナライズしないようです。
おそらく、この動作は十分に特殊であり、ドキュメントのように、一般的な免責事項によって簡単に表現するのが最適であることを説明するのは困難です。
次に例を示します。
class Foo(object):
def __init__(self):
print("Foo init running")
def __del__(self):
print("Destructor Foo")
class Bar(object):
def __init__(self):
print("Bar1 init running")
self.bar = self
self.foo = Foo()
b = Bar()
# del b
del b
コメントアウトされているため、Python 3.4ではありますが、Python2.7ではデストラクタinはFoo
呼び出されません。
追加するdel b
と、どちらの場合もデストラクタが(インタプリタ出口で)呼び出されます。
あなたがいくつかの厄介なことをした場合、Pythonが永久に削除しようとする削除不可能なオブジェクトを見つけることができます:
class Phoenix(object):
def __del__(self):
print "Deleting an Oops"
global a
a = self
a = Phoenix()
pythonはオブジェクト(特に循環参照を持つオブジェクト)がいつ__del__
削除されるかを保証しないため、どのような場合でも依存することは適切ではありません。そうは言っても、おそらくクラスをコンテキストマネージャーに変える方が良い解決策です...そうすれば、例外などの場合でもクリーンアップコードが呼び出されることを保証できます...
これは、削除を行うと問題が発生するためではないと思います。Pythonの哲学は、オブジェクトの削除の使用に依存するように開発者を奨励することではありません。これらの削除のタイミングは予測できないためです。ガベージコレクターが発生するかどうかは、ガベージコレクター次第です。
ガベージコレクターが、スコープ外になった後、不明な時間、未使用のオブジェクトの削除を延期する可能性がある場合、オブジェクトの削除中に発生する副作用に依存することは、あまり堅牢で決定論的な戦略ではありません。RAIIはPythonの方法ではありません。代わりに、Pythonコードは、コンテキストマネージャーやデコレーターなどを使用してクリーンアップを処理します。
さらに悪いことに、オブジェクトサイクルなどの複雑な状況では、ガベージコレクターがオブジェクトを削除できることを検出しない場合があります。Pythonが成熟するにつれて、この状況は改善されました。ただし、このような予想されるGCの動作には例外があるため、Python開発者がオブジェクトの削除に依存することは賢明ではありません。
インタープリターの終了は、Python開発者、特に古いバージョンのPythonの場合、すべてのオブジェクトでGC削除が実行されるようにすることについて完全に厳密ではなかったもう1つの複雑な状況であると推測します。
デストラクタが呼び出されない1つの例は、メソッド内で終了する場合です。この例を見てください:
class Foo(object):
def __init__(self):
print("Foo init running")
def __del__(self):
print("Destructor Foo")
class Bar(object):
def __init__(self):
print("Bar1 init running")
self.bar = self
self.foo = Foo()
def __del__(self):
print("Destructor Bar")
def stop(self):
del self.foo
del self
exit(1)
b = Bar()
b.stop()
出力は次のとおりです。
Bar1 init running
Foo init running
Destructor Foo
fooを明示的にデストラクタすると、デストラクタが呼び出されますが、barのデストラクタは呼び出されません。
また、fooを明示的に削除しないと、適切に破棄されません。
class Foo(object):
def __init__(self):
print("Foo init running")
def __del__(self):
print("Destructor Foo")
class Bar(object):
def __init__(self):
print("Bar1 init running")
self.bar = self
self.foo = Foo()
def __del__(self):
print("Destructor Bar")
def stop(self):
exit(1)
b = Bar()
b.stop()
出力:
Bar1 init running
Foo init running
おそらく、ほとんどのプログラマーは、デストラクタは死んだ(すでに到達できない)オブジェクトに対してのみ呼び出されるべきであると想定し、ここで終了すると、ライブオブジェクトに対してそれらを呼び出します。
開発者がライブオブジェクトに対するデストラクタ呼び出しを予期していなかった場合、厄介なUBが発生する可能性があります。少なくとも、アプリケーションがハングした場合は、タイムアウト後にアプリケーションを強制的に閉じるために何かを行う必要があります。ただし、一部のデストラクタは呼び出されない場合があります。
同じ理由で、JavaRuntime.runFinalizersOnExitは非推奨になりました。