37 votes

Pourquoi/Quand en Python, `x==y` appelle `y.__eq__(x)` ?

La documentation Python indique clairement que x==y appelle x.__eq__(y) . Cependant, il semble que dans de nombreuses circonstances, le contraire soit vrai. Où est-il documenté quand ou pourquoi cela se produit, et comment puis-je savoir avec certitude si l'objet __cmp__ o __eq__ vont être appelées.

Edit : Juste pour clarifier, je sais que __eq__ est appelé en préférence à __cmp__ mais je ne comprends pas bien pourquoi y.__eq__(x) est appelé de préférence à x.__eq__(y) alors que c'est ce qui se passe selon la documentation.

>>> class TestCmp(object):
...     def __cmp__(self, other):
...         print "__cmp__ got called"
...         return 0
... 
>>> class TestEq(object):
...     def __eq__(self, other):
...         print "__eq__ got called"
...         return True
... 
>>> tc = TestCmp()
>>> te = TestEq()
>>> 
>>> 1 == tc
__cmp__ got called
True
>>> tc == 1
__cmp__ got called
True
>>> 
>>> 1 == te
__eq__ got called
True
>>> te == 1
__eq__ got called
True
>>> 
>>> class TestStrCmp(str):
...     def __new__(cls, value):
...         return str.__new__(cls, value)
...     
...     def __cmp__(self, other):
...         print "__cmp__ got called"
...         return 0
... 
>>> class TestStrEq(str):
...     def __new__(cls, value):
...         return str.__new__(cls, value)
...     
...     def __eq__(self, other):
...         print "__eq__ got called"
...         return True
... 
>>> tsc = TestStrCmp("a")
>>> tse = TestStrEq("a")
>>> 
>>> "b" == tsc
False
>>> tsc == "b"
False
>>> 
>>> "b" == tse
__eq__ got called
True
>>> tse == "b"
__eq__ got called
True

Edit : D'après la réponse et le commentaire de Mark Dickinson, il semblerait que :

  1. Remplacement de la comparaison riche __cmp__
  2. __eq__ c'est son propre __rop__ à son __op__ (et similaire pour __lt__ , __ge__ etc)
  3. Si l'objet de gauche est une classe intégrée ou de style nouveau, et que l'objet de droite est une sous-classe de celle-ci, l'attribut __rop__ est essayée avant que l'objet de gauche __op__

Cela explique le comportement dans le TestStrCmp exemples. TestStrCmp est une sous-classe de str mais n'implémente pas sa propre __eq__ donc le __eq__ de str a la priorité dans les deux cas (c'est-à-dire tsc == "b" appelle b.__eq__(tsc) comme un __rop__ en raison de la règle 1).

Dans le TestStrEq exemples, tse.__eq__ est appelé dans les deux cas car TestStrEq est une sous-classe de str et c'est ainsi qu'on l'appelle de préférence.

Dans le TestEq exemples, TestEq met en œuvre __eq__ y int ne le fait pas. __eq__ est appelé les deux fois (règle 1).

Mais je ne comprends toujours pas le tout premier exemple avec TestCmp . tc n'est pas une sous-classe de int donc AFAICT 1.__cmp__(tc) devrait être appelé, mais ne l'est pas.

32voto

Mark Dickinson Points 6780

Vous oubliez une exception importante au comportement habituel : lorsque l'opérande de droite est une instance d'une sous-classe de la classe de l'opérande de gauche, la méthode spéciale pour l'opérande de droite est appelée en premier.

Voir la documentation à l'adresse suivante :

http://docs.python.org/reference/datamodel.html#coercion-rules

et en particulier, les deux paragraphes suivants :

Pour les objets x y y premièrement x.__op__(y) est essayé. Si cela n'est pas implémenté ou renvoie NotImplemented , y.__rop__(x) est essayé. Si cela n'est pas non plus implémenté ou renvoie NotImplemented , a exception TypeError est levée. Mais voir l'exception suivante :

Exception au point précédent : si l l'opérande de gauche est une instance d'un type intégré ou d'une classe de type nouveau, et l'opérande de droite est une instance d'une sous-classe propre de ce type ou de cette et qu'il surcharge la fonction __rop__() la méthode de droite de l'opérande __rop__() la méthode est essayée avant que l'opérande de gauche __op__() méthode.

6voto

Dancrumb Points 11918

En fait, dans le docs il déclare :

[ __cmp__ est c]aldé par des opérations de comparaison si la comparaison riche (voir ci-dessus) n'est pas définie.

__eq__ est une méthode de comparaison riche et, dans le cas de TestCmp n'est pas défini, d'où l'appel de __cmp__

1voto

dubiousjim Points 2259

Cela n'est-il pas documenté dans le Référence linguistique ? D'après un rapide coup d'œil, on dirait que __cmp__ est ignorée lorsque __eq__ , __lt__ etc. sont définis. Je comprends que cela inclut le cas où __eq__ est définie sur une classe mère. str.__eq__ est déjà défini, de sorte que __cmp__ sur ses sous-classes seront ignorées. object.__eq__ etc. ne sont pas définis de sorte que __cmp__ sur ses sous-classes seront honorées.

En réponse à la question clarifiée :

Je sais que __eq__ est appelé en préférer à __cmp__ mais je ne le suis pas. pourquoi y.__eq__(x) est appelé de préférence à x.__eq__(y) alors que la ce dernier est ce que les docs indiquent se produire.

Les médecins disent x.__eq__(y) sera appelé en premier, mais il a l'option de retourner NotImplemented dans ce cas y.__eq__(x) s'appelle. Je ne sais pas pourquoi vous pensez que quelque chose de différent se passe ici.

Quel est le cas qui vous intrigue le plus ? Je comprends que vous soyez juste perplexe à propos de la "b" == tsc y tsc == "b" cas, correct ? Dans les deux cas, str.__eq__(onething, otherthing) est appelé. Puisque vous ne surchargez pas l'option __eq__ dans TestStrCmp, vous finissez par vous fier à la méthode de la chaîne de base et elle vous dit que les objets ne sont pas égaux.

Sans connaître les détails de l'implémentation de str.__eq__ Je ne sais pas si ("b").__eq__(tsc) retournera NotImplemented et donner une chance au tsc de gérer le test d'égalité. Mais même s'il le faisait, la façon dont vous avez défini TestStrCmp, vous obtiendrez toujours un faux résultat.

Donc on ne voit pas bien ce que vous voyez d'inattendu ici.

Peut-être que ce qui se passe c'est que Python préfère __eq__ a __cmp__ s'il est défini sur soit des objets comparés, alors que vous attendiez __cmp__ sur l'objet le plus à gauche pour avoir la priorité sur __eq__ sur l'objet de droite. Est-ce que c'est ça ?

1voto

Mihail Points 1559

Comme je le sais, __eq__() est une méthode dite de "comparaison riche", et est appelée pour les opérateurs de comparaison de préférence à __cmp__() ci-dessous. __cmp__() est appelé si la "comparaison riche" n'est pas définie.

Donc dans A == B :
Si __eq__() est défini en A, il sera appelé
Else __cmp__() sera appelé

__eq__() défini dans "str", de sorte que votre __cmp__() n'a pas été appelée.

La même règle s'applique à __ne__(), __gt__(), __ge__(), __lt__() y __le__() méthodes de "comparaison riche".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X