25

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 when methods are involved, something different happens.

#(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?

I've reproduced this with both Python 2.7 and 3.3 (CPython) to make sure it's not an implementation detail of the older version. In other cases, identity testing works as expected (interpreter session continued from above):

#(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

According to the question How does Python distinguish callback function which is a member of a class?, a function is wrapped when bound as a method. This makes sense and is consistent with case (3) above.

Is there a reliable way to bind a method to some other name and then later have them compare equal like a callable object or a plain function would? If the "==" does the trick, how does that work? Why do "==" and "is" behave differently in case (1) above?

Edit

As @Claudiu pointed out, the answer to Why don't methods have reference equality? is also the answer to this question.

Community
  • 1
  • 1
jack
  • 2,094
  • 1
  • 19
  • 17
  • Have you seen this? http://stackoverflow.com/questions/306313/python-is-operator-behaves-unexpectedly-with-integers I think it could help a bit with understanding why this is happening. – taronish4 Aug 13 '13 at 18:43
  • Yea in short, it's because when calling `is` it checks for the ID and not the value itself and two instancianted objects doesn't have the same ID while compared with `==` it checks the value of the object and not just a quick ID check. – Torxed Aug 13 '13 at 18:52
  • 3
    Definite duplicate of [Why don't methods have reference equality?](http://stackoverflow.com/questions/15977808/why-dont-methods-have-reference-equality). [Martijn's answer](http://stackoverflow.com/a/15977850/15055) there is quite comprehensive. – Claudiu Aug 13 '13 at 18:56
  • 1
    I don't see why you think `==` is behaving differently. In case 1 you creating a *new* bound object every time you do `foo.bar`. The bound object is *equal* to the others but it's not the same object, hence the results. Case2 you are not passing through the class, hence there aren't any problems and the function object acts as any other object. Case 3 you are comparing a *function* object with *bound methods*, hence they are neither equal nor identical. In case 4 you are setting an attribute that happens to be a callable, but python only bounds *function* objects, hence the results. – Bakuriu Aug 13 '13 at 18:57

4 Answers4

20

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:

  • For methods written in Python, == 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.
  • For most "special" methods (__eq__, __repr__, etc.), if they're implemented in C, Python compares __self__ and an internal thing analogous to __func__, again returning True if the methods have the same implementation and are bound to equal objects.
  • For other methods implemented in C, Python does what you'd actually expect, returning True if the method objects represent the same method of the same object.

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
user2357112
  • 260,549
  • 28
  • 431
  • 505
2

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.

Marcin
  • 48,559
  • 18
  • 128
  • 201
0

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
NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • You should use `__func__` instead of `im_func` since in python3 the `im_*` attributes where removed from bound methods. – Bakuriu Aug 13 '13 at 18:54
-1

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 ==.

ovrwngtvity
  • 4,261
  • 3
  • 15
  • 20