Почему методы не имеют ссылочного равенства?
У меня была ошибка, когда я полагался на методы, равные друг другу при использовании 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