Python:哪些类型支持弱引用?

时间:2018-08-24 20:41:03

标签: python weak-references python-internals

代码:

from weakref import WeakSet
se = WeakSet()
se.add(1)

输出:

TypeError: cannot create weak reference to 'int' object

Doc

  

诸如list和dict之类的几种内置类型不直接支持弱引用,但可以通过子类添加支持:

     

...

     

其他内置类型(例如tuple和int)即使在子类化时也不支持弱引用(这是实现细节,并且在各种Python实现中可能有所不同。)

这不足以解释:

  • 为什么某些内置类型不支持弱引用?

  • 那些支持弱引用的类型到底是什么?


添加一些想法:

对于上面的示例,您可以将int包装在用户定义的包装器类中,并且该包装器类支持弱引用(熟悉Java的人会回忆起intInteger):< / p>

from weakref import WeakSet
se = WeakSet()

class Integer:
    def __init__(self, n=0):
        self.n = n

i = 1
I = Integer(1)

se.add(i)   # fail
se.add(I)   # ok

我不确定Python为什么不为常用的内置类型(intstr等)提供自动包装功能,而是简单地说它们不支持weak参考。这可能是由于性能问题引起的,但是无法弱引用这些内置类型会大大降低其使用率。

2 个答案:

答案 0 :(得分:7)

首先:这都是CPython特有的。弱引用在不同的Python实现上的工作方式有所不同。

大多数内置类型不支持弱引用,因为Python的弱引用机制为支持弱引用的每个对象增加了一些开销,并且Python开发人员团队决定他们不希望大多数内置类型承担该开销。 。这种开销表现出来的最简单的方法是,任何具有弱引用支持的对象都需要一个空间来为弱引用管理提供额外的指针,并且大多数内置对象都不为该指针保留空间。

尝试在参考支持薄弱的情况下编译所有类型的完整列表与尝试对所有红发人类的完整列表进行编译一样有成果。如果要确定某个类型是否具有弱引用支持,则可以检查其__weakrefoffset__,对于弱引用支持的类型,该值不为零:

>>> int.__weakrefoffset__
0
>>> type.__weakrefoffset__
368
>>> tuple.__weakrefoffset__
0
>>> class Foo(object):
...     pass
... 
>>> class Bar(tuple):
...     pass
... 
>>> Foo.__weakrefoffset__
24
>>> Bar.__weakrefoffset__
0

类型的__weakrefoffset__是从实例开始到弱引用指针的字节偏移量,如果实例没有弱引用指针,则为0。它在C级别上corresponds到类型struct tp_weaklistoffset。截至撰写本文时,__weakrefoffset__尚未完全记录,但tp_weaklistoffset is documented尚未公开,因为在C中实现扩展类型的人们需要了解它。

答案 1 :(得分:1)

user’s excellent answer没有涉及两件事。


首先,在2.1版中将weakref添加到了Python中。

对于2.1之后添加的所有内容(包括objecttype),除非有充分的理由不这样做,否则默认为添加weakref支持。

但是对于所有已经存在的东西,特别是像int这样的很小的东西,再增加4个字节(大多数Python实现当时是32位的,所以我们仅将指针称为4个字节)可能会引起明显的变化。已针对1.6 / 2.0或更早版本编写的所有Python代码的性能下降。因此,向这些类型添加weakref支持的条件更高。


第二,Python允许实现合并它可以证明是不可变的内置类型的值,对于其中一些内置类型,CPython可以利用这一点。例如(细节因版本而异,因此仅作为示例):

  • 从-5到255的整数,空字符串,单字符可打印ASCII字符串,空字节,单字节字节和空元组会在启动时创建单例实例,并且大多数尝试构造等于新值的尝试对这些单例之一进行访问,而是获得对单例的引用。
  • 许多字符串都缓存在一个字符串内部表中,许多尝试构造一个与内部字符串具有相同值的字符串的尝试是获得对现有字符串的引用。
  • 在单个编译单元中,编译器会将等于int,字符串,int和string的元组等的两个独立常量合并为对同一常量的两个引用。

因此,对这些类型的弱引用不会像您最初想象的那样有用。许多值永远都不会消失,因为它们是对单例或模块常量或内部字符串的引用。即使是那些不朽的东西,您对它们的引用也可能比您预期的要多。

当然,在某些情况下,weakrefs还是有用的。如果我计算出十亿个大整数,则其中大多数不会是不朽或共享的。但是,这意味着它们对于这些类型很少使用,这在权衡使每个int增大4个字节的权衡时必须成为一个因素,因此可以通过以相对不常见的方式安全释放它们来节省内存案例。