20

I had a bug where I was relying on methods being equal to each other when using is. It turns out that's not the case:

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

Why is that the case? It works for regular functions:

>>> def func(): pass
>>> func is func
True
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Claudiu
  • 224,032
  • 165
  • 485
  • 680

2 Answers2

30

Method objects are created each time you access them. Functions act as descriptors, returning a method object when their .__get__ method is called:

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

If you're on Python 3.8 or later, you can use == equality testing instead. On Python 3.8 and later, two methods are equal if their .__self__ and .__func__ attributes are identical objects (so if they wrap the same function, and are bound to the same instance, both tested with is).

Before 3.8, method == behaviour is inconsistent based on how the method was implemented - Python methods and one of the two C method types compare __self__ for equality instead of identity, while the other C method type compares __self__ by identity. See Python issue 1617161.

If you need to test that the methods represent the same underlying function, test their __func__ attributes:

>>> 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
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    If I use `==` will I get the identity-equality behavior I was looking for? – Claudiu Apr 12 '13 at 17:56
  • I think method equality checks identity for `.im_self`, not equality. [check it out](http://pastebin.com/mSbDWUna) – Claudiu Apr 12 '13 at 18:06
  • @Claudiu: yes, sorry, it tests if `im_self` is identical. – Martijn Pieters Apr 12 '13 at 18:08
  • @Claudiu Claudiu, Martijn : you mean, in the comments, that method equality checks identity for ``im_self`` **AND ``im_func``**, don't you ? That's the point that has been corrected in the edit, hasn't it ? – eyquem Jan 20 '14 at 03:00
  • Yes, both are tested for identity; functions are only equal if they are identical anyway. – Martijn Pieters Jan 20 '14 at 08:27
  • It turns out `==` for methods is [really weird](http://stackoverflow.com/questions/18216597/how-should-functions-be-tested-for-equality-or-identity/18217024#18217024). It's probably not a good idea to rely on straight `==` for method equality testing. I think `meth1.__self__ is meth2.__self__ and meth1 == meth2` should do the job for all standard bound method types. – user2357112 Jan 27 '17 at 19:32
  • @user2357112: I appreciate the heads-up, but I'm not sure I agree with the edit. I'm still pondering the implications; perhaps there is a use for `m1.__self__ == m2.__self__` being used rather than identity testing. I'll admit to being surprised, but let me take care of the answer myself; that was a pretty radical edit you made there. – Martijn Pieters Jan 28 '17 at 23:20
  • Sure, I'll let you handle it. (Claudiu's test was thrown off by the way `PyObject_RichCompareBool` and `PyObject_Compare` bypass `__eq__` and `__ne__` when both operands are the same object, so method objects representing the same method of the same object will compare equal even if the underlying object doesn't compare equal to itself through a Python-level `==`.) – user2357112 Jan 28 '17 at 23:25
0

Martijn is right that a new Methods are objects generated by .__get__ so their address pointers don't equate with an is evaluation. Note that using == will evaluate as intended in 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

Note however that methods referenced from an instance do not equate to those referenced from class because of the self reference carried along with the method from an instance.

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

In Python 3.3 the is operator for methods more often behaves the same as the == so you get the expected behavior instead in this example. This results from both __cmp__ disappearing and a cleaner method object representation in Python 3; methods now have __eq__ and references are not built-on-the-fly objects, so the behavior follows as one might expect without Python 2 expectations.

Python3.3
>>> Test.meth is Test.meth
True
>>> Test.meth == Test.meth
True
>>> Test.meth.__eq__(Test.meth)
True
Pyrce
  • 8,296
  • 3
  • 31
  • 46
  • Your analysis is off. The changes you observed in Python 3 aren't related to `is`; they're due to the disappearance of unbound method objects in Python 3. `Test.meth` is now just the raw function object you defined, instead of an unbound method object created on the fly. – user2357112 Jan 28 '17 at 23:32
  • Ahh yes, I added some clarifying details to include the unbound object issue. – Pyrce Jan 31 '17 at 19:10