次のコードが動作する理由を誰かが説明できますか?
import types
class Dummy():
def __init__(self, name):
self.name = name
def __del__(self):
print "delete",self.name
d1 = Dummy("d1")
del d1
d1 = None
print "after d1"
d2 = Dummy("d2")
def func(self):
print "func called"
d2.func = types.MethodType(func, d2)
d2.func()
del d2
d2 = None
print "after d2"
d3 = Dummy("d3")
def func(self):
print "func called"
d3.func = types.MethodType(func, d3)
d3.func()
d3.func = None
del d3
d3 = None
print "after d3"
出力(d2のデストラクタは呼び出されないことに注意してください)はこれです(python 2.7)
delete d1
after d1
func called
after d2
func called
delete d3
after d3
追加されたメソッドを削除せずにデストラクタが呼び出されるようにコードを「修正」する方法はありますか?つまり、d2.func = Noneを配置するのに最適な場所はデストラクタ内にあるということです!
ありがとう
[編集]最初のいくつかの回答に基づいて、__del__
。私は、非直感的な動作と考えるものを実証する最短の関数を作成しようとしました。循環参照が作成されたと仮定していますが、その理由はわかりません。可能であれば、循環参照を回避する方法を知りたい。
__del__
を避けるためのアドバイスはありがたいのですが、提供されたコードサンプルで適切に機能させるにはどうすればよいかという疑問がありました。
ショートバージョン:次のコードでは、weakref
を使用して循環参照を回避しています。質問を投稿する前にこれを試したと思っていましたが、何か間違ったことをしたに違いないと思います。
import types, weakref
class Dummy():
def __init__(self, name):
self.name = name
def __del__(self):
print "delete",self.name
d2 = Dummy("d2")
def func(self):
print "func called"
d2.func = types.MethodType(func, weakref.ref(d2)) #This works
#d2.func = func.__get__(weakref.ref(d2), Dummy) #This works too
d2.func()
del d2
d2 = None
print "after d2"
より長いバージョン:質問を投稿したときに、同様の質問を検索しました。代わりにwith
を使用できること、および__del__
が[〜#〜] bad [〜#〜]であることを広く認識しています。
with
の使用は理にかなっていますが、特定の状況でのみです。ファイルを開いて読み取り、閉じることは、with
が完全に良い解決策である良い例です。オブジェクトが必要な特定のコードブロックを行ったので、オブジェクトとブロックの終わりをクリーンアップする必要があります。
データベース接続は、with
を使用してうまく機能しない例としてよく使用されるようです。通常、接続を作成するコードのセクションを残し、よりイベントドリブンで接続を閉じる必要があるためです。 (順次ではなく)時間枠。
with
が適切な解決策でない場合、2つの選択肢があります。
__del__
が機能することを確認します(weakrefの使用法のより良い説明については このブログ を参照してください)atexit
モジュールを使用して、プログラムの終了時にコールバックを実行します。 このトピック を参照してください。単純化されたコードを提供しようとしましたが、私の実際の問題はイベント駆動型であるため、with
は適切なソリューションではありません(単純化されたコードではwith
で問題ありません)。また、私のプログラムは長時間実行される可能性があるため、atexit
を避けたいと思っていました。できるだけ早くクリーンアップを実行できるようにしたいと思います。
したがって、この特定のケースでは、weakref
を使用し、__del__
の動作を妨げる循環参照を防ぐことが最善のソリューションであることがわかりました。
これはルールの例外かもしれませんが、weakref
と__del__
を使用するのが正しい実装であるユースケースがあります。
___del__
_が呼び出されると想定することはできません。リソースが自動的に割り当て解除されることを期待する場所ではありません。 (非メモリ)リソースが解放されていることを確認したい場合は、release()
または同様のメソッドを作成し、それを明示的に呼び出す(または context manager = Thanatosが以下のコメントで指摘したとおり)。
少なくとも___del__
_ documentation を非常によく読んでください。そうすれば、おそらく___del__
_を使用しないでください。 (_gc.garbage
_についての他の悪い点については、___del__
_ ドキュメント も参照してください)
delの代わりに、with
演算子を使用できます。
http://effbot.org/zone/python-with-statement.htm
ファイルタイプオブジェクトと同様に、次のようなことができます
with Dummy('d1') as d:
#stuff
#d is guaranteed to be out of scope
del
は__del__
を呼び出しません
使用中のdel
はローカル変数を削除します。 __del__
は、オブジェクトが破棄されるときに呼び出されます。 Pythonは、言語がオブジェクトをいつ破棄するかを保証しません。
Pythonの最も一般的な実装としてのCPythonは、参照カウントを使用します。その結果、delは期待どおりに機能することがよくあります。ただし、参照サイクルがある場合は機能しません。
d3 -> d3.func -> d3
Pythonはこれを検出しないため、すぐにクリーンアップしません。また、参照サイクルだけではありません。例外がスローされた場合、おそらくデストラクタを呼び出したいでしょう。ただし、Pythonは通常、トレースバックの一部としてローカル変数を保持します。
解決策は、__del__
メソッドに依存しないことです。むしろ、コンテキストマネージャーを使用します。
class Dummy:
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
print "Destroying", self
with Dummy() as dummy:
# Do whatever you want with dummy in here
# __exit__ will be called before you get here
これは機能することが保証されており、パラメータをチェックして例外を処理しているかどうかを確認し、その場合は別のことを行うこともできます。
コンテキストマネージャの完全な例。
class Dummy(object):
def __init__(self, name):
self.name = name
def __enter__(self):
return self
def __exit__(self, exct_type, exce_value, traceback):
print 'cleanup:', d
def __repr__(self):
return 'Dummy(%r)' % (self.name,)
with Dummy("foo") as d:
print 'using:', d
print 'later:', d
問題の本当の心はここにあるように思えます:
関数の追加は動的(実行時)であり、事前に知られていない
あなたが本当に求めているのは、さまざまな機能を、ポリモーフィズムとも呼ばれるプログラム状態を表すオブジェクトに柔軟にバインドする方法だと思います。 Pythonはメソッドをアタッチ/デタッチするのではなく、異なるクラスをインスタンス化することで非常にうまくいきます。クラスの組織をもう一度見ることをお勧めします。一時的な状態オブジェクト。is-a:ではなくhas-aパラダイムを使用して、状態が変化するたびに、コアデータを状態オブジェクトにラップするか、コアの属性に対する新しい状態オブジェクト。
そのようなPythonic OOPを使用できないと確信している場合は、クラス内のすべての関数を定義して最初から追加のインスタンス属性にバインドすることにより、別の方法で問題を回避できます(コンパイルしている場合を除く)これらの関数はユーザー入力からオンザフライで)
class LongRunning(object):
def bark_loudly(self):
print("WOOF WOOF")
def bark_softly(self):
print("woof woof")
while True:
d = LongRunning()
d.bark = d.bark_loudly
d.bark()
d.bark = d.bark_softly
d.bark()
weakref
を使用する別の解決策は、func.__get__(self, type(self))
を返すクラスで___getattr__
_または___getattribute__
_をオーバーライドすることによって呼び出されたときにのみ、関数をインスタンスに動的にバインドすることです。インスタンスにバインドされた関数のfunc
の代わりに。これが、クラスで定義された関数の動作です。残念ながら(一部のユースケースでは)pythonは、インスタンス自体にアタッチされた関数に対して同じロジックを実行しませんが、これを行うために変更できます。インスタンスにバインドされた記述子にも同様の問題がありました。ここでのパフォーマンスはおそらくweakref
を使用した場合ほど良くありませんが、pythonビルトインのみを使用して動的に割り当てられた関数に対して透過的に動作するオプションです。
これを頻繁に行う場合は、インスタンスレベルの関数の動的バインディングを行うカスタムメタクラスが必要になる場合があります。
別の方法は、関数をクラスに直接追加することです。これにより、呼び出されたときに適切にバインディングが実行されます。多くのユースケースでは、これにはいくつかの頭痛の種が含まれます。つまり、関数が衝突しないように適切に名前空間を設定します。インスタンスIDはこれに使用できますが、cPythonのIDはプログラムの存続期間にわたって一意であることが保証されていないため、ユースケースで機能することを確認するためにこれを少し熟考する必要があります。特に、オブジェクトがスコープ外に出たときにクラス関数を必ず削除する必要があります。したがって、そのID /メモリアドレスが再び利用可能になります。 ___del__
_はこれに最適です:)。または、オブジェクト作成時にインスタンスに名前空間化されたすべてのメソッドをクリアできます(___init__
_または___new__
_)。
別の代替手段(pythonマジックメソッドをいじるのではなく)は、動的にバインドされた関数を呼び出すためのメソッドを明示的に追加することです。これには、ユーザーが通常のpython構文を使用して関数を呼び出せないという欠点があります。
_class MyClass(object):
def dynamic_func(self, func_name):
return getattr(self, func_name).__get__(self, type(self))
def call_dynamic_func(self, func_name, *args, **kwargs):
return getattr(self, func_name).__get__(self, type(self))(*args, **kwargs)
"""
Alternate without using descriptor functionality:
def call_dynamic_func(self, func_name, *args, **kwargs):
return getattr(self, func_name)(self, *args, **kwargs)
"""
_
この投稿を完成させるために、weakref
オプションも表示します。
_import weakref
inst = MyClass()
def func(self):
print 'My func'
# You could also use the types modules, but the descriptor method is cleaner IMO
inst.func = func.__get__(weakref.ref(inst), type(inst))
_