Почему методы не имеют ссылочного равенства?

У меня была ошибка, когда я полагался на методы, равные друг другу при использовании is. Оказывается, что не случай:

>>> class What(object):
    def meth(self):
        pass

>>> What.meth is What.meth
False
>>> inst = What()
>>> inst.meth is inst.meth
False

Почему это так? Он работает для регулярных функций:

>>> def func():
    pass

>>> func is func
True

Ответы

Ответ 1

Объекты метода создаются каждый раз, когда вы обращаетесь к ним. Функции действуют как descriptors, возвращая объект метода при вызове метода .__get__:

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

Вместо этого используйте == проверку равенства.

Два метода равны, если их атрибуты .im_self и .im_func идентичны. Если вам нужно проверить, что методы представляют одну и ту же базовую функцию, проверьте их атрибуты im_func:

>>> What.meth == What.meth     # unbound methods (or functions in Python 3)
True
>>> What().meth == What.meth   # unbound method and bound method
False
>>> What().meth == What().meth # bound methods with *different* instances
False
>>> What().meth.im_func == What().meth.im_func  # functions
True

Ответ 2

Martijn прав, что новые методы - это объекты, сгенерированные с помощью .__get__, поэтому их указатели адресов не приравниваются к оценке is. Обратите внимание, что использование == будет оцениваться как указано в 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

Обратите внимание, однако, что методы, ссылающиеся на экземпляр, не приравниваются к тем, на которые ссылается класс, из-за самонаправления, переносимого вместе с методом из экземпляра.

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

В Python 3.3 оператор is для методов чаще ведет себя так же, как и ==, поэтому вместо этого вы ожидаете, что поведение будет выглядеть в этом примере. Это происходит как при исчезновении __cmp__, так и в представлении объекта более чистого метода в Python 3; методы теперь имеют __eq__, а ссылки не являются объектами "на лету", поэтому поведение следует за тем, как можно ожидать без ожиданий Python 2.

Python3.3
>>> Test.meth is Test.meth
True
>>> Test.meth == Test.meth
True
>>> Test.meth.__eq__(Test.meth)
True