2

私が持っているユーティリティ モジュールにクリーンアップ ルーチンを実装しようとしています。問題の解決策を探し回った結果、最終的にweakrefコールバックを使用してクリーンアップを行うことにしました。ただし、同じモジュール内からオブジェクトへの強い参照が原因で、期待どおりに機能しないことが懸念されます。説明する:

foo_lib.py

class Foo(object):

    _refs = {}

    def __init__(self, x):

        self.x = x
        self._weak_self = weakref.ref(self, Foo._clean)
        Foo._refs[self._weak_self] = x

    @classmethod
    def _clean(cls, ref):

        print 'cleaned %s' % cls._refs[ref]

foo = Foo()

次に、他のクラスが を参照しfoo_lib.fooます。1.5.1 の古いドキュメントを見つけましたが、これは私の懸念事項 ( http://www.python.org/doc/essays/cleanup/ ) を参照しているようなものですが、完全に快適にfooなるものは何もなく、次のような方法でリリースされる予定です。コールバックは確実にトリガーされます。この質問を解決してくれるドキュメントを教えてくれる人はいますか?

4

2 に答える 2

1

Python モジュールは終了時にクリーンアップされ、おそらくすべての__del__メソッド が呼び出されます。

__del__()インタープリターが終了したときにまだ存在しているオブジェクトに対してメソッドが呼び出されることは保証されていません。

アンダースコアで始まる名前は最初にクリアされます:

バージョン 1.5 以降、Python は、名前が単一のアンダースコアで始まるグローバルが、他のグローバルが削除される前にモジュールから削除されることを保証します。__del__()そのようなグローバルへの他の参照が存在しない場合、これは、メソッドが呼び出されたときにインポートされたモジュールがまだ使用可能であることを保証するのに役立ちます。

__del__弱参照コールバックは、メソッドと同じメカニズムに依存しています。C の解放関数 ( type->tp_dealloc)。

fooインスタンスはFoo._cleanクラス メソッドへの参照を保持しますが、グローバルFoo既にクリアされている可能性があります ( NoneCPython で割り当てられています)。コールバックが登録されると、メソッドは決して参照されないため、メソッドは安全である必要があります。Foo

于 2013-08-12T21:42:19.737 に答える
1

ここで行うべき正しいことは、シャットダウンに頼るのではなく、ある時点で明示的に強い参照を解放することです。

特に、モジュールがリリースされた場合、そのグローバルはリリースされます... しかし、モジュールがリリースされることはどこにも文書化されていないようです。そのため、シャットダウン時にオブジェクトへの参照が残っている可能性があります。そして、Martijn Pieters が指摘したように:

__del__()インタープリターが終了したときにまだ存在しているオブジェクトに対してメソッドが呼び出されることは保証されていません。

ただし、インタープリターが終了する少し前に、オブジェクトへの (弱い) 参照がないことを確認できれば、クリーンアップの実行を保証できます。

ハンドラーを使用atexitして自分で明示的にクリーンアップできますが、メインモジュールの最後に落ちる前 (または を呼び出すsys.exitか、最後の非デーモンスレッドを終了するなど) に明示的に行うことができます。最も簡単な方法は、多くの場合、関数全体を取得して、または/mainでラップすることです。withtryfinally

または、もっと簡単に言うと、クリーンアップ コードを__del__メソッドや weakref コールバックに入れようとしないでください。withクリーンアップ コード自体をまたはfinallyまたはに入れるだけatexitです。


別の回答へのコメント:

私が実際にやろうとしているのは、通常はタイマーによって開かれているサブプロセスを閉じることですが、プログラムが終了するときに核にする必要があります。これを実行してデーモンサブプロセスを開始し、他のプロセスを個別に監視および強制終了する唯一の本当に「信頼できる」方法はありますか?

この種のことを行う通常の方法は、タイマーを外部から信号を送ることができるものに置き換えることです。アプリのアーキテクチャと使用しているタイマーの種類を知らなくても (たとえば、reactor がタイマーを開始するシングル スレッドの非同期サーバーと、OS タイマー メッセージがタイマーを開始するシングル スレッドの非同期 GUI アプリと、マルチ- タイマーがsleep間隔の間の単なるスレッドであるスレッド化されたアプリ vs. …)、より具体的に説明するのは困難です。

その間、サブプロセスを処理するためのより簡単な方法があるかどうかを確認することもできます。たとえば、明示的なプロセス グループを使用して、プロセスの代わりにプロセス グループを強制終了するとします (Windows と Unix の両方で、すべての子プロセスが強制終了されますが、詳細は大きく異なります)。あるいは、サブプロセスにパイプを渡して、パイプのもう一方の端がダウンしたときにサブプロセスを終了させますか?


ドキュメントは、残りの参照が削除されたとしても、それらが削除される順序についても保証していないことに注意してください。実際、CPython を使用している場合、Py_Finalize具体的には「ランダムな順序で行われる」と述べています。

ソースが面白い。明らかに明示的にランダム化されているわけではなく、完全に恣意的というわけでもありません。最初に何もなくなるまで GC 収集を実行し、次に GC 自体をファイナライズし、PyImport_Cleanup(基本的にはただのsys.modules.clear()) を実行し、次に別の収集がコメント アウトされ (理由についていくつかの議論があります)、最後に_PyImport_Fini(定義されている) を実行します。 「内部使用のみ」としてのみ)。

ただし、これは、モジュールが実際にオブジェクトへの唯一の (弱でない) 参照を保持していて、モジュール自体を含む壊れないサイクルがないと仮定すると、モジュールはシャットダウン時にクリーンアップされ、オブジェクトへの最後の参照により、オブジェクトもクリーンアップされます。(もちろん、ビルトイン、拡張モジュール、およびこの時点でまだ存在するものを直接参照しているもの以外は当てにできません…しかし、上記のコードは問題ないはずfooですFoo。他の非ビルトインに依存しないでください。)

これは CPython 固有であり、実際には CPython 3.3 固有であることに注意してください。確実にするために、バージョンに関連する同等のソースを読みたいと思うでしょう。繰り返しになりますが、ドキュメントには「ランダムな順序で」削除されることが明示されているため、実装固有の動作に依存したくない場合は、それを期待する必要があります。


もちろん、クリーンアップ コードが呼び出される保証はまだありません。たとえば、未処理のシグナル (Unix の場合) または構造化された例外 (Windows の場合) は、何もクリーンアップする機会を与えずにインタープリターを強制終了します。そのためのハンドラーを作成したとしても、誰かが常に電源コードを引っ張ることができます。したがって、完全に堅牢な設計が必要な場合は、いつでもクリーンアップせずに中断できる必要があります (ジャーナリング、アトミック ファイル操作の使用、明示的な承認を伴うプロトコルなどによって)。

于 2013-08-12T22:03:51.700 に答える