web-dev-qa-db-ja.com

Pythonオブジェクトを正しくクリーンアップする方法

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

    # ...

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

上記の__del__(self)はAttributeError例外で失敗します。私は理解しています Pythonは保証しません__del__()が呼び出されたときの "グローバル変数"(この文脈ではメンバーデータ?)の存在。これが事実であり、これが例外の理由である場合、どのように私はオブジェクトがきちんと破壊することを確かめますか。

410
wilhelmtell

クリーンアップが必要なリソースを管理するには、Pythonのwithステートメントを使用することをお勧めします。明示的な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節を使用せずに、コンストラクタを使用してPackageをインスタンス化することもできます。あなたはそれが起こりたくないのです。 __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
544
Clint Miller

標準的な方法は 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)

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

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...
30
ostrokach

Clint's answer の付録として、 contextlib.contextmanager を使用してPackageResourceを単純化できます。

@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 を使用します。この場合、with contextlib.closing(Package(...))を介して未修正の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
26
Tobias Kienzler

__del__が呼ばれる前にインスタンスメンバーが削除されることは可能ではないと思います。私の考えでは、あなたの特定のAttributeErrorの理由はどこかにあるのでしょう(たぶんあなたは間違って他の場所にself.fileを削除します)。

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

16
Virgil Dupras

表示されている以上のコードがある場合、問題は__init__にある可能性がありますか。

__del__が正しく実行されなかった場合や例外をスローした場合でも__init__が呼び出されます。

出典

11
n3o59hf

デストラクタを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変数をなんらかの方法で削除するのを防ぐためのものです(削除されても元のファイルリストには影響しません)。変数への参照がまだあるにもかかわらず、これが削除されていない場合は、プロキシカプセル化を削除できます。

8
Unknown

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

7
SCGH

これが最小限の作業用のスケルトンです。

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

重要: 自分自身を返す


あなたが私のようで、( Clint Millerの正解 の)return selfの部分を見落としているなら、あなたはこのナンセンスを見つめているでしょう:

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

私はこれに半日を費やした。それが次の人に役立つことを願っています。

5
user2394284

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

5