如何在python中可靠地清理?

时间:2009-06-25 13:53:09

标签: python ctypes cyclic-reference

我有一些ctypes绑定,并且每个body.New我应该调用body.Free。我绑定的库没有与其余代码隔离的分配例程(可以在任何地方调用它们),并使用我需要的几个有用的功能来进行循环引用。

我认为如果我找到一种将析构函数挂钩到对象的可靠方法,它就会解决。 (如果他们在数据被删除之前只是给我回调,那么weakrefs会有所帮助。

很明显,当我输入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)

我也试图通过弱点来解决它,那些事情似乎变得更糟,只是在很大程度上更难以预测。

即使我没有加入velocity_func,至少在我这样做时会出现周期:

class Toy(object):
    def __init__(self, body):
        self.body.owner = self

...

def collision(a, b, contacts):
    whatever(a.body.owner)

那么如何确保Structures将被垃圾收集,即使它们是由共享库分配/释放的?

如果您对更多详细信息感兴趣,可以使用存储库:http://bitbucket.org/cheery/ctypes-chipmunk/

3 个答案:

答案 0 :(得分:3)

您想要做的是创建一个分配内容然后在对象不再使用时自动解除分配的对象,遗憾的是,在Python中几乎不可能。不保证会调用 del 语句,因此您不能依赖它。

Python中的标准方法很简单:

try:
    allocate()
    dostuff()
finally:
    cleanup()

或者从2.5开始,你也可以创建上下文管理器并使用with语句,这是一种更简洁的方法。

但这两者主要用于在代码片段的开头分配/锁定时。如果要为程序的整个运行分配事物,则需要在启动时,在程序的主代码运行之前分配资源,然后再解除分配。这里没有涉及一种情况,那就是当你想动态分配和释放许多资源并在代码中的许多地方使用它们时。例如,您需要一个内存缓冲池或类似的池。但是大多数情况都是针对内存的,Python会为你处理,所以你不必为此烦恼。当然,您希望对非内存的事物进行动态池分配,然后您需要在示例中尝试的解除分配类型,并且 很难处理Python

答案 1 :(得分:0)

如果没有破坏弱点,我想这可行:

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中可能没有可靠的清理解决方案,除了强制它是明确的。

答案 2 :(得分:0)

在CPython中,__del__ 对象的可靠析构函数,因为当引用计数达到零时将始终调用它(注意:可能存在一些情况 - 如循环引用定义了__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)

几点说明:

  1. 此更改仅添加对全局 body 对象的引用,因此至少与从该类派生的所有对象一样多。
  2. 然而,由于单元测试和维护,使用全局对象并不好;更好的是为对象设置一个工厂,这将为类设置正确的“主体”,并且在单元测试的情况下将很容易放置一个模拟对象。但这完全取决于你,你认为在这个项目中有多少努力是有意义的。