Python中的__weakref__究竟是什么?

时间:2016-04-22 07:28:39

标签: python python-3.x python-internals

令人惊讶的是,__weakref__没有明确的文档。解释了弱引用here__weakref__的文档中也很快提到了__slots__。但是我找不到关于__weakref__本身的任何内容。

究竟是什么__weakref__? - 它只是一个作为标志的成员:如果存在,该对象可能被弱引用? - 或者它是一个可以被覆盖/分配以获得所需行为的函数/变量?怎么样?

3 个答案:

答案 0 :(得分:41)

__weakref__只是一个不透明的对象,它引用了对当前对象的所有弱引用。实际上它是weakref(或有时weakproxy)的一个实例,它既是对象的弱引用,也是对该对象的所有弱引用的双向链表的一部分。

它只是一个实现细节,它允许垃圾收集器通知弱引用它已经收集了它的引用,并且不再允许访问它的底层指针。

弱引用不能依赖于检查它引用的对象的引用计数。这是因为该内存可能已被回收,现在正由另一个对象使用。最好的情况是VM会崩溃,最坏的情况是弱引用将允许访问它最初没有引用的对象。这就是为什么垃圾收集器必须通知弱引用它的引用不再有效。

有关此对象的结构和C-API,请参阅weakrefobject.h。实施细节为here

答案 1 :(得分:25)

[编辑1:解释链表性质以及重复使用弱点时]

有趣的是,official documentation在这个主题上有点不具启发性:

  

对于每个实例没有__weakref__变量,定义__slots__的类不支持对其实例的弱引用。如果需要弱引用支持,则将__weakref__添加到__slots__声明中的字符串序列。

关于该主题的type object documentation似乎没有太多帮助:

  

当类型的__slots__声明包含名为__weakref__的插槽时,该插槽将成为该类型实例的弱引用列表头,并且插槽的偏移量存储在类型tp_weaklistoffset

弱引用形成链表。该列表的头部(对象的第一个弱引用)可通过__weakref__获得。尽可能重用weakrefs,因此列表(不是Python列表!)通常为空或包含单个元素。

示例

首次使用weakref.ref()时,会为目标对象创建一个新的弱引用链。此链的头部是新的weakref,并存储在目标对象的__weakref__

>>> import weakref
>>> class A(object): pass
>>> a = A()
>>> b = weakref.ref(a)
>>> c = weakref.ref(b)
>>> print(b is c is a.__weakref__)
True

我们可以看到,b被重复使用。我们可以强制python创建一个新的weakref,例如添加回调参数:

>>> def callback():
>>>   pass
>>> a = A()
>>> b = weakref.ref(a)
>>> c = weakref.ref(b, callback)
>>> print(b is c is a.__weakref__)
False

现在b is a.__weakref__c是链中的第二个引用。 Python代码无法直接访问引用链。我们只看到链的头部元素(b),但不知道链是如何继续的(b - > c)。

所以__weakref__是对象的所有弱引用的内部链表的头部。我找不到任何官方文档,其中__weakref__的这个角色被简明地解释,所以应该不要依赖于这种行为,因为它是一个实现细节。

答案 2 :(得分:16)

__weakref__变量是一个属性,它使对象支持弱引用并保留对对象的弱引用。

python文档解释如下:

  

当对引用的唯一剩余引用是弱引用时,垃圾收集可以自由地销毁引用并将其内存重用于其他内容。

因此,弱引用的职责是为对象提供条件,以便能够被垃圾收集,无论其类型和范围如何。

关于__slots__,我们可以先查看文档,这很好地解释了它:

  

默认情况下,类的实例具有属性存储的字典。这会浪费具有很少实例变量的对象的空间。在创建大量实例时,空间消耗会变得很严重。

     

可以通过在类定义中定义__slots__来覆盖默认值。 __slots__声明采用一系列实例变量,并在每个实例中保留足够的空间来保存每个变量的值。保存空间是因为没有为每个实例创建__dict__

现在,由于使用__slots__您将控制属性的所需存储空间,因此实际上会阻止为每个实例自动创建__dict____weakref__。其中__weakref__是每个对象的必要变量,以便能够处理弱引用。

此外,除了所有这些object.__slots__课程的文档说:

  

可以为此类变量分配字符串,可迭代或具有实例使用的变量名称的字符串序列。 __slots__为声明的变量保留空间,并阻止为每个实例自动创建__dict____weakref__

因此,简而言之,我们可以得出结论__slots__用于手动管理存储分配,因为__weakref__是接受与存储相关的对象的弱引用的许可(因为因此,__slots__将控制__weakref__以及控制__dict__属性。

此外,文档向您展示了使用__slots__制作对象以支持弱引用的方法:

  

对于每个实例没有__weakref__变量,定义__slots__的类不支持对其实例的弱引用。如果需要弱引用支持,则将'__weakref__'添加到__slots__声明中的字符串序列。

这是python 3.X中的一个例子:

>>> class Test:
...     __slots__ = ['a', 'b']
... 
>>> 
>>> import weakref
>>> 
>>> t = Test()
>>> 
>>> r = weakref.ref(t)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot create weak reference to 'Test' object
>>> 
>>> class Test:
...     __slots__ = ['a', 'b', '__weakref__']
... 
>>> t = Test()
>>> r = weakref.ref(t)
>>> 
>>> t.__weakref__
<weakref at 0x7f735bc55d68; to 'Test' at 0x7f735bc51fc8>

但是在python 2.7中,尽管文档与前面提到的文档类似,但是从没有在__weakref__名称中提供__slots__变量的实例创建弱引用并不是&#39;提出TypeError

>>> class Test:
...    __slots__ = ['a', 'b']
... 
>>> t = Test()
>>> 
>>> r = weakref.ref(t)
>>> 
>>> r
<weakref at 0x7fe49f4185d0; to 'instance' at 0x7fe4a3e75f80>