如何测试函数的平等性或身份?

时间:2013-08-13 18:34:09

标签: python

我希望能够测试两个可调用对象是否相同。我更喜欢身份语义(使用“是”运算符),但我发现当涉及方法时,会发生不同的事情。

#(1) identity and equality with a method
class Foo(object):
    def bar(self):
        pass
foo = Foo()
b = foo.bar
b == foo.bar    #evaluates True. why?
b is foo.bar    #evaluates False. why?

我用Python 2.7和3.3(CPython)重现了这一点,以确保它不是旧版本的实现细节。在其他情况下,身份测试按预期工作(翻译会议从上面继续):

#(2) with a non-method function
def fun(self):
    pass
f = fun
f == fun    #evaluates True
f is fun    #evaluates True

#(3) when fun is bound as a method 
Foo.met = fun
foo.met == fun    #evaluates False
foo.met is fun    #evaluates False

#(4) with a callable data member
class CanCall(object):
    def __call__(self):
        pass
Foo.can = CanCall()
c = foo.can
c == foo.can    #evaluates True
c is foo.can    #evaluates True

根据问题How does Python distinguish callback function which is a member of a class?,函数在绑定为方法时被包装。这是有道理的,并且与上面的情况(3)一致。

是否有一种可靠的方法将方法绑定到其他名称,然后将它们比较为可调用对象或普通函数?如果“==”成功,那该怎么办?为什么“==”和“是”在上面的情况(1)中表现不同?

修改

正如@Claudiu指出的那样,Why don't methods have reference equality?的答案也是这个问题的答案。

4 个答案:

答案 0 :(得分:10)

Python没有为类foo.bar的每个实例foo保留规范Foo对象。相反,当Python评估foo.bar时,会创建一个方法对象。因此,

foo.bar is not foo.bar

至于==,事情变得混乱。 Python有大量的方法对象类型,具体取决于方法是用Python实现还是用C语言实现方法之一。这些方法对象类型以不同方式响应==

因此,如果您运行以下代码:

class Foo(object):
    def __eq__(self, other):
        return True if isinstance(other, Foo) else NotImplemented
    def foo(self):
        pass

print Foo().foo == Foo().foo
print [].__repr__ == [].__repr__
print [].append == [].append

你得到the following bizarre output

True
True
False

您很可能不需要身份语义,因为许多对象可以表示相同的方法。你也不应该依赖普通的==方法,因为语义是如此混乱,通常是无用的,并且如果方法在C中被重写,则容易改变。幸运的是,你可能会处理所有方法对象类型具有__self__属性,表示它们绑定的对象,所以

meth1.__self__ is meth2.__self__ and meth1 == meth2

应该是比较两个方法对象是否代表同一对象的相同方法的一般方法。

答案 1 :(得分:2)

tldr:方法是描述符,这就是为什么会发生这种情况。如果你真的需要比较相等,请使用==

is(实际上)测试id的相等性。所以让我们检查一下:

>>> id(foo.bar)
4294145364L
>>> id(foo.bar)
4294145364L
>>> id(foo.bar)
4294145364L
>>> b = foo.bar
>>> id(foo.bar)
4293744796L
>>> id(foo.bar)
4293744796L
>>> b()
>>> id(foo.bar)
4293744796L
>>> b = 1
>>> id(foo.bar)
4294145364L
>>> type(foo.bar)
<type 'instancemethod'>
>>>

因此,直接原因是表达式foo.bar间歇性地返回另一个对象。

如果确实需要检查是否相等,请使用==。但是,我们都希望深究这一点。

>>> foo.__dict__['bar']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'bar'
>>> Foo.__dict__['bar']
<function bar at 0xffe2233c>
>>> getattr(foo, 'bar')
<bound method Foo.bar of <__main__.Foo object at 0xffe2f9ac>>
>>> foo.bar
<bound method Foo.bar of <__main__.Foo object at 0xffe2f9ac>>
>>>

看起来绑定方法有一些特别之处。

>>> type(foo.bar)
<type 'instancemethod'>
>>> help(type(foo.bar))
Help on class instancemethod in module __builtin__:

class instancemethod(object)
 |  instancemethod(function, instance, class)
 |
 |  Create an instance method object.
 |
 |  Methods defined here:
 |
 |  __call__(...)
 |      x.__call__(...) <==> x(...)
 |
 |  __cmp__(...)
 |      x.__cmp__(y) <==> cmp(x,y)
 |
 |  __delattr__(...)
 |      x.__delattr__('name') <==> del x.name
 |
 |  __get__(...)
 |      descr.__get__(obj[, type]) -> value
 |
 |  __getattribute__(...)
 |      x.__getattribute__('name') <==> x.name
 |
 |  __hash__(...)
 |      x.__hash__() <==> hash(x)
 |
 |  __repr__(...)
 |      x.__repr__() <==> repr(x)
 |
 |  __setattr__(...)
 |      x.__setattr__('name', value) <==> x.name = value
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __func__
 |      the function (or other callable) implementing a method
 |
 |  __self__
 |      the instance to which a method is bound; None for unbound methods
 |
 |  im_class
 |      the class associated with a method
 |
 |  im_func
 |      the function (or other callable) implementing a method
 |
 |  im_self
 |      the instance to which a method is bound; None for unbound methods
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |
 |  __new__ = <built-in method __new__ of type object>
 |      T.__new__(S, ...) -> a new object with type S, a subtype of T

现在,请注意这列出了__get__方法。这意味着instancemethod对象是描述符。根据{{​​3}},foo.bar表达式返回(getattr(foo,'bar').__get__(foo)的结果。这就是为什么这个值可以改变的原因。

至于 更改的原因,我不能告诉你,除了它可能是一个实现细节。

答案 2 :(得分:1)

虽然我没有回答你的所有问题,但我怀疑诀窍是将__func__用于拥有它的callables(即方法):

In [32]: def same_func(func1, func2):
   ....:     if hasattr(func1, '__func__'):
   ....:         func1 = func1.__func__
   ....:     if hasattr(func2, '__func__'):
   ....:         func2 = func2.__func__
   ....:     return func1 is func2
   ....: 

In [33]: same_func(b, foo.bar)
Out[33]: True

In [34]: same_func(f, fun)
Out[34]: True

In [35]: same_func(foo.met, fun)
Out[35]: True

In [36]: same_func(c, foo.can)
Out[36]: True

答案 3 :(得分:1)

您可以使用相同foo is bar的{​​{1}}来检查身份。如果要检查“相等”(值),请使用id(foo) == id(bar)