43

次のコードがそのように動作する理由を誰かが説明できますか?

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__ます。私は、直感的でない振る舞いであると私が考えるものを示す最短の関数を作成しようとしました。循環参照が作成されていると思いますが、理由はわかりません。可能であれば、循環参照を回避する方法を知りたいのですが…。

4

8 に答える 8

40

が呼び出されるとは想定できません__del__。リソースが自動的に割り当て解除されることを期待する場所ではありません。(メモリ以外の)リソースが確実に解放されるようにしたい場合は、release()または同様のメソッドを作成してから、それを明示的に呼び出す必要があります(または、以下のコメントで Thanatos が指摘したようにコンテキスト マネージャで使用します)。

少なくとも、__del__ ドキュメントをよく読む必要があります__del__。(に関するその他の悪い点については、gc.garbage ドキュメントも参照してください__del__)

于 2011-05-24T00:31:45.173 に答える
23

避けるべきアドバイスに感謝し__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__悪いことです。

使用withすることは理にかなっていますが、特定の状況でのみです。ファイルを開いて読み取り、閉じることはwith、完全に良い解決策である良い例です。オブジェクトが必要な特定のコードブロックに移動し、オブジェクトとブロックの終わりをクリーンアップしたいとします。

データベース接続は、を使用してうまく機能しない例として頻繁に使用されるようwithです。通常、接続を作成するコードのセクションを離れ、よりイベント駆動型の(シーケンシャルではなく)時間枠で接続を閉じる必要があるためです。 。

が適切な解決策ではない場合with、2つの選択肢があります。

  1. 動作することを確認し__del__ます(weakrefの使用法の詳細については、このブログを参照してください)
  2. atexitプログラムが閉じたときに、モジュールを使用してコールバックを実行します。たとえば、このトピックを参照してください。

簡略化されたコードを提供しようとしましたが、私の実際の問題はよりイベント駆動型であるためwith、適切な解決策ではありません(with簡略化されたコードには問題ありません)。また、プログラムが長時間実行される可能性があるため、回避しatexitたいと思いました。また、できるだけ早くクリーンアップを実行できるようにしたいと考えています。

weakrefしたがって、この特定のケースでは、循環参照を使用して機能を妨げるのを防ぐのに最適なソリューションであることがわかりました__del__

これはルールの例外かもしれませんが、正しい実装であるIMHOを使用するユースケースがありweakrefます__del__

于 2011-05-29T02:04:09.450 に答える
11

delの代わりに、with演算子を使用できます。

http://effbot.org/zone/python-with-statement.htm

ファイルタイプオブジェクトと同じように、次のようなことができます

with Dummy('d1') as d:
    #stuff
#d's __exit__ method is guaranteed to have been called
于 2011-05-24T00:37:48.723 に答える
8

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

これは動作することが保証されており、パラメーターをチェックして、例外を処理しているかどうかを確認し、その場合は別のことを行うこともできます。

于 2011-05-24T00:43:26.497 に答える
2

問題の真の核心はここにあるように私には思えます:

関数の追加は (実行時に) 動的であり、事前にはわかりません

あなたが本当に求めているのは、さまざまな機能をプログラムの状態を表すオブジェクトにバインドする柔軟な方法 (ポリモーフィズムとも呼ばれる) だと思います。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()
于 2013-06-12T20:39:25.090 に答える
2

コンテキスト マネージャーの完全な例。

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
于 2011-05-29T16:52:19.233 に答える