Pythonで確実にクリーンアップする方法は?
-
20-08-2019 - |
質問
いくつかのctypesバインディングがあり、各body.Newに対してbody.Freeを呼び出す必要があります。私がバインドしているライブラリには、残りのコードから隔離された割り当てルーチンがなく(そこからどこでも呼び出すことができます)、循環参照を作成するために必要ないくつかの便利な機能を使用します。
デストラクタをオブジェクトにフックする信頼できる方法を見つければ解決すると思います。 (weakrefは、データがドロップされる直前に コールバックを提供してくれると助かります。
したがって、velocity_funcを入れると、このコードは明らかに失敗します:
class Body(object):
def __init__(self, mass, inertia):
self._body = body.New(mass, inertia)
def __del__(self):
print '__del__ %r' % self
if body:
body.Free(self._body)
...
def set_velocity_func(self, func):
self._body.contents.velocity_func = ctypes_wrapping(func)
また、weakrefsを介して解決しようとしましたが、それらは事態が悪化しているように見えますが、大部分は予測不可能です。
velocity_funcを入れなくても、少なくともこれを行うとサイクルが表示されます:
class Toy(object):
def __init__(self, body):
self.body.owner = self
...
def collision(a, b, contacts):
whatever(a.body.owner)
構造が共有ライブラリによって割り当て/解放されている場合でも、ガベージコレクションを確実に取得する方法は?
詳細に興味がある場合はリポジトリがあります: http://bitbucket.org/cheery/ ctypes-chipmunk /
解決 3
weakrefsが壊れていない場合、これはうまくいくと思います:
from weakref import ref
pointers = set()
class Pointer(object):
def __init__(self, cfun, ptr):
pointers.add(self)
self.ref = ref(ptr, self.cleanup)
self.data = cast(ptr, c_void_p).value # python cast it so smart, but it can't be smarter than this.
self.cfun = cfun
def cleanup(self, obj):
print 'cleanup 0x%x' % self.data
self.cfun(self.data)
pointers.remove(self)
def cleanup(cfun, ptr):
Pointer(cfun, ptr)
まだ試しています。重要な点は、整数を除いて、ポインターには外部ポインターへの強力な参照がないことです。 ctypesがバインディングで解放すべきメモリを解放しない場合、これは機能するはずです。ええ、それは基本的にハックですが、私が試みてきた以前のものよりもうまくいくと思います。
編集:試してみたところ、コードを少し微調整しても機能しているようです。驚くべきことは、すべての構造から del を取り出しても、まだ失敗しているように見えることです。興味深いが、イライラする。
どちらも機能せず、奇妙な可能性があるため、場所にある循環参照を破棄することができましたが、問題は解決しません。
編集:まあ..weakrefsは結局壊れていました!明示的に強制する以外には、Pythonの信頼できるクリーンアップの解決策はない可能性があります。
他のヒント
やりたいこと、つまり物を割り当てるオブジェクトを作成し、オブジェクトが使用されなくなったら自動的に割り当てを解除することは、残念ながらPythonではほとんど不可能です。 del ステートメントの呼び出しは保証されていないため、それに頼ることはできません。
Pythonの標準的な方法は単純です:
try:
allocate()
dostuff()
finally:
cleanup()
または2.5以降では、コンテキストマネージャーを作成し、withステートメントを使用することもできます。これは、より適切な方法です。
ただし、これらの両方は、主にコードスニペットの先頭で割り当て/ロックする場合に使用します。プログラムの実行全体に物を割り当てたい場合、起動時に、プログラムのメインコードが実行される前にリソースを割り当て、その後で割り当てを解除する必要があります。ここで説明されていない状況が1つあります。それは、多くのリソースを動的に割り当ておよび割り当て解除し、コードの多くの場所でそれらを使用する場合です。たとえば、メモリバッファなどのプールが必要な場合。しかし、これらのケースのほとんどはメモリのためであり、Pythonがあなたのために処理するので、それらについて気にする必要はありません。もちろん、メモリではないものの動的なプール割り当てが必要な場合があります。そして、あなたの例で試みる割り当て解除のタイプが必要であり、Pythonで行うのが難しい。
CPythonでは、参照カウントがゼロに達すると常に呼び出されるため、__del__
はオブジェクトの信頼できるデストラクタです(注:アイテムの循環参照のような場合があります。 <=>メソッドの定義-参照カウントがゼロに達することはありませんが、それは別の問題です。
更新
コメントから、私は問題がオブジェクトの破壊の順序に関係していることを理解しています。 body はグローバルオブジェクトであり、他のすべてのオブジェクトの前に破壊されているため、もはや利用できません。
実際、グローバルオブジェクトを使用するのは良くありません。このような問題だけでなく、メンテナンスも原因です。
このようなクラスでクラスを変更します
class Body(object):
def __init__(self, mass, inertia):
self._bodyref = body
self._body = body.New(mass, inertia)
def __del__(self):
print '__del__ %r' % self
if body:
body.Free(self._body)
...
def set_velocity_func(self, func):
self._body.contents.velocity_func = ctypes_wrapping(func)
注意事項:
- この変更は、グローバルな body オブジェクトへの参照を追加するだけです。したがって、少なくともそのクラスから派生したすべてのオブジェクトと同じくらい存続します。
- それでも、グローバルオブジェクトを使用するのは、ユニットのテストとメンテナンスのために良くありません。正しい<!> quot; body <!> quot;を設定するオブジェクトのファクトリーを持つ方が良いでしょう。クラスに追加します。単体テストの場合は、モックオブジェクトを簡単に配置します。しかし、それは本当にあなた次第であり、このプロジェクトであなたがどれだけの努力が理にかなっていると思います。