I would like to be able to test whether two callable objects are the same or not. I would prefer identity semantics (using the \"is\" operator), but I\'ve discovered that wh
While I don't have answers to all of your questions, I suspect the trick is to use __func__
for callables that have it (i.e. for methods):
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
Python doesn't keep a canonical foo.bar
object for every instance foo
of class Foo
. Instead, a method object is created when Python evaluates foo.bar
. Thus,
foo.bar is not foo.bar
As for ==
, things get messy. Python 3.8 fixed method comparison so two methods are equal if they represent the same method of the same object, but on lower versions, the behavior is inconsistent.
Python has a surprisingly large number of method object types, depending on whether the method was implemented in Python or one of the several ways methods can be implemented in C. Before Python 3.8, these method object types respond to ==
differently:
==
compares the methods' __func__
and __self__
attributes, returning True if the method objects represent methods implemented by the same function and bound to equal objects, rather than the same object. Thus, x.foo == y.foo
will be True if x == y
and foo
is written in Python.__self__
and an internal thing analogous to __func__
, again returning True if the methods have the same implementation and are bound to equal objects.Thus, if you run the following code on a Python version below 3.8:
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)
You get the following bizarre output:
True
True
False
To get the Python 3.8 semantics on lower versions, you can use
meth1.__self__ is meth2.__self__ and meth1 == meth2
tldr: Methods are descriptors, which is why this can happen. Use ==
if you really need to compare for equality.
is
(in effect) tests for equality of id
. So let's check that out:
>>> 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'>
>>>
So, the immediate cause is that the expression foo.bar
intermittently returns a different object.
If you do need to check for equality, just use ==
. However, we all want to get to the bottom of this.
>>> 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>>
>>>
It looks like there's something special about bound methods.
>>> 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
Now, notice this lists a __get__
method. That means the instancemethod
object is a descriptor. As per http://docs.python.org/2/reference/datamodel.html#implementing-descriptors the expression foo.bar
returns the result of (getattr(foo,'bar').__get__(foo)
. And that is why this value can change.
As to why it does change, I can't tell you, except that it is likely an implementation detail.
You can use foo is bar
that is the same id(foo) == id(bar)
to check identity. If you want to check 'equality' (value) use ==
.