Pythonオブジェクトを正しくクリーンアップするにはどうすればよいですか?


462
class Package:
    def __init__(self):
        self.files = []

    # ...

    def __del__(self):
        for file in self.files:
            os.unlink(file)

__del__(self)上記はAttributeError例外で失敗します。が呼び出されたときにPythonが「グローバル変数」(このコンテキストのメンバーデータ?)の存在を保証しないことを理解してい__del__()ます。それが事実であり、これが例外の理由である場合、オブジェクトが適切に破壊されることを確認するにはどうすればよいですか?


3
あなたがリンクしたものを読むと、あなたがプログラムが終了するときについて話しているのでない限り、消えるグローバル変数はここには適用されないようです。それ以外の場合は、__ del __()メソッドのメンバー変数には適用されないと思います。
ケビンアンダーソン

3
例外は、プログラムが終了するずっと前にスローされます。私が受け取るAttributeError例外はPythonで、self.filesがPackageの属性であると認識されていません。私はこれを間違っているかもしれませんが、「グローバル」によってそれらがメソッドに対してグローバルな変数を意味しない場合(ただし、クラスに対してローカルである可能性があります)、この例外の原因がわかりません。Googleは、__ del __(self)が呼び出される前にPythonがメンバーデータをクリーンアップする権利を留保することを示唆しています。
ヴィルヘルムテル2009年

1
投稿されたコードは、Python 2.5で動作するようです。失敗している実際のコードを投稿できますか、または簡略化しました(エラーを引き起こすより簡単なバージョンほど簡単ですか?)
Silverfish

@ wilhelmtellより具体的な例を挙げられますか?すべてのテストで、delデストラクタは完全に機能します。
不明

7
知りたい場合:この記事で__del__は、の対応物として使用してはならない理由について詳しく説明し__init__ます。(すなわち、それは意味で「デストラクタ」ではありません__init__コンストラクタです。
フランクリン

回答:


619

withクリーンアップが必要なリソースの管理には、Pythonのステートメントを使用することをお勧めします。明示的なclose()ステートメントを使用する場合の問題はfinally、例外が発生したときにリソースリークを防ぐために、呼び出しをまったく忘れたり、ブロックに配置し忘れたりすることを心配する必要があることです。

withステートメントを使用するには、次のメソッドを使用してクラスを作成します。

  def __enter__(self)
  def __exit__(self, exc_type, exc_value, traceback)

上記の例では、

class Package:
    def __init__(self):
        self.files = []

    def __enter__(self):
        return self

    # ...

    def __exit__(self, exc_type, exc_value, traceback):
        for file in self.files:
            os.unlink(file)

次に、誰かがあなたのクラスを使用したいと思ったとき、彼らは次のことをします:

with Package() as package_obj:
    # use package_obj

変数package_objは、Package型のインスタンスになります(これは、__enter__メソッドによって返される値です)。その__exit__メソッドは、例外が発生したかどうかに関係なく、自動的に呼び出されます。

このアプローチをさらに一歩進めることもできます。上記の例では、with句を使用せずに、コンストラクタを使用してパッケージをインスタンス化することができます。あなたはそれが起こりたくないのです。これを修正するには、__enter__および__exit__メソッドを定義するPackageResourceクラスを作成します。次に、Packageクラスが__enter__メソッド内で厳密に定義され、返されます。このようにして、呼び出し元はwithステートメントを使用せずにPackageクラスをインスタンス化できませんでした。

class PackageResource:
    def __enter__(self):
        class Package:
            ...
        self.package_obj = Package()
        return self.package_obj

    def __exit__(self, exc_type, exc_value, traceback):
        self.package_obj.cleanup()

これは次のように使用します。

with PackageResource() as package_obj:
    # use package_obj

35
技術的に言えば、Pac​​kageResource().__ enter __()を明示的に呼び出して、ファイナライズされないパッケージを作成することもできますが、実際にはコードを破壊する必要があります。おそらく心配する必要はありません。
David Z、

3
ちなみに、Python 2.5 を使用している場合、withステートメントを使用できるようにするには、将来のインポートwith_statement から実行する必要があります。
クリントミラー

2
__del __()が動作する理由を示し、コンテキストマネージャーソリューションの使用に信用を与えるのに役立つ記事を見つけました:andy-pearce.com/blog/posts/2013/Apr/python-destructor-drawbacks
eikonomega

2
パラメータを渡したい場合、そのきれいな構成をどのように使用しますか?できるようになりたいwith Resource(param1, param2) as r: # ...
snooze92 '20

4
@ snooze92では、Resourceに、* argsと** kwargsを自身に格納する__init__メソッドを指定して、それらをenterメソッドの内部クラスに渡すことができます。withステートメントを使用する場合、__ init__は__enter__の前に呼び出されます
Brian Schlenker

48

標準的な方法は使用することatexit.registerです:

# package.py
import atexit
import os

class Package:
    def __init__(self):
        self.files = []
        atexit.register(self.cleanup)

    def cleanup(self):
        print("Running cleanup...")
        for file in self.files:
            print("Unlinking file: {}".format(file))
            # os.unlink(file)

ただし、これはPackage、Pythonが終了するまで、作成されたすべてのインスタンスを保持することに注意してください。

上記のコードを使用してpackage.pyとして保存されたデモ:

$ python
>>> from package import *
>>> p = Package()
>>> q = Package()
>>> q.files = ['a', 'b', 'c']
>>> quit()
Running cleanup...
Unlinking file: a
Unlinking file: b
Unlinking file: c
Running cleanup...

2
atexit.registerアプローチの良い点は、クラスのユーザーが何をするかを気にする必要がないことです(使用したwithか、明示的に呼び出した__enter__か?)。終了すると、機能しません。私の場合、オブジェクトがスコープから外れるか、それともpythonが終了するかどうかは関係ありません。:)
hlongmore 2018年

EnterとExitを使用して追加することもできatexit.register(self.__exit__)ますか?
myradio

@myradioそれがどのように役立つかわかりませんか?内__exit__ですべてのクリーンアップロジックを実行して、コンテキストマネージャーを使用できませんか?また、__exit__追加の引数(つまり__exit__(self, type, value, traceback))を取るので、それらについて説明する必要があります。どちらにしても、ユースケースが変わっているように見えるので、SOに別の質問を投稿するように思えますか?
ostrokach

33

クリントの答えの付録として、以下PackageResourceを使用して簡略化できますcontextlib.contextmanager

@contextlib.contextmanager
def packageResource():
    class Package:
        ...
    package = Package()
    yield package
    package.cleanup()

あるいは、おそらくPythonicではないかもしれませんが、オーバーライドすることができますPackage.__new__

class Package(object):
    def __new__(cls, *args, **kwargs):
        @contextlib.contextmanager
        def packageResource():
            # adapt arguments if superclass takes some!
            package = super(Package, cls).__new__(cls)
            package.__init__(*args, **kwargs)
            yield package
            package.cleanup()

    def __init__(self, *args, **kwargs):
        ...

そして単に使用しますwith Package(...) as package

物事を短くするには、クリーンアップ関数に名前を付けてcloseを使用しますcontextlib.closing。この場合、変更されていないPackageクラスを使用するか、より単純なクラスにwith contextlib.closing(Package(...))オーバーライドできます。__new__

class Package(object):
    def __new__(cls, *args, **kwargs):
        package = super(Package, cls).__new__(cls)
        package.__init__(*args, **kwargs)
        return contextlib.closing(package)

そして、このコンストラクタは継承されているので、単に継承することができます、例えば

class SubPackage(Package):
    def close(self):
        pass

1
これは素晴らしいです。特に最後の例が好きです。Package.__new__()ただし、メソッドの4行のボイラープレートを回避できないのは残念です。または多分できます。おそらく、ボイラープレートを一般化するクラスデコレータまたはメタクラスを定義できます。Pythonic思考のための食べ物。
セシルカレー

@CecilCurryありがとう、そして良い点。から継承するクラスでPackageもこれを行う必要があります(まだテストしていません)ので、メタクラスは必要ありません。私過去にメタクラスを使用するかなり奇妙な方法をいくつか見つけましたが...
Tobias Kienzler

@CecilCurry実際には、コンストラクターが継承されるので、クラスの親としての代わりにPackage(またはより優れた名前のクラスClosing)を使用できますobject。しかし、多重継承がこれをいかに台無しにしているかを私に聞かないでください...
Tobias Kienzler

17

__del__が呼び出される前にインスタンスメンバーが削除される可能性はないと思います。私の推測では、特定のAttributeErrorの理由は別の場所にあると考えられます(おそらく、誤ってself.fileを別の場所に削除した可能性があります)。

ただし、他の人が指摘したように、の使用は避けてください__del__。これの主な理由は、のインスタンス__del__がガベージコレクションされないためです(参照カウントが0に達したときにのみ解放されます)。したがって、インスタンスが循環参照に関与している場合、アプリケーションが実行されている限り、インスタンスはメモリに存在します。(私はこれについてすべて間違っているかもしれませんが、gc docsをもう一度読む必要がありますが、このように機能することを確信しています)。


5
を持つオブジェクトは__del__、他のオブジェクトからの参照カウント__del__がゼロで到達できない場合、ガベージコレクションされる可能性があります。つまり、を使用してオブジェクト間に参照サイクルがある場合__del__、それらのオブジェクトは収集されません。ただし、それ以外の場合は期待どおりに解決する必要があります。
コリン2013年

「Python 3.4以降、__ del __()メソッドは参照サイクルのガベージコレクションを妨げなくなり、インタープリターのシャットダウン中にモジュールグローバルが強制的にNoneになりません。したがって、このコードはCPythonで問題なく動作するはずです。」- docs.python.org/3.6/library/...
トマシュGandor

14

より良い代替案はweakref.finalizeを使用することですファイナライザオブジェクトおよびファイナライザと__del __()メソッドの比較の例を参照してください。


1
これを今日使用すると、他のソリューションよりも完璧に機能します。シリアルポートを開くマルチプロセッシングベースのコミュニケータークラスがstop()あり、ポートとjoin()プロセスを閉じるメソッドがあります。ただし、プログラムが予期せstop()ず終了した場合は呼び出されません-私はファイナライザーで解決しました。しかし、いずれにしても_finalizer.detach()、stopメソッドを呼び出して、2回呼び出されないようにします(手動で、後でファイナライザによって再度呼び出されます)。
Bojan P.

3
IMO、これは本当に最良の答えです。ガベージコレクションでのクリーンアップの可能性と、出口でのクリーンアップの可能性を組み合わせています。注意点は、Python 2.7にはweakref.finalizeがないことです。
hlongmore

12

__init__示されているよりも多くのコードがある場合、問題が発生する可能性があると思いますか?

__del____init__正しく実行されなかったり、例外がスローされたりした場合でも呼び出されます。

ソース


2
ありそうですね。使用時にこの問題を回避する最善の方法__del__は、クラスレベルですべてのメンバーを明示的に宣言し、__init__失敗した場合でも常に存在するようにすることです。与えられた例では、files = ()は、動作しますが、ほとんどの場合、割り当てるだけNoneです。どちらの場合も、実際の値をに割り当てる必要があります__init__
セーレンLøvborg

11

最小限の作業スケルトンは次のとおりです。

class SkeletonFixture:

    def __init__(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def method(self):
        pass


with SkeletonFixture() as fixture:
    fixture.method()

重要:自分を返す


あなたが私のようなもので、return selfクリントミラーの正解の)部分を見落としている場合、あなたはこのナンセンスを凝視しているでしょう。

Traceback (most recent call last):
  File "tests/simplestpossible.py", line 17, in <module>                                                                                                                                                          
    fixture.method()                                                                                                                                                                                              
AttributeError: 'NoneType' object has no attribute 'method'

それが次の人に役立つことを願っています。


8

デストラクタをtry / exceptステートメントでラップするだけで、グローバルがすでに破棄されている場合でも例外はスローされません。

編集する

これを試して:

from weakref import proxy

class MyList(list): pass

class Package:
    def __init__(self):
        self.__del__.im_func.files = MyList([1,2,3,4])
        self.files = proxy(self.__del__.im_func.files)

    def __del__(self):
        print self.__del__.im_func.files

これは、呼び出し時に存在することが保証されているファイルリストをdel関数に詰め込みます。weakrefプロキシは、Pythonまたは自分自身が何らかの方法でself.files変数を削除するのを防ぐためのものです(削除された場合、元のファイルリストには影響しません)。変数への参照がまだ多くても削除されない場合は、プロキシカプセル化を削除できます。


2
問題は、メンバーのデータがなくなった場合、私には遅すぎるということです。そのデータが必要です。上記の私のコードを参照してください。どのファイルを削除するかを知るには、ファイル名が必要です。私はコードを簡略化しましたが、自分でクリーンアップする必要のある他のデータがあります(つまり、インタープリターはクリーンアップの方法を知りません)。
ヴィルヘルムテル2009年

4

これを行う慣用的な方法は、close()メソッド(または同様の)を提供し、それを明示的に呼び出すことです。


20
これは以前使用した方法ですが、他の問題に遭遇しました。例外が他のライブラリによってあちこちにスローされるので、エラーが発生した場合の混乱を解消するためにPythonの助けが必要です。特に、デストラクタを呼び出すにはPythonが必要です。そうしないと、コードがすぐに管理できなくなり、.close()への呼び出しが存在するはずの出口ポイントを確実に忘れてしまいます。
ヴィルヘルムテル2009年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.