2013-08-13 2 views
15

Я хотел бы иметь возможность проверить, являются ли два вызываемых объекта одинаковыми или нет. Я бы предпочел идентификационную семантику (используя оператор «is»), но я обнаружил, что когда задействованы методы, происходит что-то другое.Как проверить функции на равенство или идентичность?

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

Я воспроизвел как с Python 2.7 и 3.3 (CPython), чтобы убедиться, что это не деталь реализации старой версии. В других случаях тестирование идентичности работает, как ожидалось (интерпретатор сессия продолжила сверху):

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

По вопросу How does Python distinguish callback function which is a member of a class?, функция обернута при связывании в качестве метода. Это имеет смысл и согласуется с рассмотренным выше случаем (3).

Есть ли надежный способ привязки метода к каким-либо другим именем, а затем сравнить их как объект, подлежащий вызову, или простая функция? Если «==» делает трюк, как это работает? Почему «==» и «есть» ведут себя по-другому в случае (1) выше?

Редактировать

Как @Claudiu отметил, что ответ на Why don't methods have reference equality? также ответ на этот вопрос.

+0

Вы видели это? http://stackoverflow.com/questions/306313/python-is-operator-behaves-unexpectedly-with-integers Я думаю, что это может помочь немного понять, почему это происходит. – taronish4

+0

Интересные вопросы, спасибо за публикацию. (+1) – NPE

+0

Короче говоря, это связано с тем, что при вызове 'is' он проверяет идентификатор, а не само значение, а два неинстанцированных объекта не имеют одинакового идентификатора, а по сравнению с' == 'он проверяет значение объект, а не просто быстрый идентификационный чек. – Torxed

ответ

9

Python не сохраняет каноническую foo.bar объект для каждого экземпляра foo класса Foo. Вместо этого объект метода создается, когда Python оценивает foo.bar. Таким образом,

foo.bar is not foo.bar 

Что касается ==, все становится беспорядочным. Python имеет удивительно большое количество типов объектов метода, в зависимости от того, был ли реализован метод в Python или один из нескольких способов методов могут быть реализованы в C. Этих типов объектов метода реагируют на == по-разному:

  • Для methods written in Python , == сравнивает атрибуты методов __func__ и __self__, возвращая значение True, если объекты метода представляют методы, реализованные одной и той же функцией и привязанные к , равным объектам, а не одному и тому же объекту. Таким образом, x.foo == y.foo будет Истинным, еслии foo написано на Python.
  • Для most "special" methods (__eq__, __repr__, etc.), if they're implemented in C Python сравнивает __self__ и внутреннюю вещь, аналогичную __func__, снова возвращающую значение True, если методы имеют одинаковую реализацию и привязаны к равным объектам.
  • Для other methods implemented in C Python выполняет то, что вы действительно ожидали, вернув True, если объекты метода представляют один и тот же метод одного и того же объекта.

Таким образом, если вы запустите следующий код:

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 

the following bizarre output Вы получаете:

True 
True 
False 

Вы, скорее всего, не хотят семантику идентичности, так как многие объекты могут представляют один и тот же метод. Вы также не должны полагаться на простые методы ==, так как семантика настолько беспорядочна, обычно бесполезна и подвержена изменениям, если метод переписывается в C. К счастью, все типы объектов метода, с которыми вы, вероятно, имеете дело, имеют __self__ атрибут, представляющий объект, к которому они присоединены, так

meth1.__self__ is meth2.__self__ and meth1 == meth2 

должен быть общий способ сравнения двух объектов метода представляют собой один и тот же метод того же объекта.

1

В то время как у меня нет ответов на все вопросы, я подозреваю, что хитрость заключается в том, чтобы использовать __func__ для вызываемых объектов, которые имеют его (т.е. методы):

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

Вы должны использовать '__func__' вместо' im_func', так как в python3 атрибуты 'im_ *', которые удалены из связанных методов. – Bakuriu

+0

@ Бакуриу: Исправлено, спасибо! – NPE

2

tldr: Методы - это дескрипторы, поэтому это может произойти. Используйте ==, если вам действительно нужно сравнить для равенства.

is (по существу) испытания для равенства id. Итак, давайте посмотрим, что из:

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

Таким образом, непосредственной причиной является то, что выражение foo.bar периодически возвращает другой объект.

Если вам нужно проверить равенство, просто используйте ==. Однако мы все хотим разобраться в этом.

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

Похоже, что есть что-то особенное в связанных методах.

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

Теперь обратите внимание, что в этом списке указан метод __get__. Это означает, что объект instancemethod является дескриптором. В соответствии с http://docs.python.org/2/reference/datamodel.html#implementing-descriptors выражение foo.bar возвращает результат (getattr(foo,'bar').__get__(foo). И вот почему это значение может изменение.

Как это почему делает изменение, я не могу сказать вам, за исключением того, что это, скорее всего, деталь реализации.

1

Вы можете использовать foo is bar, то же самое id(foo) == id(bar), чтобы проверить личность. Если вы хотите проверить «равенство» (значение), используйте ==.

Смежные вопросы