考虑这个代码片段:
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
(a non-data descriptor ) 是我正在实现的 API 的一部分。 Example
类是如何使用描述符的示例。
为什么描述符在类本身内部存储对实例的引用?基本上用于缓存目的。 Descriptor
需要与实现者签订此契约(Contract):它将在任何类中使用,假设
- 该类有一个没有参数的构造函数,它提供了一个“匿名实例”(我的定义)
- 该类具有一些特定于行为的方法(此处为
something
)。 - 该类的实例可以在未定义的时间内保持事件状态。
它不假设任何关于:
- 构造一个对象需要多长时间
- 该类是否实现了del或其他魔术方法
- 类(class)预计能活多久
此外,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__
.
最佳答案
对于您的示例,为什么不将 _my_instance
存储在描述符类的字典中,而不是存储在保存描述符的类上?您可以在该字典中使用weakref或WeakValueDictionary,这样当对象消失时,字典将丢失其引用,并且描述符将在下次访问时创建一个新的引用。
编辑:我认为您对在实例存在时收集类的可能性存在误解。 Python 中的方法存储在类中,而不是实例中(除非有特殊技巧)。如果您有一个 Class
类的对象 obj
,并且您允许在 obj
仍然存在时对 Class
进行垃圾回收,然后在对象上调用方法 obj.meth() 将会失败,因为该方法会与类一起消失。这就是为什么你唯一的选择是削弱你的 class->obj 引用;即使您可以使对象弱引用它们的类,如果弱点“生效”(即,如果在实例仍然存在时收集类),它所要做的就是破坏类。
关于python - 垃圾收集一个类及其实例的引用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15530290/