我希望能够测试两个可调用对象是否相同。我更喜欢身份语义(使用“是”运算符),但我发现当涉及方法时,会发生不同的事情。
#(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?的答案也是这个问题的答案。
答案 0 :(得分:10)
Python没有为类foo.bar
的每个实例foo
保留规范Foo
对象。相反,当Python评估foo.bar
时,会创建一个方法对象。因此,
foo.bar is not foo.bar
至于==
,事情变得混乱。 Python有大量的方法对象类型,具体取决于方法是用Python实现还是用C语言实现方法之一。这些方法对象类型以不同方式响应==
:
==
比较方法'__func__
和__self__
属性,如果方法对象表示由同一函数实现并绑定到相等的对象,而不是相同的对象。因此,如果x.foo == y.foo
和x == y
是用Python编写的,foo
将为True。__eq__
, __repr__
, etc.), if they're implemented in C,Python比较__self__
和类似于__func__
的内部事物,如果方法具有相同的实现并且绑定到相等的对象,则再次返回True。因此,如果您运行以下代码:
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)
。