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?

Était-ce utile?

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 que x!=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 que x!=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 soit NotImplemented.

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

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

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 de not sur __eq__ (par exemple ORM de SQLAlchemy, où à la fois __eq__ et __ne__ retour des objets proxy spéciaux, non True ou False, et en essayant de not le résultat de __eq__ retournerait False, 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 de self.__eq__ NotImplemented (not self.__eq__(other) serait erroné supplémentaire, parce que NotImplemented est truthy, alors quand __eq__ ne savait pas comment effectuer la comparaison, __ne__ retournerait False, 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:

  1. (S'applique à tous les opérateurs) Lors de l'exécution LHS OP RHS, LHS.__op__(RHS) d'essayer, et si ce rendement NotImplemented, essayez RHS.__rop__(LHS). Exception: Si RHS est une sous-classe de la classe de LHS, puis RHS.__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__ est LHS.__ne__(RHS), puis RHS.__ne__(LHS), inversée si RHS est une sous-classe de la classe de LHS)
  2. 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) retour True ne signifie pas le retour de LHS.__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:

  1. 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)
  2. 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 appelle x.__le__(y), x==y appelle x.__eq__(y),   x!=y appelle x.__ne__(y), les appels x>y x.__gt__(y) et x>=y   appelle x.__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 soit NotImplemented. 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 pas x<=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 de NotImplemented.

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__"
  1. != appelle A.__ne__.
  2. A.__ne__ appelle A.__eq__.
  3. retourne A.__eq__ NotImplemented.
  4. != appelle B.__ne__.
  5. 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
  1. != appelle A.__ne__.
  2. A.__ne__ appelle A.__eq__.
  3. retourne A.__eq__ True.
  4. retourne != not True, qui est False.

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
  1. != appelle A.__ne__.
  2. A.__ne__ appelle ==.
  3. == appelle A.__eq__.
  4. retourne A.__eq__ NotImplemented.
  5. == appelle B.__eq__.
  6. retourne B.__eq__ NotImplemented.
  7. retourne == A() is B(), qui est False.
  8. retourne A.__ne__ not False, qui est True.

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
  1. != appelle A.__ne__.
  2. A.__ne__ appelle ==.
  3. == appelle A.__eq__.
  4. retourne A.__eq__ True.
  5. retourne A.__ne__ not True, qui est False.

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top