考虑这个代码片段:
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 的一部分。Example
class 是如何使用描述符的示例。
为什么描述符会在类本身中存储对实例的引用?基本上用于缓存目的。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__
。