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

    # ...

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

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

4

10 に答える 10

717

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上記の例では、句を使用せずにコンストラクターを使用して Package をインスタンス化することができます。あなたはそれが起こることを望んでいません。__enter__メソッドとメソッドを定義する PackageResource クラスを作成することで、これを修正できます__exit__。次に、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
于 2009-05-14T19:39:56.287 に答える
84

標準的な方法は、次を使用すること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...
于 2017-01-13T03:48:57.130 に答える
37

Clint's answer の付録として、次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
于 2015-05-20T12:11:21.680 に答える
18

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

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

于 2009-05-14T19:51:55.367 に答える
15

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

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

ソース

于 2012-11-29T08:22:09.730 に答える
8

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

編集

これを試して:

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

于 2009-05-14T19:08:42.307 に答える
6

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

于 2009-05-14T19:09:24.753 に答える