J'ai une classe dans laquelle je veux remplacer l' __eq__()
opérateur. Il semble logique que je devrais également remplacer l' __ne__()
opérateur, mais est-il judicieux d'implémenter __ne__
en __eq__
tant que tel?
class A:
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
Ou y a-t-il quelque chose qui me manque dans la façon dont Python utilise ces opérateurs qui en fait une mauvaise idée?
la source
__ne__
aide__eq__
, seulement que vous le mettre en œuvre.NotImplemented
retour d'un côté comme un signal à déléguer de__ne__
l'autre côté,not self == other
c'est (en supposant que l'opérande__eq__
ne sait pas comment comparer l'autre opérande) déléguer implicitement__eq__
de l'autre côté, puis l'inverser. Pour les types étranges, par exemple les champs de SQLAlchemy ORM, cela pose des problèmes .__ne__
sont automatiquement déléguées à__eq__
et la citation de cette réponse n'existe plus dans les documents. En bout de ligne, il est parfaitement pythonique de ne mettre en œuvre__eq__
et de laisser__ne__
déléguer.Réponse courte: Ne pas mettre en œuvre, mais si vous devez, utiliser
==
, non__eq__
Dans Python 3,
!=
est la négation de==
par défaut, donc vous n'êtes même pas obligé d'écrire un__ne__
, et la documentation n'est plus d'avis sur l'écriture d'un.De manière générale, pour le code Python 3 uniquement, n'en écrivez pas à moins que vous n'ayez besoin d'éclipser l'implémentation parent, par exemple pour un objet intégré.
Autrement dit, gardez à l'esprit le commentaire de Raymond Hettinger :
Si vous avez besoin que votre code fonctionne dans Python 2, suivez la recommandation pour Python 2 et cela fonctionnera très bien dans Python 3.
Dans Python 2, Python lui-même n'implémente automatiquement aucune opération en termes d'une autre - par conséquent, vous devez définir le
__ne__
en termes de==
plutôt que de__eq__
. PAR EXEMPLEVoir la preuve que
__ne__()
opérateur de mise en œuvre basé sur__eq__
et__ne__
dans Python 2fournit un comportement incorrect dans la démonstration ci-dessous.
Longue réponse
La documentation de Python 2 dit:
Cela signifie donc que si nous définissons
__ne__
en termes de l'inverse de__eq__
, nous pouvons obtenir un comportement cohérent.Cette section de la documentation a été mise à jour pour Python 3:
et dans la section "Quoi de neuf" , nous voyons que ce comportement a changé:
Pour l'implémentation
__ne__
, nous préférons utiliser l'==
opérateur au lieu d'utiliser la__eq__
méthode directement afin que siself.__eq__(other)
une sous-classe retourneNotImplemented
pour le type vérifié, Python vérifiera de manière appropriéeother.__eq__(self)
Dans la documentation :Administré un opérateur de comparaison riche, si elles ne sont pas du même type, Python vérifie si le
other
est un sous - type, et si elle a défini cet opérateur, il utilise laother
méthode de la première (inverse pour<
,<=
,>=
et>
). SiNotImplemented
est renvoyé, alors il utilise la méthode opposée. (Il ne vérifie pas deux fois la même méthode.) L'utilisation de l'==
opérateur permet à cette logique de se produire.Attentes
Sémantiquement, vous devez implémenter
__ne__
en termes de vérification d'égalité car les utilisateurs de votre classe s'attendront à ce que les fonctions suivantes soient équivalentes pour toutes les instances de A:Autrement dit, les deux fonctions ci-dessus doivent toujours renvoyer le même résultat. Mais cela dépend du programmeur.
Démonstration d'un comportement inattendu lors de la définition
__ne__
basée sur__eq__
:Tout d'abord la configuration:
Instanciez des instances non équivalentes:
Comportement prévisible:
(Remarque: bien que chaque seconde assertion de chacun des éléments ci-dessous soit équivalente et donc logiquement redondante à celle qui la précède, je les inclue pour démontrer que l' ordre n'a pas d'importance lorsque l'un est une sous-classe de l'autre. )
Ces instances ont été
__ne__
implémentées avec==
:Ces instances, testées sous Python 3, fonctionnent également correctement:
Et rappelez-vous que ceux-ci ont été
__ne__
implémentés avec__eq__
- bien que ce soit le comportement attendu, l'implémentation est incorrecte:Comportement inattendu:
Notez que cette comparaison contredit les comparaisons ci-dessus (
not wrong1 == wrong2
).et,
Ne sautez pas
__ne__
dans Python 2Pour obtenir des preuves que vous ne devriez pas ignorer l'implémentation
__ne__
dans Python 2, consultez ces objets équivalents:Le résultat ci-dessus devrait être
False
!Source Python 3
L'implémentation CPython par défaut pour
__ne__
esttypeobject.c
dansobject_richcompare
:Mais les
__ne__
utilisations par défaut__eq__
?Les
__ne__
détails d'implémentation par défaut de Python 3 au niveau C sont utilisés__eq__
car le niveau supérieur==
( PyObject_RichCompare ) serait moins efficace - et doit donc également gérerNotImplemented
.Si
__eq__
est correctement implémenté, alors la négation de==
est également correcte - et cela nous permet d'éviter les détails d'implémentation de bas niveau dans notre__ne__
.L' utilisation
==
nous permet de garder notre logique de bas niveau dans un endroit, et éviter d' adressageNotImplemented
dans__ne__
.On pourrait supposer à tort que cela
==
peut revenirNotImplemented
.Il utilise en fait la même logique que l'implémentation par défaut de
__eq__
, qui vérifie l'identité (voir do_richcompare et nos preuves ci-dessous)Et les comparaisons:
Performance
Ne me croyez pas sur parole, voyons ce qui est le plus performant:
Je pense que ces chiffres de performance parlent d'eux-mêmes:
Cela a du sens quand on considère que
low_level_python
c'est faire de la logique en Python qui serait autrement gérée au niveau C.Réponse à certains critiques
Un autre répondeur écrit:
Ne
__ne__
jamais revenirNotImplemented
ne le rend pas incorrect. Au lieu de cela, nous gérons la priorisation avecNotImplemented
via la vérification de l'égalité avec==
. En supposant qu'il==
soit correctement implémenté, nous avons terminé.Eh bien, expliquons cela.
Comme indiqué précédemment, Python 3 gère par défaut
__ne__
en vérifiant d'abord siself.__eq__(other)
retourneNotImplemented
(un singleton) - qui devrait être vérifié avecis
et retourné si c'est le cas, sinon il devrait renvoyer l'inverse. Voici cette logique écrite comme un mixin de classe:Ceci est nécessaire pour l'exactitude de l'API Python de niveau C, et il a été introduit dans Python 3, faisant
__ne__
méthodes de ce correctif pour fermer le problème 21408 et__ne__
méthodes du nettoyage de suivi supprimées iciredondant. Toutes les
__ne__
méthodes pertinentes ont été supprimées, y compris celles qui implémentent leur propre contrôle ainsi que celles qui délèguent__eq__
directement ou via==
- et==
c'était la manière la plus courante de le faire.La symétrie est-elle importante?
Notre critique persistante fournit un exemple pathologique pour justifier la manipulation
NotImplemented
en__ne__
, valorisant la symétrie avant tout. Traitons l'argument avec un exemple clair:Donc, par cette logique, afin de maintenir la symétrie, nous devons écrire le compliqué
__ne__
, quelle que soit la version de Python.Apparemment, nous ne devons pas penser que ces instances sont à la fois égales et non égales.
Je propose que la symétrie soit moins importante que la présomption de code sensible et suivant les conseils de la documentation.
Cependant, si A avait une implémentation sensée de
__eq__
, alors nous pourrions toujours suivre ma direction ici et nous aurions toujours une symétrie:Conclusion
Pour le code compatible Python 2, utilisez
==
pour implémenter__ne__
. C'est plus:En Python 3 uniquement, utilisez la négation de bas niveau au niveau C - c'est encore plus simple et performant (bien que le programmeur soit responsable de déterminer si elle est correcte ).
Encore une fois, n'écrivez pas de logique de bas niveau en Python de haut niveau.
la source
a1 != c2
retourFalse
--- il n'a pas fonctionnéa1.__ne__
, maisc2.__ne__
, ce qui a annulé la méthode du mixin__eq__
. DepuisNotImplemented
est la vérité,not NotImplemented
estFalse
.not (self == other)
, mais personne ne prétend que ce n'est pas rapide (enfin, plus rapide que toute autre option sur Py2 de toute façon). Le problème est que c'est faux dans certains cas; Python lui-même avait l'habitude de fairenot (self == other)
, mais a changé parce qu'il était incorrect en présence de sous-classes arbitraires . Le plus rapide à la mauvaise réponse est toujours faux .__ne__
délégués à__eq__
(des deux côtés si nécessaire), mais il ne retombe jamais__ne__
de l'autre côté même lorsque les deux__eq__
"abandonnent". Le bon__ne__
délègue à son propre__eq__
, mais si cela revientNotImplemented
, il retombe pour aller de l'autre côté__ne__
, plutôt que d'inverser l'autre côté__eq__
(puisque l'autre côté n'a peut-être pas explicitement choisi de déléguer à__eq__
, et vous ne devriez pas prendre cette décision pour lui).__eq__
ni ne__ne__
retourne niTrue
niFalse
, mais plutôt un objet proxy (qui se trouve être "véridique"). Une mise en œuvre incorrecte__ne__
signifie que l'ordre est important pour la comparaison (vous n'obtenez un proxy que dans un ordre).__ne__
complètement. Dans un an, Py2 sera mort et nous l'ignorons. :-)Pour mémoire, un portable Py2 / Py3 canoniquement correct et croisé
__ne__
ressemblerait à:Cela fonctionne avec tout
__eq__
ce que vous pourriez définir:not (self == other)
, n'interfère pas avec certains cas ennuyeux / complexes impliquant des comparaisons où l'une des classes impliquées n'implique pas que le résultat de__ne__
est le même que celui denot
on__eq__
(par exemple l'ORM de SQLAlchemy, où les deux__eq__
et__ne__
retournent des objets proxy spéciaux, pasTrue
ouFalse
, et en essayantnot
le résultat de__eq__
retourneraitFalse
, plutôt que le bon objet proxy).not self.__eq__(other)
cela, cela délègue correctement à l'__ne__
autre instance lorsqueself.__eq__
retourneNotImplemented
(not self.__eq__(other)
serait très faux, carNotImplemented
c'est la vérité, donc quand__eq__
je ne savais pas comment effectuer la comparaison,__ne__
reviendraitFalse
, ce qui implique que les deux objets étaient égaux alors qu'en fait le seul l'objet demandé n'avait aucune idée, ce qui impliquerait un défaut de différent)Si vous
__eq__
n'utilisez pas deNotImplemented
retours, cela fonctionne (avec une surcharge insignifiante), s'il l'utiliseNotImplemented
parfois, cela le gère correctement. Et la vérification de la version de Python signifie que si la classe estimport
-ed dans Python 3, elle__ne__
n'est pas définie, ce qui permet à l'__ne__
implémentation de secours native et efficace de Python (une version C de ce qui précède) de prendre le relais.Pourquoi cela est nécessaire
Règles de surcharge Python
L'explication de la raison pour laquelle vous faites cela au lieu d'autres solutions est quelque peu obscure. Python a quelques règles générales sur la surcharge des opérateurs, et des opérateurs de comparaison en particulier:
LHS OP RHS
, essayezLHS.__op__(RHS)
, et si cela revientNotImplemented
, essayezRHS.__rop__(LHS)
. Exception: siRHS
est une sous-classe deLHS
la classe de, alors testez d'RHS.__rop__(LHS)
abord . Dans le cas des opérateurs de comparaison,__eq__
et__ne__
sont leurs propres "cordes" (donc l'ordre de test pour__ne__
estLHS.__ne__(RHS)
, alorsRHS.__ne__(LHS)
, inversé siRHS
est une sous-classe deLHS
la classe de)LHS.__eq__(RHS)
retourTrue
n'implique pas deLHS.__ne__(RHS)
retoursFalse
(en fait, les opérateurs ne sont même pas obligés de retourner des valeurs booléennes; les ORM comme SQLAlchemy ne le font pas intentionnellement, ce qui permet une syntaxe de requête plus expressive). Depuis Python 3, l'__ne__
implémentation par défaut se comporte de cette façon, mais ce n'est pas contractuel; vous pouvez remplacer__ne__
par des moyens qui ne sont pas strictement opposés à__eq__
.Comment cela s'applique à la surcharge des comparateurs
Ainsi, lorsque vous surchargez un opérateur, vous avez deux tâches:
NotImplemented
, afin que Python puisse déléguer à l'implémentation de l'autre opérandeLe problème avec
not self.__eq__(other)
ne délègue jamais à l'autre côté (et est incorrect s'il
__eq__
retourne correctementNotImplemented
). Quandself.__eq__(other)
revientNotImplemented
(ce qui est "véridique"), vous retournez silencieusementFalse
, doncA() != something_A_knows_nothing_about
retourneFalse
, quand il aurait dû vérifier s'ilsomething_A_knows_nothing_about
savait comment comparer aux instances deA
, et si ce n'est pas le cas, il aurait dû revenirTrue
(car si aucune des deux parties ne sait comment comparer les uns aux autres, ils sont considérés comme différents les uns des autres). SiA.__eq__
est incorrectement implémenté (retourneFalse
au lieu deNotImplemented
quand il ne reconnaît pas l'autre côté), alors c'est "correct" duA
point de vue de, retournantTrue
(puisqueA
ne pense pas que c'est égal, donc ce n'est pas égal), mais cela pourrait être faux desomething_A_knows_nothing_about
le point de vue de, puisqu'il n'a même jamais demandésomething_A_knows_nothing_about
;A() != something_A_knows_nothing_about
se termineTrue
, maissomething_A_knows_nothing_about != A()
pourraitFalse
, ou toute autre valeur de retour.Le problème avec
not self == other
est plus subtile. Cela va être correct pour 99% des classes, y compris toutes les classes pour lesquelles
__ne__
est l'inverse logique de__eq__
. Maisnot self == other
enfreint les deux règles mentionnées ci-dessus, ce qui signifie que pour les classes où__ne__
n'est pas l'inverse logique de__eq__
, les résultats sont à nouveau non symétriques, car on ne demande jamais à l'un des opérandes s'il peut implémenter__ne__
du tout, même si l'autre l'opérande ne peut pas. L'exemple le plus simple est une classe bizarre qui retourneFalse
pour toutes les comparaisons, doncA() == Incomparable()
et lesA() != Incomparable()
deux retournentFalse
. Avec une implémentation correcte deA.__ne__
(une qui retourneNotImplemented
quand elle ne sait pas comment faire la comparaison), la relation est symétrique;A() != Incomparable()
etIncomparable() != A()
d'accord sur le résultat (car dans le premier cas,A.__ne__
retourneNotImplemented
, puisIncomparable.__ne__
retourneFalse
, tandis que dans le second,Incomparable.__ne__
retourneFalse
directement). Mais quandA.__ne__
est implémenté en tant quereturn not self == other
,A() != Incomparable()
renvoieTrue
(carA.__eq__
renvoie, nonNotImplemented
, puisIncomparable.__eq__
retourneFalse
etA.__ne__
inverse cela àTrue
), tandis queIncomparable() != A()
renvoieFalse.
Vous pouvez voir un exemple de cela en action ici .
Évidemment, une classe qui revient toujours
False
pour les deux__eq__
et qui__ne__
est un peu étrange. Mais comme mentionné précédemment,__eq__
et vous__ne__
n'avez même pas besoin de retournerTrue
/False
; l'ORM SQLAlchemy a des classes avec des comparateurs qui retournent un objet proxy spécial pour la construction de requêtes, pasTrue
/ pasFalse
du tout (ils sont "véridiques" s'ils sont évalués dans un contexte booléen, mais ils ne sont jamais censés être évalués dans un tel contexte).En ne surcharge pas
__ne__
correctement, vous allez briser les classes de ce genre, comme le code:fonctionnera (en supposant que SQLAlchemy sache comment insérer
MyClassWithBadNE
dans une chaîne SQL du tout; cela peut être fait avec des adaptateurs de type sansMyClassWithBadNE
avoir à coopérer du tout), en passant l'objet proxy attendu àfilter
, tandis que:finira par passer
filter
un simpleFalse
, carself == other
retourne un objet proxy, etnot self == other
convertit simplement l'objet proxy de vérité enFalse
. Heureusement,filter
lève une exception sur le traitement d'arguments invalides commeFalse
. Bien que je sois sûr que beaucoup diront que celaMyTable.fieldname
devrait être systématiquement sur le côté gauche de la comparaison, le fait demeure qu'il n'y a aucune raison programmatique pour appliquer cela dans le cas général, et un générique correct__ne__
fonctionnera dans les deux cas, alors qu'ilreturn not self == other
ne fonctionne que dans un seul arrangement.la source
Réponse courte: oui (mais lisez la documentation pour bien faire les choses)
L'implémentation de la
__ne__
méthode par ShadowRanger est la bonne (et il se trouve que c'est l'implémentation par défaut de la__ne__
méthode depuis Python 3.4):Pourquoi? Parce qu'il garde une propriété mathématique importante, la symétrie de l'
!=
opérateur. Cet opérateur est binaire, donc son résultat doit dépendre du type dynamique des deux opérandes, pas seulement d'un. Ceci est implémenté via une double répartition pour les langages de programmation permettant une répartition multiple (comme Julia ). En Python qui n'autorise qu'une seule distribution, la double distribution est simulée pour les méthodes numériques et les méthodes de comparaison riches en renvoyant la valeurNotImplemented
dans les méthodes d'implémentation qui ne prennent pas en charge le type de l'autre opérande; l'interpréteur essaiera alors la méthode reflétée de l'autre opérande.L'implémentation
not self == other
de la__ne__
méthode par Aaron Hall est incorrecte car elle supprime la symétrie de l'!=
opérateur. En effet, il ne peut jamais retournerNotImplemented
(not NotImplemented
estFalse
) et donc la__ne__
méthode avec une priorité plus élevée ne peut jamais se rabattre sur la__ne__
méthode avec une priorité inférieure. Auparavant, il s'agissaitnot self == other
de l'implémentation Python 3 par défaut de la__ne__
méthode, mais c'était un bogue qui a été corrigé dans Python 3.4 en janvier 2015, comme ShadowRanger l'a remarqué (voir le numéro 21408 ).Implémentation des opérateurs de comparaison
La référence du langage Python pour Python 3 indique dans son chapitre III Modèle de données :
Traduire cela en code Python donne (en utilisant
operator_eq
for==
,operator_ne
for!=
,operator_lt
for<
,operator_gt
for>
,operator_le
for<=
etoperator_ge
for>=
):Implémentation par défaut des méthodes de comparaison
La documentation ajoute:
La mise en œuvre par défaut des méthodes de comparaison (
__eq__
,__ne__
,__lt__
,__gt__
,__le__
et__ge__
) peut donc être donnée par:C'est donc la bonne implémentation de la
__ne__
méthode. Et il ne revient pas toujours l'inverse de la__eq__
méthode , car lorsque la__eq__
méthode retourneNotImplemented
, son inversenot NotImplemented
estFalse
(commebool(NotImplemented)
estTrue
) au lieu du produit souhaitéNotImplemented
.Implémentations incorrectes de
__ne__
Comme Aaron Hall l'a démontré ci-dessus, ce
not self.__eq__(other)
n'est pas l'implémentation par défaut de la__ne__
méthode. Mais non plusnot self == other
. Ce dernier est démontré ci-dessous en comparant le comportement de l'implémentation par défaut avec le comportement de l'not self == other
implémentation dans deux cas:__eq__
méthode retourneNotImplemented
;__eq__
méthode renvoie une valeur différente deNotImplemented
.Implémentation par défaut
Voyons ce qui se passe lorsque la
A.__ne__
méthode utilise l'implémentation par défaut et que laA.__eq__
méthode retourneNotImplemented
:!=
appelsA.__ne__
.A.__ne__
appelsA.__eq__
.A.__eq__
revientNotImplemented
.!=
appelsB.__ne__
.B.__ne__
revient"B.__ne__"
.Cela montre que lorsque la
A.__eq__
méthode retourneNotImplemented
, laA.__ne__
méthode retombe sur laB.__ne__
méthode.Voyons maintenant ce qui se passe lorsque la
A.__ne__
méthode utilise l'implémentation par défaut et que laA.__eq__
méthode renvoie une valeur différente deNotImplemented
:!=
appelsA.__ne__
.A.__ne__
appelsA.__eq__
.A.__eq__
revientTrue
.!=
revientnot True
, c'est-à-direFalse
.Cela montre que dans ce cas, la
A.__ne__
méthode renvoie l'inverse de laA.__eq__
méthode. Ainsi, la__ne__
méthode se comporte comme annoncé dans la documentation.Le remplacement de l'implémentation par défaut de la
A.__ne__
méthode par l'implémentation correcte donnée ci-dessus donne les mêmes résultats.not self == other
la mise en oeuvreVoyons ce qui se passe lors du remplacement de l'implémentation par défaut de la
A.__ne__
méthode avec l'not self == other
implémentation et laA.__eq__
méthode retourneNotImplemented
:!=
appelsA.__ne__
.A.__ne__
appels==
.==
appelsA.__eq__
.A.__eq__
revientNotImplemented
.==
appelsB.__eq__
.B.__eq__
revientNotImplemented
.==
revientA() is B()
, c'est-à-direFalse
.A.__ne__
revientnot False
, c'est-à-direTrue
.L'implémentation par défaut de la
__ne__
méthode renvoyée"B.__ne__"
, nonTrue
.Voyons maintenant ce qui se passe lors du remplacement de l'implémentation par défaut de la
A.__ne__
méthode par l'not self == other
implémentation et laA.__eq__
méthode renvoie une valeur différente deNotImplemented
:!=
appelsA.__ne__
.A.__ne__
appels==
.==
appelsA.__eq__
.A.__eq__
revientTrue
.A.__ne__
revientnot True
, c'est-à-direFalse
.L'implémentation par défaut de la
__ne__
méthode est également renvoyéeFalse
dans ce cas.Étant donné que cette implémentation ne parvient pas à répliquer le comportement de l'implémentation par défaut de la
__ne__
méthode lorsque la__eq__
méthode retourneNotImplemented
, elle est incorrecte.la source
__ne__
méthode lorsque la__eq__
méthode renvoie NotImplemented, il est incorrect.» -A
définit l'égalité inconditionnelle. Ainsi,A() == B()
. AinsiA() != B()
devrait être faux , et il est . Les exemples donnés sont pathologiques (c'est-à-dire__ne__
qu'ils ne doivent pas renvoyer de chaîne, et__eq__
ne doivent pas dépendre de__ne__
- ils__ne__
doivent plutôt dépendre de__eq__
, qui est l'attente par défaut dans Python 3). Je suis toujours -1 sur cette réponse jusqu'à ce que vous puissiez changer d'avis.NotImplemented
si elle n'implémente pas l'opération pour une paire d'arguments donnée. Par convention,False
etTrue
sont renvoyées pour une comparaison réussie. Cependant, ces méthodes peuvent renvoyer n'importe quelle valeur , donc si l'opérateur de comparaison est utilisé dans un contexte booléen (par exemple, dans la condition d'une instruction if), Python appellerabool()
la valeur pour déterminer si le résultat est vrai ou faux. "__ne__
tue une propriété mathématique importante, la symétrie de l'!=
opérateur. Cet opérateur est binaire, donc son résultat doit dépendre du type dynamique des deux opérandes, pas d'un seul. Ceci est correctement implémenté dans les langages de programmation via le double envoi pour le langage permettant l' envoi multiple . En Python qui n'autorise qu'une seule distribution, la double distribution est simulée en renvoyant laNotImplemented
valeur.B
,, qui renvoie une chaîne de vérité sur toutes les vérifications de__ne__
, etA
qui retourneTrue
sur toutes les vérifications de__eq__
. C'est une contradiction pathologique. Dans une telle contradiction, il serait préférable de soulever une exception. A l'insu deB
,A
n'est pas tenu de respecterB
la mise en œuvre de__ne__
à des fins de symétrie. À ce stade de l'exemple, la façon dont lesA
outils__ne__
ne m'intéresse pas. Veuillez trouver un cas pratique et non pathologique pour faire valoir votre point de vue. J'ai mis à jour ma réponse pour m'adresser à vous.__ne__
fonctionne dans des cas d'utilisation typiques ne le rend pas juste. Les Boeing 737 MAX ont effectué 500 000 vols avant les accidents…Si tous
__eq__
,__ne__
,__lt__
,__ge__
,__le__
et du__gt__
sens pour la classe, puis juste mettre en œuvre à la__cmp__
place. Sinon, faites ce que vous faites, à cause du commentaire de Daniel DiPaolo (pendant que je le testais au lieu de le rechercher;))la source
__cmp__()
méthode spéciale n'est plus prise en charge dans Python 3.x, vous devez donc vous habituer à utiliser les opérateurs de comparaison enrichis.