¿Cómo se deben probar las funciones para la igualdad o identidad?

Me gustaría poder probar si dos objetos que se pueden llamar son iguales o no. Preferiría la semántica de identidad (utilizando el operador “es”), pero he descubierto que cuando se trata de métodos, sucede algo diferente.

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

He reproducido esto con Python 2.7 y 3.3 (CPython) para asegurarme de que no sea un detalle de implementación de la versión anterior. En otros casos, las pruebas de identidad funcionan como se esperaba (la sesión del intérprete continúa desde arriba):

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

De acuerdo con la pregunta ¿Cómo distingue Python la función de callback que es miembro de una clase? , una función se envuelve cuando se enlaza como un método. Esto tiene sentido y es consistente con el caso (3) anterior.

¿Existe una forma confiable de vincular un método a otro nombre y luego hacer que se comparen de la misma forma que un objeto llamable o una función simple? Si el “==” hace el truco, ¿cómo funciona eso? ¿Por qué “==” y “es” se comportan de manera diferente en el caso (1) anterior?

Editar

Como señaló @Claudiu, la respuesta a ¿Por qué los métodos no tienen igualdad de referencia? También es la respuesta a esta pregunta.

Python no conserva un objeto foo.bar canónico para cada instancia foo de la clase Foo . En su lugar, se crea un objeto de método cuando Python evalúa foo.bar . Así,

 foo.bar is not foo.bar 

En cuanto a == , las cosas se complican. Python tiene un número sorprendentemente grande de tipos de objetos de método, dependiendo de si el método se implementó en Python o una de las varias formas en que se pueden implementar métodos en C. Estos tipos de objetos de método responden a == diferente:

  • Para los métodos escritos en Python , == compara los __func____func__ y __self__ los métodos, devolviendo True si los objetos del método representan métodos implementados por la misma función y vinculados a objetos iguales , en lugar del mismo objeto. Por lo tanto, x.foo == y.foo será verdadero si x == y y foo está escrito en Python.
  • Para la mayoría de los métodos “especiales” ( __eq__ , __repr__ , etc.), si se implementan en C , Python compara __self__ y una cosa interna análoga a __func__ , de nuevo devuelve True si los métodos tienen la misma implementación y están vinculados a objetos iguales .
  • Para otros métodos implementados en C , Python hace lo que realmente esperas, devolviendo True si los objetos del método representan el mismo método del mismo objeto.

Por lo tanto, si ejecuta el siguiente código:

 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 

Obtienes la siguiente salida extraña :

 True True False 

Lo más probable es que no desee semántica de identidad, ya que muchos objetos pueden representar el mismo método. Tampoco debe confiar en plain == para los métodos, ya que la semántica es tan desordenada, generalmente inútil, y propensa a cambiar si el método se reescribe en C. Afortunadamente, todos los tipos de objetos de método con los que es probable que trabaje tienen una __self__ atributo que representa el objeto al que están vinculados, por lo que

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

debe ser una forma general de comparar si dos objetos de método representan el mismo método del mismo objeto.

tldr: los métodos son descriptores, por lo que esto puede suceder. Use == si realmente necesita comparar para la igualdad.

is (en efecto) pruebas para la igualdad de id . Así que vamos a ver que:

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

Entonces, la causa inmediata es que la expresión foo.bar devuelve intermitentemente un objeto diferente.

Si necesita verificar la igualdad, simplemente use == . Sin embargo, todos queremos llegar al fondo de esto.

 >>> foo.__dict__['bar'] Traceback (most recent call last): File "", line 1, in  KeyError: 'bar' >>> Foo.__dict__['bar']  >>> getattr(foo, 'bar') > >>> foo.bar > >>> 

Parece que hay algo especial sobre los métodos ligados.

 >>> type(foo.bar)  >>> 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__ =  | T.__new__(S, ...) -> a new object with type S, a subtype of T 

Ahora, note que esto enumera un método __get__ . Eso significa que el objeto instancemethod es un descriptor. Según http://docs.python.org/2/reference/datamodel.html#implementing-descriptors, la expresión foo.bar devuelve el resultado de (getattr(foo,'bar').__get__(foo) . Y eso es Por qué este valor puede cambiar.

En cuanto a por qué cambia, no puedo decirle, excepto que es probable que sea un detalle de implementación.

Si bien no tengo respuestas a todas sus preguntas, sospecho que el truco es usar __func__ para las __func__ que lo tienen (es decir, para los métodos):

 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 

Puede usar foo is bar que es el mismo id(foo) == id(bar) para verificar la identidad. Si desea marcar ‘igualdad’ (valor) use == .