1

考虑这个代码片段:

import gc
from weakref import ref


def leak_class(create_ref):
    class Foo(object):
        # make cycle non-garbage collectable
        def __del__(self):
            pass

    if create_ref:
        # create a strong reference cycle
        Foo.bar = Foo()
    return ref(Foo)


# without reference cycle
r = leak_class(False)
gc.collect()
print r() # prints None

# with reference cycle
r = leak_class(True)
gc.collect()
print r() # prints <class '__main__.Foo'>

它创建了一个无法收集的引用循环,因为引用的实例有一个__del__ 方法。循环在这里创建:

# create a strong reference cycle
Foo.bar = Foo()

这只是一个概念证明,可以通过一些外部代码、描述符或任何东西添加引用。如果您不清楚,请记住每个对象都包含对其类的引用:

  +-------------+             +--------------------+
  |             |  Foo.bar    |                    |
  | Foo (class) +------------>| foo (Foo instance) |
  |             |             |                    |
  +-------------+             +----------+---------+
        ^                                |
        |         foo.__class__          |
        +--------------------------------+

如果我可以保证Foo.bar只能从 访问Foo,那么循环就没有必要了,因为理论上该实例只能持有对其类的弱引用。

你能想出一种实用的方法来使这项工作不泄漏吗?


正如一些人问为什么外部代码会修改一个类但不能控制它的生命周期,考虑这个例子,类似于我正在努力的现实生活中的例子:

class Descriptor(object):
    def __get__(self, obj, kls=None):
        if obj is None:
            try:
                obj = kls._my_instance
            except AttributeError:
                obj = kls()
                kls._my_instance = obj
        return obj.something()


# usage example #
class Example(object):
    foo = Descriptor()

    def something(self):
        return 100


print Example.foo

仅在此代码中Descriptor非数据描述符)是我正在实现的 API 的一部分。Exampleclass 是如何使用描述符的示例。

为什么描述符会在类本身中存储对实例的引用?基本上用于缓存目的。Descriptor需要与实现者签订此合同:它将在任何类中使用,假设

  1. 该类有一个没有参数的构造函数,它给出了一个“匿名实例”(我的定义)
  2. 该类具有一些特定于行为的方法(something此处)。
  3. 类的实例可以在未定义的时间内保持活动状态。

它不假设任何关于:

  1. 构建一个对象需要多长时间
  2. 该类是否实现了del或其他魔术方法
  3. 预计班级会持续多久

此外,API 旨在避免类实现者的任何额外负载。我本可以将缓存对象的责任转移给实现者,但我想要一个标准的行为。

这个问题实际上有一个简单的解决方案:使默认行为缓存实例(就像在这段代码中所做的那样),但如果实现者必须实现__del__.

当然,如果我们假设必须在调用之间保留类状态,这不会那么简单。


作为一个起点,我正在编写一个“弱对象”,它的实现object只保留了对其类的弱引用:

from weakref import proxy

def make_proxy(strong_kls):
    kls = proxy(strong_kls)
    class WeakObject(object):
        def __getattribute__(self, name):
            try:
                attr = kls.__dict__[name]
            except KeyError:
                raise AttributeError(name)

            try:
                return attr.__get__(self, kls)
            except AttributeError:
                return attr
        def __setattr__(self, name, value):
            # TODO: implement...
            pass
    return WeakObject

Foo.bar = make_proxy(Foo)()

它似乎适用于有限数量的用例,但我必须重新实现整套object方法,而且我不知道如何处理覆盖的类__new__

4

2 回答 2

2

对于您的示例,为什么不将_my_instancedict 存储在描述符类上,而不是存储在包含描述符的类中?您可以在该dict中使用weakref或WeakValueDictionary,这样当对象消失时,该dict将失去其引用,并且描述符将在下一次访问时创建一个新的。

编辑:我认为您对在实例存在时收集类的可能性存在误解。Python 中的方法存储在类上,而不是实例上(除非有特殊技巧)。如果您有一个objclass对象Class,并且允许Class在仍然存在的情况下进行垃圾回收obj,那么在该对象上调用方法obj.meth()将失败,因为该方法将与该类一起消失。这就是为什么你唯一的选择是削弱你的 class->obj 引用;即使您可以使对象弱引用它们的类,它所要做的就是在弱点“生效”时破坏类(即,如果在实例仍然存在时收集类)。

于 2013-03-20T20:58:02.573 回答
1

__del__您面临的问题只是一般 ref-cycle-with-问题的一个特例。

我认为在您的情况下创建循环的方式没有任何异常,也就是说,您应该采用避免一般问题的标准方法。

我认为实现和使用 aweak object很难做到正确,你仍然需要记住在你定义的所有地方都使用它__del__。这听起来不是最好的方法。

相反,您应该尝试以下方法:

  1. 考虑不在__del__课堂上定义(推荐)
  2. 在定义的类中__del__,避免引用循环(通常,可能很难/不可能确保在代码中的任何地方都没有创建循环。在您的情况下,似乎您希望循环存在)
  3. 明确地打破循环,使用del(如果在你的代码中有适当的点来做到这一点)
  4. 扫描gc.garbage列表,并明确打破引用循环(使用del
于 2013-03-20T20:41:29.650 回答