【问题标题】:Wrong result when comparing ref and WeakMethod in Python?在 Python 中比较 ref 和 WeakMethod 时结果错误?
【发布时间】:2023-02-23 02:58:51
【问题描述】:

我正在使用 set 来保存对可调用对象的弱引用。这些可以是函数、可调用实例(即使用 __call__ 方法)和绑定方法。在 docs 之后,我将 weakref.WeakMethod 用于绑定方法,将 weakref.ref 用于其他可调用对象。

我面临的问题最好用一个例子来解释:

from weakref import ref, WeakMethod

class Callbacks:
    def method(self, *args, **kwargs):
        print('method()')

    def __call__(self, *args, **kwargs):
        print('__call__()')


cb = Callbacks()
listeners = set()

listeners.add(ref(cb))
print(f'#listeners: expected = 1, actual = {len(listeners)}')

listeners.add(WeakMethod(cb.method))
print(f'#listeners: expected = 2, actual = {len(listeners)}')

这打印:

#listeners:预期= 1,实际= 1
#listeners:预期= 2,实际= 1

深入挖掘,我确实看到了 WeakMethod(cb.method) == ref(cb),尽管 cb.method != cb。我错过了什么?

【问题讨论】:

  • 无法复制;当我执行python3 -munittest tmp.py时,所有4个测试都通过了(以上是tmp.py的内容)。
  • 鉴于被测代码根本不使用弱引用,除了 weakref 模块本身之外,您是否正在测试任何有用的东西并不清楚。
  • @chepner 改写了问题和动机(希望如此)清楚。
  • 好的,是的,这更清楚了,是的,这对我来说确实很奇怪。该文档仅提到WeakMethod模拟对绑定方法的引用,所以也许它真的只是对对象的弱引用,稍后“重建”对绑定方法的弱引用。 (注意 cb.method 创建了一个新的每次使用method实例;它不仅仅是像 cb 那样引用长寿命对象的表达式。)

标签: python weak-references


【解决方案1】:

首先,确保您知道表达式 cb.method 创建了一个新的每次评估时类method 的实例;它不仅仅是对某个长期存在的对象的引用。

>>> x = cb.method
>>> y = cb.method
>>> x is y
False

其次,WeakMethodref的子类,所以它实际上只是对原始对象的引用,以及有关该方法的附加信息,允许它根据需要重构绑定方法。

WeakMethod 的文档更详细地说明了为什么需要它,而不仅仅是使用 ref(cb.method)

第三,WeakMethod.__hash__ 被设置为等于ref.__hash__,因此散列 WeakMethod 不会使用任何额外信息。就哈希值而言,任何涉及cbWeakMethod都相当于对cb本身的弱引用。

这表明您将不得不调整代码以适应弱引用的行为方式。

【讨论】:

  • 谢谢@chepner。对我来说,这似乎是WeakMethod中的一个怪癖:即使id(x) != id(y)x == yWeakMethod(x) == WeakMethod(y)都是真的,这是有道理的。然而,尽管cb != cb.methodm,我们仍然得到ref(cb) == WeakMethod(cb.method)。由于集合使用 __eq__ 比较对象,因此我的第二次调用不会将可调用对象添加到集合中。显然,我会更改我的代码来解决这个问题。
  • 是的,不知道有没有更深层次的原因为什么WeakMethod.__hash__ 设置为rep.__hash__,而不是进行考虑特定方法的定义。
【解决方案2】:

所以,问题是WeakMethod 的散列和__eq__ 与创建WeakMethod 的同一实例的weakref.ref() 返回的值相同。这意味着尽管是不同的对象,但只要涉及普通集合,两个弱引用都是“同一件事”。 WeakMethod 上的额外数据将在调用时返回绑定方法 - 但 refWeakMethod 对象都是对同一实例的弱引用 - 并将在原始实例开始后立即返回 None没有硬引用了。

这样做的副作用就是你发现的:在集合或字典中,你只能有对实例的弱引用,或对其任何方法的弱引用作为元素或键。

查看 iPython sn-p 波纹管,因为在添加到集合时仅保留同一实例的第一个 (ref, WeakMethod):


In [101]: class A:
     ...:     def b(self):
     ...:         print("b")
     ...: 

In [102]: instance = [A()]  # not assigning directly to the variable, as ipython creates other r
     ...: eferences to it, and it becomes hard to test the weakreferences

In [103]: r1 = weakref.ref(instance[0])

In [104]: r2 = weakref.WeakMethod(instance[0].b)

In [105]: r1().b()
b

In [106]: r2().b()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[106], line 1
----> 1 r2().b()

AttributeError: 'function' object has no attribute 'b'

In [107]: r2()()
b

In [108]: r1 is r2
Out[108]: False

In [109]: hash(r1) == hash(r2), r1 == r2
Out[109]: (True, True)

In [110]: b = set()

In [111]: b.add(r1)

In [112]: b
Out[112]: {<weakref at 0x7fda574df5b0; to 'A' at 0x7fda576d2c10>}

In [113]: b.add(r2)

In [114]: b
Out[114]: {<weakref at 0x7fda574df5b0; to 'A' at 0x7fda576d2c10>}

In [115]: c = set()

In [116]: c.add(r2)

In [117]: c
Out[117]: {<weakref at 0x7fda565233e0; to 'A' at 0x7fda576d2c10>}

In [118]: c.add(r1)

In [119]: c
Out[119]: {<weakref at 0x7fda565233e0; to 'A' at 0x7fda576d2c10>}

In [120]: next(iter(c))()
Out[120]: <bound method A.b of <__main__.A object at 0x7fda576d2c10>>

In [121]: next(iter(c))()()
b

In [122]: next(iter(b))().b()
b


在“现实世界”中,使用refWeakMethods 的级别都比较低,最好使用weakref.WeakSetweakref.WeakKeyDictionary 来保存你的弱引用——它更实用,因为你不必先调用来检索硬引用,然后测试调用的结果是否为 None,然后使用你的对象:使用 WeakSet 允许你在每次需要迭代你持有弱引用的对象时简单地使用你的项目。

【讨论】:

    猜你喜欢
    • 2019-11-19
    • 1970-01-01
    • 1970-01-01
    • 2014-03-20
    • 1970-01-01
    • 2016-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多