web-dev-qa-db-ja.com

私はこれを理解していませんpython __del__ behavior

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

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

31

__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つの選択肢があります。

  1. __del__が機能することを確認します(weakrefの使用法のより良い説明については このブログ を参照してください)
  2. atexitモジュールを使用して、プログラムの終了時にコールバックを実行します。 このトピック を参照してください。

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

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

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

16

___del___が呼び出されると想定することはできません。リソースが自動的に割り当て解除されることを期待する場所ではありません。 (非メモリ)リソースが解放されていることを確認したい場合は、release()または同様のメソッドを作成し、それを明示的に呼び出す(または context manager = Thanatosが以下のコメントで指摘したとおり)。

少なくとも___del___ documentation を非常によく読んでください。そうすれば、おそらく___del___を使用しないでください。 (_gc.garbage_についての他の悪い点については、___del___ ドキュメント も参照してください)

35
Nick Bastin

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

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

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

with Dummy('d1') as d:
    #stuff
#d is guaranteed to be out of scope
11
Falmarri

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

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

8
Winston Ewert

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

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()
2

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))
_
0
DylanYoung