Python, dois-je mettre en œuvre l'opérateur __ne __ () basé sur __eq__?
-
08-10-2019 - |
Question
J'ai une classe où je veux remplacer l'opérateur __eq__()
. Il semble logique que je remplacer l'opérateur __ne__()
aussi bien, mais est-il logique de mettre en œuvre __ne__
basé sur __eq__
en tant que telle?
class A:
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
Ou est-il quelque chose que je suis absent de la façon dont Python utilise ces opérateurs qui rend ce pas une bonne idée?
La solution
Oui, c'est parfaitement bien. En fait, la documentation vous invite à définir __ne__
lorsque vous définissez __eq__
:
Il n'y a pas de relations implicites parmi les opérateurs de comparaison. le vérité de
x==y
ne signifie pas quex!=y
c'est faux. En conséquence, lors de la définition__eq__()
, on devrait également définir__ne__()
afin que les opérateurs se comportent comme prévu.
Dans beaucoup de cas (comme celui-ci), il sera aussi simple que niant le résultat de __eq__
, mais pas toujours.
Autres conseils
Python, dois-je mettre en œuvre l'opérateur de
__ne__()
basé sur__eq__
?
Réponse courte: Non Utilisation ==
au lieu du __eq__
En Python 3, !=
est la négation de ==
par défaut, de sorte que vous n'êtes même pas obligé d'écrire un __ne__
et la documentation n'est plus opiniâtre d'écrire un.
D'une manière générale, pour Python code 3 uniquement, ne pas écrire un sauf si vous avez besoin d'éclipser la mise en œuvre des parents, par exemple pour un objet intégré.
C'est, garder à l'esprit commentaire de Raymond Hettinger :
La méthode
__ne__
suit automatiquement__eq__
seulement si__ne__
n'est pas déjà défini dans une superclasse. Donc, si vous êtes héritant d'une commande intégrée, il est préférable de passer outre les deux.
Si vous avez besoin de votre code en Python 2, suivez la recommandation pour Python 2 et il en Python 3 très bien.
En Python 2, Python lui-même ne pas mettre en œuvre automatiquement toute opération en termes d'une autre - par conséquent, vous devez définir l'__ne__
en termes de ==
au lieu du __eq__
.
PAR EXEMPLE.
class A(object):
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self == other # NOT `return not self.__eq__(other)`
Voir la preuve que
- la mise en œuvre opérateur
__ne__()
basé sur__eq__
et - ne pas appliquer
__ne__
en Python 2 du tout
fournit un comportement incorrect dans la démonstration ci-dessous.
Réponse longue
Le documentation pour Python 2 dit:
Il n'y a pas de relations implicites entre les opérateurs de comparaison. le vérité de
x==y
ne signifie pas quex!=y
est faux. En conséquence, lorsque définissant__eq__()
, il faut également définir__ne__()
de sorte que le les opérateurs se comportent comme prévu.
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:
Par défaut, les délégués de
__ne__()
à__eq__()
et inversera le résultat à moins qu'il soitNotImplemented
.
et dans la section "nouveautés" , nous voyons ce comportement a changé:
!=
retourne maintenant à l'opposé de==
, à moins que revient==
NotImplemented
.
Pour la mise en œuvre __ne__
, nous préférons utiliser l'opérateur ==
au lieu d'utiliser la méthode __eq__
directement de sorte que si self.__eq__(other)
d'un rendement de sous-classe NotImplemented
pour le type vérifié, Python correctement vérifier other.__eq__(self)
A partir de la documentation :
L'objet NotImplemented
Ce type a une valeur unique. Il y a un seul objet avec cette valeur. Cet objet est accessible par le haut-nom
NotImplemented
. méthodes numériques et riches méthodes de comparaison peuvent retourner cette valeur si elles ne mettent pas en œuvre l'opération pour les opérandes à condition de. (L'interprète essayera alors l'opération réfléchie, ou un autre repli, selon l'opérateur). Sa valeur de vérité est vrai.
Lorsque donné un opérateur de comparaison riche,si elles ne sont pas du même type, vérifie si le python other
est un sous-type, et si elle a défini que l'opérateur, il utilise la méthode de l'other
premier (inverse pour <
, <=
, >=
et >
). Si NotImplemented
est retourné, puis il utilise la méthode de l'inverse. (Il fait pas pour vérifier la même méthode deux fois.) En utilisant l'opérateur ==
permet cette logique d'avoir lieu.
Les attentes
sémantiquement, vous devez mettre en œuvre __ne__
en termes de contrôle pour l'égalité parce que les utilisateurs de votre classe s'attendront les fonctions suivantes soient équivalentes pour toutes les instances de A:.
def negation_of_equals(inst1, inst2):
"""always should return same as not_equals(inst1, inst2)"""
return not inst1 == inst2
def not_equals(inst1, inst2):
"""always should return same as negation_of_equals(inst1, inst2)"""
return inst1 != inst2
C'est, à la fois des fonctions ci-dessus devrait toujours le même résultat. Mais cela dépend du programmeur.
Démonstration de comportement inattendu lors de la définition __ne__
basé sur __eq__
:
D'abord la configuration:
class BaseEquatable(object):
def __init__(self, x):
self.x = x
def __eq__(self, other):
return isinstance(other, BaseEquatable) and self.x == other.x
class ComparableWrong(BaseEquatable):
def __ne__(self, other):
return not self.__eq__(other)
class ComparableRight(BaseEquatable):
def __ne__(self, other):
return not self == other
class EqMixin(object):
def __eq__(self, other):
"""override Base __eq__ & bounce to other for __eq__, e.g.
if issubclass(type(self), type(other)): # True in this example
"""
return NotImplemented
class ChildComparableWrong(EqMixin, ComparableWrong):
"""__ne__ the wrong way (__eq__ directly)"""
class ChildComparableRight(EqMixin, ComparableRight):
"""__ne__ the right way (uses ==)"""
class ChildComparablePy3(EqMixin, BaseEquatable):
"""No __ne__, only right in Python 3."""
instancier des instances non équivalentes:
right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)
Comportement attendu:
(Note: alors que chaque seconde affirmation de chacun des ci-dessous est équivalent et donc logiquement redondant à celui qui le précédait, je les inclure pour démontrer que pour ne compte pas quand on est une sous-classe de l'autre . )
Ces cas ont __ne__
mis en œuvre avec ==
:
assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1
Ces cas, des contrôles effectués sous Python 3, fonctionne aussi correctement:
assert not right_py3_1 == right_py3_2
assert not right_py3_2 == right_py3_1
assert right_py3_1 != right_py3_2
assert right_py3_2 != right_py3_1
Et rappelez-vous que ceux-ci ont mis en œuvre avec __ne__
__eq__
- alors que c'est le comportement attendu, la mise en œuvre est incorrect:
assert not wrong1 == wrong2 # These are contradicted by the
assert not wrong2 == wrong1 # below unexpected behavior!
Comportement inattendu:
Notez que cette comparaison contredit les comparaisons ci-dessus (not wrong1 == wrong2
).
>>> assert wrong1 != wrong2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
et
>>> assert wrong2 != wrong1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
Ne sautez pas __ne__
en Python 2
Pour preuve que vous ne devriez pas ignorer la mise en œuvre __ne__
en Python 2, voir ces objets équivalents:
>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True
Le résultat ci-dessus doit être False
!
Source Python 3
La valeur par défaut la mise en œuvre CPython pour __ne__
est en typeobject.c
dans object_richcompare
:
case Py_NE:
/* By default, __ne__() delegates to __eq__() and inverts the result,
unless the latter returns NotImplemented. */
if (self->ob_type->tp_richcompare == NULL) {
res = Py_NotImplemented;
Py_INCREF(res);
break;
}
res = (*self->ob_type->tp_richcompare)(self, other, Py_EQ);
if (res != NULL && res != Py_NotImplemented) {
int ok = PyObject_IsTrue(res);
Py_DECREF(res);
if (ok < 0)
res = NULL;
else {
if (ok)
res = Py_False;
else
res = Py_True;
Py_INCREF(res);
}
}
Nous voyons ici
Mais la __ne__
par défaut utilise __eq__
?
default __ne__
Détail de la mise en œuvre de Python 3 au niveau C utilise __eq__
parce que le niveau supérieur ==
( PyObject_RichCompare ) serait moins efficace -. et, par conséquent, il doit également gérer NotImplemented
Si __eq__
est correctement mis en œuvre, la négation de ==
est correcte -. Et il nous permet d'éviter les détails de mise en œuvre de faible niveau dans notre __ne__
Utilisation ==
nous permet de garder notre logique de bas niveau dans un place et éviter répondre NotImplemented
à __ne__
.
On peut supposer à tort que ==
peut retourner NotImplemented
.
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)
class Foo:
def __ne__(self, other):
return NotImplemented
__eq__ = __ne__
f = Foo()
f2 = Foo()
Et les comparaisons:
>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True
Performance
Ne pas prendre ma parole, nous allons voir ce qui est plus performant:
class CLevel:
"Use default logic programmed in C"
class HighLevelPython:
def __ne__(self, other):
return not self == other
class LowLevelPython:
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
def c_level():
cl = CLevel()
return lambda: cl != cl
def high_level_python():
hlp = HighLevelPython()
return lambda: hlp != hlp
def low_level_python():
llp = LowLevelPython()
return lambda: llp != llp
Je pense que ces chiffres de performance parlent d'eux mêmes:
>>> import timeit
>>> min(timeit.repeat(c_level()))
0.09377292497083545
>>> min(timeit.repeat(high_level_python()))
0.2654011140111834
>>> min(timeit.repeat(low_level_python()))
0.3378178110579029
Cela rend sense quand on considère que low_level_python
fait logique en Python qui seraient autrement traitées au niveau C.
Réponse à certains critiques
Un autre écrit de REPONDEUR:
mise en œuvre d'Aaron Salle de
not self == other
de la méthode__ne__
est incorrecte car il ne peut jamais revenirNotImplemented
(not NotImplemented
estFalse
) et donc la méthode__ne__
qui a la priorité ne peut jamais se replier sur la méthode__ne__
qui ne possède pas la priorité.
Avoir __ne__
jamais NotImplemented
de retour ne fait pas incorrect. , Nous prenons en charge au lieu des priorités avec NotImplemented
par la vérification de l'égalité avec ==
. En supposant ==
est correctement mis en œuvre, nous avons terminé.
not self == other
utilisé pour la mise en œuvre par défaut Python 3 de la méthode__ne__
mais il était un bug et il a été corrigé dans Python 3.4 en Janvier 2015, comme ShadowRanger remarqué (voir le numéro # 21408).
Eh bien, nous allons expliquer cela.
Comme indiqué précédemment, Python 3 par défaut poignées __ne__
en vérifiant d'abord si les rendements de self.__eq__(other)
NotImplemented
(un singleton) - qui doit être vérifiée avec is
et retourné si oui, sinon il doit retourner l'inverse. Voici que la logique écrite comme mixin classe:
class CStyle__ne__:
"""Mixin that provides __ne__ functionality equivalent to
the builtin functionality
"""
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
Ceci est nécessaire pour la correction pour le niveau C API Python, et il a été introduit en Python 3, faire
- les méthodes de
__ne__
dans ce patch pour fermer Numéro 21408 et - les méthodes de
__ne__
dans le suivi sur le nettoyage enlevé
redondante. Toutes les méthodes de __ne__
pertinentes ont été supprimés, y compris ceux qui mettent en œuvre leur propre contrôle, ainsi que ceux que délégué à __eq__
directement ou via ==
-. Et ==
était le moyen le plus courant de le faire
Conclusion
Pour Python 2 code compatible, utilisez ==
pour mettre en œuvre __ne__
. Il est plus:
- correct
- simple,
- performant
En Python 3 uniquement, utilisez la négation de bas niveau au niveau C - il est même plus simple et performant (bien que le programmeur est chargé de déterminer si elle est correcte ).
Encore une fois, faire pas logique écriture faible niveau haut niveau Python.
Pour la petite histoire, un __ne__
portable canoniquement correcte et croix py2 / PY3 ressemblerait à ceci:
import sys
class ...:
...
def __eq__(self, other):
...
if sys.version_info[0] == 2:
def __ne__(self, other):
equal = self.__eq__(other)
return equal if equal is NotImplemented else not equal
Cela fonctionne avec tout __eq__
vous pouvez définir:
- Contrairement à
not (self == other)
, ne gêne pas dans certains cas ennuyeux / complexes impliquant des comparaisons où l'une des classes concernées ne signifie pas que le résultat de__ne__
est le même que le résultat denot
sur__eq__
(par exemple ORM de SQLAlchemy, où à la fois__eq__
et__ne__
retour des objets proxy spéciaux, nonTrue
ouFalse
, et en essayant denot
le résultat de__eq__
retourneraitFalse
, plutôt que l'objet proxy correct). - Contrairement à
not self.__eq__(other)
, cela correctement délègue au__ne__
de l'autre instance lors du retour deself.__eq__
NotImplemented
(not self.__eq__(other)
serait erroné supplémentaire, parce queNotImplemented
est truthy, alors quand__eq__
ne savait pas comment effectuer la comparaison,__ne__
retourneraitFalse
, ce qui implique que les deux objets étaient égaux, alors qu'en fait le seul objet demandé avait aucune idée, ce qui impliquerait un défaut de ne pas égal)
Si votre __eq__
ne pas utiliser retourne NotImplemented
, cela fonctionne (avec les frais généraux de sens), si elle ne utilise NotImplemented
parfois, ce poignées correctement. Et les moyens de contrôle de version Python que si la classe est import
-ed en Python 3, __ne__
n'est pas définie, ce qui permet natif de Python, fallback efficace la mise en œuvre de __ne__
(une version C de ce qui précède) de prendre en charge.
Pourquoi est-ce nécessaire
règles Python surcharge
L'explication des raisons pour lesquelles vous faites cela au lieu d'autres solutions est un peu Arcane. Python a quelques règles générales sur les opérateurs et les opérateurs, la surcharge de comparaison, en particulier:
- (S'applique à tous les opérateurs) Lors de l'exécution
LHS OP RHS
,LHS.__op__(RHS)
d'essayer, et si ce rendementNotImplemented
, essayezRHS.__rop__(LHS)
. Exception: SiRHS
est une sous-classe de la classe deLHS
, puisRHS.__rop__(LHS)
test first . Dans le cas des opérateurs de comparaison,__eq__
et__ne__
sont leurs propres s « ROP » (donc l'ordre d'essai pour__ne__
estLHS.__ne__(RHS)
, puisRHS.__ne__(LHS)
, inversée siRHS
est une sous-classe de la classe deLHS
) - En dehors de l'idée de l'opérateur « troqué », il n'y a pas de relation implicite entre les opérateurs. Même par exemple de la même classe,
LHS.__eq__(RHS)
retourTrue
ne signifie pas le retour deLHS.__ne__(RHS)
False
(en fait, les opérateurs ne sont même pas tenus de retourner les valeurs booléennes, ORM comme SQLAlchemy ne volontairement pas, ce qui permet une syntaxe de requête plus expressif). A partir de Python 3, la mise en œuvre de__ne__
par défaut se comporte de cette façon, mais ce n'est pas contractuel; vous pouvez remplacer__ne__
d'une manière qui ne sont pas contraires strictes__eq__
.
Comment cela s'applique à la surcharge des comparateurs
Ainsi, lorsque vous surchargez un opérateur, vous avez deux emplois:
- Si vous savez comment mettre en œuvre l'opération vous-même, faites-le, en utilisant uniquement votre propre connaissance de la façon de faire la comparaison (jamais déléguer, implicitement ou explicitement, de l'autre côté de l'opération; vous risqueriez incorrection et / ou une récursion infinie, selon la façon dont vous le faites)
- Si vous ne pas savoir comment mettre en œuvre l'opération vous-même, toujours
NotImplemented
de retour, de sorte que Python peut déléguer à la mise en œuvre de l'autre opérande
Le problème avec not self.__eq__(other)
def __ne__(self, other):
return not self.__eq__(other)
jamais les délégués de l'autre côté (et est incorrecte si __eq__
retourne correctement NotImplemented
). Lorsque self.__eq__(other)
retours NotImplemented
(qui est « truthy »), vous revenez en silence False
, alors retourne A() != something_A_knows_nothing_about
False
, quand il aurait dû vérifier si something_A_knows_nothing_about
savait comment comparer aux instances de A
, et si elle ne le fait pas, il aurait dû retourner True
(car si aucune des deux parties sait comparer à l'autre, ils sont considérés comme pas égaux les uns aux autres). Si A.__eq__
est correctement mis en œuvre (retour False
au lieu de NotImplemented
quand il ne reconnaît pas l'autre côté), alors ceci est « correcte » du point de vue de A
, retour True
(depuis A
ne pense pas que c'est égal, il est donc pas égal), mais il pourrait être mal du point de vue de something_A_knows_nothing_about
, car il n'a même jamais demandé something_A_knows_nothing_about
; se termine A() != something_A_knows_nothing_about
jusqu'à True
, mais something_A_knows_nothing_about != A()
pourrait False
, ou toute autre valeur de retour.
Le problème avec not self == other
def __ne__(self, other):
return not self == other
est plus subtile. Il va être correct pour 99% des classes, y compris toutes les classes pour lesquelles __ne__
est l'inverse logique de __eq__
. Mais les pauses not self == other
les deux règles mentionnées ci-dessus, ce qui signifie pour les classes où __ne__
ne sont pas l'inverse logique de __eq__
, les résultats sont encore une fois non réfléchi, parce que l'un des opérandes est jamais demandé si il peut mettre en œuvre __ne__
du tout, même si l'autre opérande ne peut pas. L'exemple le plus simple est une classe weirdo qui retourne False
pour tous comparaisons, si A() == Incomparable()
et A() != Incomparable()
les deux False
de retour. Avec une mise en œuvre correcte de A.__ne__
(celui qui NotImplemented
des rendements quand il ne sait pas comment faire la comparaison), la relation est réflexive; A() != Incomparable()
et Incomparable() != A()
sont d'accord sur les résultats (parce que dans le premier cas, retourne A.__ne__
NotImplemented
, retourne alors Incomparable.__ne__
False
, alors que dans ce dernier, retourne Incomparable.__ne__
False
directement). Mais quand A.__ne__
est mis en œuvre return not self == other
, retourne A() != Incomparable()
True
(parce que les rendements de A.__eq__
, pas NotImplemented
, retourne alors Incomparable.__eq__
False
et A.__ne__
à True
que Inverse), alors que les revenus Incomparable() != A()
False.
Vous pouvez voir un exemple dans l'action ici .
De toute évidence, une classe qui retourne toujours False
pour les __eq__
et __ne__
est un peu étrange. Mais comme mentionné précédemment, __eq__
et __ne__
ne même pas besoin de retourner True
/ False
; le SQLAlchemy ORM a des classes avec des comparateurs qui retourne un objet proxy spécial pour la construction des requêtes, et non True
/ False
du tout (ils sont « truthy » si évaluées dans un contexte booléen, mais ils ne sont jamais censés être évalués dans un tel contexte ).
En ne pas surcharger __ne__
correctement, vous cours de rupture de ce genre, comme le code:
results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
fonctionnera (en supposant SQLAlchemy sait comment insérer MyClassWithBadNE
dans une chaîne SQL du tout, ce qui peut être fait avec les cartes de type sans MyClassWithBadNE
avoir à coopérer à tous), en passant l'objet proxy devrait filter
, alors que:
results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
finira par passer filter
une False
plaine, parce que self == other
retourne un objet proxy et not self == other
convertit simplement l'objet proxy truthy à False
. Si tout va bien, filter
renvoie une exception à être manipulés comme des arguments non valides False
. Même si je suis sûr que beaucoup diront que MyTable.fieldname
devrait être constamment sur le côté gauche de la comparaison, les restes de fait qu'il n'y a aucune raison programmatique pour faire respecter ce dans le cas général, et un bon générique __ne__
fonctionnera de toute façon, alors que return not self == other
ne fonctionne que dans un arrangement.
Réponse courte: oui (mais lisez la documentation de le faire à droite)
La mise en œuvre de ShadowRanger de la méthode __ne__
est correcte (dans le sens où il se comporte exactement comme la implémentation par défaut Python 3):
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
mise en œuvre d'Aaron Salle de not self == other
de la méthode __ne__
est incorrecte car il ne peut jamais revenir NotImplemented
(not NotImplemented
est False
) et donc la méthode __ne__
qui a la priorité ne peut jamais se replier sur la méthode __ne__
qui ne possède pas la priorité. not self == other
utilisé pour la mise en œuvre par défaut Python 3 de la méthode __ne__
mais il était un bug et il a été corrigé dans Python 3.4 en Janvier 2015, comme ShadowRanger remarqué (voir numéro # 21408 ).
Mise en œuvre des opérateurs de comparaison
Python Langage pour Python 3 états dans son chapitre III modèle de données :
object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)
Ce sont les méthodes dites « comparaison riches ». la correspondance entre les symboles de l'opérateur et les noms de méthode est la suivante: appels
x<y
x.__lt__(y)
,x<=y
appellex.__le__(y)
,x==y
appellex.__eq__(y)
,x!=y
appellex.__ne__(y)
, les appelsx>y
x.__gt__(y)
etx>=y
appellex.__ge__(y)
.Une méthode riche de comparaison peut renvoyer le
NotImplemented
singleton si il ne met pas en œuvre l'opération pour une paire d'arguments.Il n'y a pas des versions de ces arguments permutés méthodes (à utiliser lorsque l'argument gauche ne prend pas en charge l'opération, mais le droit l'argument ne); plutôt,
__lt__()
et__gt__()
sont uns des autres réflexion,__le__()
et__ge__()
sont la réflexion de l'autre, et__eq__()
et__ne__()
sont leur propre réflexion. Si les opérandes sont de différents types, et le type de droit opérande est un direct ou sous-classe indirecte du type de l'opérande gauche, le procédé réfléchie le droit opérande est prioritaire, sinon la méthode de l'opérande gauche a la priorité. subclassing virtuel n'est pas considéré.
Traduire en ce code Python donne (en utilisant operator_eq
pour ==
, operator_ne
pour !=
, operator_lt
pour <
, operator_gt
pour >
, operator_le
pour <=
et operator_ge
pour >=
):
def operator_eq(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__eq__(left)
if result is NotImplemented:
result = left.__eq__(right)
else:
result = left.__eq__(right)
if result is NotImplemented:
result = right.__eq__(left)
if result is NotImplemented:
result = left is right
return result
def operator_ne(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__ne__(left)
if result is NotImplemented:
result = left.__ne__(right)
else:
result = left.__ne__(right)
if result is NotImplemented:
result = right.__ne__(left)
if result is NotImplemented:
result = left is not right
return result
def operator_lt(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__gt__(left)
if result is NotImplemented:
result = left.__lt__(right)
else:
result = left.__lt__(right)
if result is NotImplemented:
result = right.__gt__(left)
if result is NotImplemented:
raise TypeError(f"'<' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_gt(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__lt__(left)
if result is NotImplemented:
result = left.__gt__(right)
else:
result = left.__gt__(right)
if result is NotImplemented:
result = right.__lt__(left)
if result is NotImplemented:
raise TypeError(f"'>' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_le(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__ge__(left)
if result is NotImplemented:
result = left.__le__(right)
else:
result = left.__le__(right)
if result is NotImplemented:
result = right.__ge__(left)
if result is NotImplemented:
raise TypeError(f"'<=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_ge(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__le__(left)
if result is NotImplemented:
result = left.__ge__(right)
else:
result = left.__ge__(right)
if result is NotImplemented:
result = right.__le__(left)
if result is NotImplemented:
raise TypeError(f"'>=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
par défaut mise en œuvre des méthodes de comparaison
La documentation ajoute:
Par défaut, les délégués de
__ne__()
à__eq__()
et inversera le résultat à moins qu'il soitNotImplemented
. Il n'y a pas d'autres sous-entendus les relations entre les opérateurs de comparaison, par exemple, la vérité de(x<y or x==y)
ne signifie pasx<=y
.
La mise en œuvre par défaut des méthodes de comparaison (__eq__
, __ne__
, __lt__
, __gt__
, __le__
et __ge__
) peut ainsi être donnée par:
def __eq__(self, other):
return NotImplemented
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
def __lt__(self, other):
return NotImplemented
def __gt__(self, other):
return NotImplemented
def __le__(self, other):
return NotImplemented
def __ge__(self, other):
return NotImplemented
C'est donc la mise en œuvre correcte de la méthode __ne__
. Et il ne revient pas toujours l'inverse de la méthode __eq__
parce que quand la méthode retourne de __eq__
NotImplemented
, son not NotImplemented
inverse est False
(comme bool(NotImplemented)
est True
) au lieu de la NotImplemented
souhaitée.
implémentations incorrectes de __ne__
Aaron Hall a démontré ci-dessus, not self.__eq__(other)
n'est pas l'implémentation par défaut de la méthode __ne__
. . Mais ni est not self == other
Ce dernier est démontré ci-dessous en comparant le comportement de l'implémentation par défaut avec le comportement de la mise en œuvre de not self == other
dans deux cas:
- la méthode retourne de
__eq__
NotImplemented
; - la méthode de
__eq__
renvoie une valeur différente deNotImplemented
.
Mise en oeuvre par défaut
Voyons voir ce qui se passe lorsque la méthode A.__ne__
utilise l'implémentation par défaut et le retour de la méthode A.__eq__
NotImplemented
:
class A:
pass
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) == "B.__ne__"
-
!=
appelleA.__ne__
. -
A.__ne__
appelleA.__eq__
. - retourne
A.__eq__
NotImplemented
. -
!=
appelleB.__ne__
. - retourne
B.__ne__
"B.__ne__"
.
Cela montre que lorsque la méthode retourne de A.__eq__
NotImplemented
, la méthode A.__ne__
retombe sur la méthode B.__ne__
.
Maintenant, nous allons voir ce qui se passe lorsque la méthode A.__ne__
utilise l'implémentation par défaut et la méthode renvoie à une A.__eq__
de différentes valeur de NotImplemented
:
class A:
def __eq__(self, other):
return True
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is False
-
!=
appelleA.__ne__
. -
A.__ne__
appelleA.__eq__
. - retourne
A.__eq__
True
. - retourne
!=
not True
, qui estFalse
.
Cela montre que, dans ce cas, le procédé de A.__ne__
renvoie l'inverse de la méthode de A.__eq__
. Ainsi, les méthode de se comporte __ne__
aiment annoncés dans la documentation.
Redéfinition l'implémentation par défaut de la méthode A.__ne__
la mise en œuvre correcte donnée ci-dessus donne les mêmes résultats.
not self == other
mise en œuvre
Voyons voir ce qui se passe lors de la substitution de la mise en œuvre par défaut de la méthode A.__ne__
la mise en œuvre de not self == other
et le retour de la méthode A.__eq__
NotImplemented
:
class A:
def __ne__(self, other):
return not self == other
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is True
-
!=
appelleA.__ne__
. -
A.__ne__
appelle==
. -
==
appelleA.__eq__
. - retourne
A.__eq__
NotImplemented
. -
==
appelleB.__eq__
. - retourne
B.__eq__
NotImplemented
. - retourne
==
A() is B()
, qui estFalse
. - retourne
A.__ne__
not False
, qui estTrue
.
La mise en œuvre par défaut de la méthode __ne__
retourné "B.__ne__"
, pas True
.
Maintenant, nous allons voir ce qui se passe lors de la substitution de la mise en œuvre par défaut de la méthode A.__ne__
la mise en œuvre de not self == other
et la méthode renvoie à une A.__eq__
de différentes valeur de NotImplemented
:
class A:
def __eq__(self, other):
return True
def __ne__(self, other):
return not self == other
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is False
-
!=
appelleA.__ne__
. -
A.__ne__
appelle==
. -
==
appelleA.__eq__
. - retourne
A.__eq__
True
. - retourne
A.__ne__
not True
, qui estFalse
.
L'implémentation par défaut de la méthode de __ne__
également retourné False
dans ce cas.
Depuis cette mise en œuvre ne parvient pas à reproduire le comportement de l'implémentation par défaut du generacodméthode icetagcode lorsque la méthode retourne de __ne__
__eq__
, il est incorrect.
Si toutes __eq__
, __ne__
, __lt__
, __ge__
, __le__
et __gt__
de sens pour la classe, puis juste mettre en œuvre __cmp__
à la place. Sinon, faites comme vous faites, à cause du bit Daniel DiPaolo dit (alors que je testais au lieu de regarder vers le haut;))