【问题标题】:Python: which types support weak references?Python:哪些类型支持弱引用?
【发布时间】:2019-01-31 08:18:21
【问题描述】:

代码:

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):

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 等)提供自动包装,而是简单地说它们不支持弱引用。这可能是由于性能问题,但无法弱引用这些内置类型大大减少了它的使用。

【问题讨论】:

  • 相关:What exactly is __weakref__ in Python?(可以说是同一个问题,但倒退了)
  • 您对 C API 有任何了解吗?您是否按照链接(紧接在您引用的文档之后)到 Weak Reference Support?如果是这样,答案的第 1 部分非常简单,尽管第 2 部分仍然不是那么简单。如果不是,那就更复杂了,因为它需要解释这些文档中的大部分内容。
  • @Aran-Fey 什么是__weakref__ 不是什么是weakref?这个问题没有提到内置类型。
  • @abarnert 我点击了链接,但它没有告诉你为什么内置类型不支持弱引用。您可以从该部分得出的唯一结论可能是*内置类型太简单,无法支持弱引用,但这还不够表达。
  • @Cyker 这就是为什么我说如果您阅读了第 1 部分的答案会非常简单,而不是说阅读它会给您答案。 (它告诉你如果内置类型想要成为弱引用,它们必须做什么,但你仍然需要解释他们为什么不这样做。而且,对于第 2 部分,唯一的答案是遍历每个内置类型,无论是在源代码中还是在 REPL 中,以检查哪些是这样做的。)

标签: python weak-references python-internals


【解决方案1】:

首先:这都是 CPython 特有的。 Weakrefs 在不同的 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。它 corresponds 到 C 级别的类型结构的 tp_weaklistoffset。在撰写本文时,__weakrefoffset__ 完全没有文档记录,但 tp_weaklistoffset is documented,因为在 C 中实现扩展类型的人需要了解它。

【讨论】:

  • 还有一个历史事实是大多数内置类型早于弱引用,因此添加弱引用支持将是大量现有 Python 代码的性能回归。但除此之外,我认为这涵盖了所有内容。
  • 感谢您提供__weakrefoffset__。从您发布的代码中,我们可以肯定地说所有直接从object 继承的用户定义类(可能是递归的,但不涉及链中的另一个内置类)都支持弱引用?
  • @Cyker:不,因为__slots__ 使事情复杂化。
  • @user2357112 你能举个例子吗?我认为大多数开发人员在定义自定义类时不会打扰__slots__。如果是这样,并且他们通过class Foo(object): ... 定义子类,那么Foo 似乎总是支持CPython 中的弱引用。在不同的 Python 实现中不确定这一点。
  • @Cyker 任何需要优化内存使用的开发人员肯定会考虑在他们的类型上使用__slots__,而任何不需要优化内存使用的开发人员可能不是在寻找弱引用,所以我认为用户的观点非常相关。同时,here's a trivial example.
【解决方案2】:

user’s excellent answer 没有涵盖两件事。


首先,weakref 在 Python 2.1 版本中被添加。

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

但是对于已经存在的所有内容,尤其是像 int 这样非常小的内容,再增加 4 个字节(当时大多数 Python 实现都是 32 位的,所以我们只调用 4 个字节的指针)可能会导致明显的性能回归对于为 1.6/2.0 或更早版本编写的所有 Python 代码。因此,为这些类型添加弱引用支持需要通过更高的门槛。


其次,Python 允许实现合并它可以证明是不可变的内置类型的值,对于其中一些内置类型,CPython 充分利用了这一点。例如(具体情况因版本而异,仅以此为例):

  • 从 -5 到 255 的整数、空字符串、可打印的单字符 ASCII 字符串、空字节、单字节字节和空元组在启动时创建单例实例,并且大多数尝试构造一个等于的新值改为其中一个单例获取对单例的引用。
  • 许多字符串都缓存在字符串实习表中,并且许多尝试构造与实习字符串具有相同值的字符串而不是获取对现有字符串的引用。
  • 在单个编译单元中,编译器会将两个独立的常量合并为相同常量的两个引用,它们是相等的整数、字符串、整数元组和字符串等。

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

当然,在某些情况下,weakrefs 还是有用的。如果我计算十亿个大整数,其中大部分都不会是不朽的,也不会共享。但这意味着它们不太经常对这些类型有用,这必须是权衡使每个 int 4 字节更大的权衡时的一个因素,这样您就可以通过在一些相对不常见的情况下安全地释放它们来节省内存案例。

【讨论】:

    猜你喜欢
    • 2018-12-14
    • 2011-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多