为什么方法没有引用相等性?

20

我遇到了一个bug,当使用is时,我依赖于方法相等。结果发现不是这种情况:

>>> class What:
...     def meth(self):
...         pass

>>> What.meth is What.meth  # This is False in Python 2
True
>>> inst = What()
>>> inst.meth is inst.meth
False

为什么会这样呢?对于普通函数这是有效的:

>>> def func(): pass
>>> func is func
True

4
顺便说一下,这适用于 Python 3。 - poke
3
@poke 不管怎样,What.meth is What.meth 是成立的(返回True),但是在Python 3.8.0中,inst = What(); inst.meth is inst.meth 返回False - user3064538
2个回答

31

每次访问时都会创建方法对象。函数充当描述符,在调用它们的.__get__方法时返回一个方法对象:

>>> What.__dict__['meth']
<function What.meth at 0x10a6f9c80>
>>> What.__dict__['meth'].__get__(What(), What)
<bound method What.meth of <__main__.What object at 0x10a6f7b10>>

如果您使用的是Python 3.8或更高版本,则可以使用==进行相等性测试。在Python 3.8及更高版本中,如果两个方法的.__self__.__func__属性是相同的对象(因此如果它们包装相同的函数,并且绑定到相同的实例上,都通过is进行测试),这两种方法就是相等的。

在3.8之前,方法==的行为根据方法的实现方式不一致 - Python方法和两种C方法类型之一将比较__self__的相等性而不是身份,而另一种C方法类型会通过身份比较__self__。请参阅Python问题1617161

如果您需要测试这些方法是否代表相同的基础函数,请测试它们的__func__属性:

>>> What.meth == What.meth     # functions (or unbound methods in Python 2)
True
>>> What().meth == What.meth   # bound method and function
False
>>> What().meth == What().meth # bound methods with *different* instances
False
>>> What().meth.__func__ == What().meth.__func__ # functions
True

1
如果我使用 ==,我会得到我想要的身份相等行为吗? - Claudiu
我认为方法相等性检查.im_self的身份,而不是相等性。请查看 - Claudiu
@Claudiu:是的,抱歉,它测试im_self是否相同。 - Martijn Pieters
@Claudiu Claudiu,Martijn:你们在评论中是不是指方法相等性检查“im_self”和“im_func”的身份?这就是在编辑中进行更正的重点,对吧? - eyquem
是的,两者都经过身份验证;如果函数相同,则只有它们才是相等的。 - Martijn Pieters
显示剩余3条评论

0
Martijn是正确的,新方法是由.__get__生成的对象,因此它们的地址指针不等同于is评估。请注意,在Python 2.7中使用==将按预期进行评估。
Python2.7
class Test(object):
    def tmethod(self):
        pass

>>> Test.meth is Test.meth
False
>>> Test.meth == Test.meth
True

>>> t = Test()
>>> t.meth is t.meth
False
>>> t.meth == t.meth
True

请注意,从实例引用的方法与从类引用的方法并不相等,因为从实例传递的方法具有自我引用。
>>> t = Test()
>>> t.meth is Test.meth
False
>>> t.meth == Test.meth
False

在Python 3.3中,方法的is操作符更经常地表现得和==一样,所以你可以在这个例子中得到预期的行为。这是由于__cmp__消失和Python 3中更清洁的方法对象表示造成的;方法现在有__eq__,并且引用不再是即兴生成的对象,因此行为遵循人们对Python 2的期望。
Python3.3
>>> Test.meth is Test.meth
True
>>> Test.meth == Test.meth
True
>>> Test.meth.__eq__(Test.meth)
True

你的分析有误。你在Python 3中观察到的变化与is无关,而是由于Python 3中未绑定方法对象的消失所致。Test.meth现在只是你定义的原始函数对象,而不是在运行时创建的未绑定方法对象。 - user2357112
啊,是的,我添加了一些澄清细节,包括未绑定对象问题。 - Pyrce

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接