【发布时间】:2013-03-09 22:41:27
【问题描述】:
考虑这段代码sn-p:
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(non-data descriptor)是我正在实现的 API 的一部分。 Example 类是如何使用描述符的示例。
为什么描述符会在类本身中存储对实例的引用?基本上用于缓存目的。 Descriptor 要求与实现者签订此合同:它将在任何类中使用,假设为
- 该类有一个没有参数的构造函数,它给出了一个“匿名实例”(我的定义)
- 该类具有一些特定于行为的方法(此处为
something)。 - 类的实例可以在未定义的时间内保持活动状态。
它不假设任何关于:
- 构造一个对象需要多长时间
- 类是否实现del或其他魔术方法
- 预计班级会持续多久
此外,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__ 的类。
【问题讨论】:
-
让什么工作,究竟是什么?你想达到什么目的?完成实例后,为什么不直接删除属性
Foo.bar? -
假设引用是由外部代码添加的,它无法控制实例的生命周期。我在编写可以应用于任何类的描述符时首先偶然发现了这个问题,包括
Foo。 -
所以,如果我理解的话,在您的真实案例中,您有一个本地类定义,它在某些时候超出了范围,需要被释放。但在其生命周期中,一些外部代码会为其添加属性。你能解释一下如何以及为什么?
-
@shx2 我添加了一个几乎真实的代码示例(在第一个水平中断之后)并进行了解释。在我现实生活中的问题中,我限制了合同以避免这种情况,但我留下了我的问题。