2013-08-13 10 views
15

Ich möchte in der Lage sein zu testen, ob zwei aufrufbare Objekte identisch sind oder nicht. Ich würde die Identitätssemantik bevorzugen (mit dem "is" -Operator), aber ich habe entdeckt, dass, wenn Methoden involviert sind, etwas anderes passiert.Wie sollten Funktionen auf Gleichheit oder Identität getestet werden?

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

Ich habe dies sowohl mit Python reproduziert 2.7 und 3.3 (CPython), um sicherzustellen, es ist nicht ein Implementierungsdetail der älteren Version. In anderen Fällen arbeitet Identitätsprüfung als (Interpreter-Sitzung von oben fortgesetzt) ​​erwartet:

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

Nach der Frage How does Python distinguish callback function which is a member of a class?, eine Funktion eingewickelt wird, wenn als Methode gebunden. Dies ist sinnvoll und entspricht dem obigen Fall (3).

Gibt es eine zuverlässige Möglichkeit, eine Methode an einen anderen Namen zu binden und sie dann später gleich wie ein aufrufbares Objekt oder eine einfache Funktion vergleichen zu lassen? Wenn das "==" funktioniert, wie funktioniert das? Warum verhalten sich "==" und "ist" im obigen Fall (1) anders?

bearbeiten

Wie @Claudiu darauf hingewiesen, ist die Antwort auf Why don't methods have reference equality? auch die Antwort auf diese Frage.

+0

Haben Sie das gesehen? http://stackoverflow.com/questions/306313/python-is-operator-behaves-unexpectedly-with-integers Ich denke, es könnte ein wenig helfen, zu verstehen, warum dies passiert. – taronish4

+0

Interessante Fragen, danke für das Posten. (+1) – NPE

+0

Ja kurz gesagt, es ist, weil beim Aufrufen von 'is' es nach der ID und nicht nach dem Wert selbst sucht und zwei instanzierte Objekte nicht die gleiche ID haben, verglichen mit' == 'überprüft es den Wert der Objekt und nicht nur eine schnelle ID-Prüfung. – Torxed

Antwort

9

Python keine kanonische nicht halten foo.bar Objekt für jede Instanz foo der Klasse Foo. Stattdessen wird ein Methodenobjekt erstellt, wenn Python foo.bar auswertet. Also,

foo.bar is not foo.bar 

Wie für ==, Dinge werden chaotisch. Python hat eine überraschend große Anzahl von Arten Methode Objekt, je nachdem, ob die Methode in Python implementiert wurde oder eine von mehreren Möglichkeiten, Methoden unterschiedlich Diese Methode Objekttypen reagieren auf == in C implementiert werden können:

  • Für methods written in Python== vergleicht die Methoden '__func__ und __self__ Attribute und gibt True zurück, wenn die Methodenobjekte Methoden darstellen, die von der gleichen Funktion implementiert und an gleiche Objekte gebunden sind, und nicht dasselbe Objekt. Daher wird x.foo == y.foo True sein, wenn x == y und foo in Python geschrieben wird.
  • Für most "special" methods (__eq__, __repr__, etc.), if they're implemented in C vergleicht Python __self__ und eine interne Sache analog __func__, wieder True zurückgegeben, wenn die Methoden die gleiche Implementierung haben und an gleiche Objekte gebunden sind.
  • Für other methods implemented in C führt Python das aus, was Sie tatsächlich erwarten, und gibt True zurück, wenn die Methodenobjekte dieselbe Methode desselben Objekts darstellen.

Wenn Sie also den folgenden Code ausführen:

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 

Sie erhalten the following bizarre output:

True 
True 
False 

Sie höchstwahrscheinlich nicht wollen Identität Semantik, da viele Objekte können repräsentieren die gleiche Methode. Sie sollten auch nicht auf einfache == für Methoden verlassen, da die Semantik so chaotisch, in der Regel nutzlos und anfällig für Änderungen sind, wenn die Methode in C umgeschrieben wird. Glücklicherweise haben alle Methodenobjekttypen, mit denen Sie zu tun haben, eine __self__ Attribut darstellt, das Objekt an die sie gebunden sind, so

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

sollte eine allgemeine Art und Weise zu vergleichen, wenn zwei Verfahren Objekte die gleiche Methode des gleichen Objekts darstellen.

1

Während ich Antworten müssen nicht alle Fragen, vermute ich, der Trick __func__ für Callables zu verwenden ist, die es haben (dh für Methoden):

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

Sie sollten '__func__' anstelle von' im_func' verwenden, da in python3 die 'im_ *' - Attribute aus gebundenen Methoden entfernt wurden. – Bakuriu

+0

@Bakuriu: Fest, danke! – NPE

2

tldr: Methoden sind Deskriptoren, weshalb dies passieren kann. Verwenden Sie ==, wenn Sie wirklich für Gleichheit vergleichen müssen.

is (in der Tat) Tests für die Gleichheit von id. Lassen Sie uns also, dass der Check-out:

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

So ist die unmittelbare Ursache ist, dass der Ausdruck foo.bar intermittierend ein anderes Objekt zurückgibt.

Wenn Sie auf Gleichheit prüfen müssen, verwenden Sie einfach ==. Aber wir alle wollen dem auf den Grund gehen.

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

Es sieht so aus als gäbe es etwas Besonderes über gebundene Methoden.

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

Nun bemerken dies eine __get__ Methode auflistet. Das bedeutet, dass das Objekt instancemethod ein Deskriptor ist. Gemäß http://docs.python.org/2/reference/datamodel.html#implementing-descriptors liefert der Ausdruck foo.bar das Ergebnis (getattr(foo,'bar').__get__(foo). Und deshalb kann dieser Wert ändern.

Warum es tut ändern, kann ich Ihnen nicht sagen, außer dass es wahrscheinlich ein Implementierungsdetail ist.

1

Sie können foo is bar das gleiche id(foo) == id(bar) verwenden, um die Identität zu überprüfen. Wenn Sie "Gleichheit" (Wert) überprüfen möchten, verwenden Sie ==.

Verwandte Themen