Pregunta

Tengo una clase donde quiero anular el operador __eq__(). Parece tener sentido que debería reemplazar el operador __ne__() también, pero ¿tiene sentido para poner en práctica __ne__ basado en __eq__ como tal?

class A:
    def __eq__(self, other):
        return self.value == other.value

    def __ne__(self, other):
        return not self.__eq__(other)

O hay algo que me falta con la forma en Python usa estos operadores que hacen de esta no es una idea buena?

¿Fue útil?

Solución

Sí, eso está perfectamente bien. De hecho, la documentación le insta a definir __ne__ cuando se define __eq__:

No existen relaciones implícitas entre los operadores de comparación. los verdad de x==y no implica que x!=y Es falso. En consecuencia, al definir __eq__(), también se debe definir __ne__() para que los operadores se comportan como se esperaba.

En muchos casos (como éste), será tan simple como la negación del resultado de __eq__, pero no siempre.

Otros consejos

Python, que debe aplicar el operador __ne__() basado en __eq__?

Respuesta corta: No. El uso == en lugar de la __eq__

En Python 3, != es la negación de == por defecto, por lo que ni siquiera se requiere para escribir una __ne__, y la documentación ya no está obstinado en escribir uno.

En términos generales, para Python 3-único código, no escribir uno menos que necesite hacer sombra a la puesta en práctica de los padres, por ejemplo, para un objeto incorporado.

Es decir, tener en cuenta de Raymond Hettinger comentario :

El método __ne__ sigue automáticamente de __eq__ sólo si __ne__ no se ha definido en una superclase. Por lo tanto, si estás heredando de una orden interna, lo mejor es reemplazar ambos.

Si se necesita el código de trabajo en Python 2, siga la recomendación para Python 2 y funcionará en Python 3 acaba bien.

En Python 2, en sí Python no implementa automáticamente cualquier operación en términos de otra -, por lo tanto, se debe definir el __ne__ en términos de == en lugar de la __eq__. P.EJ.

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

Ver prueba de que

  • operador __ne__() implementación basada en __eq__ y
  • no implementar __ne__ en Python 2 en absoluto

proporciona un comportamiento incorrecto en la demostración a continuación.

Respuesta larga

El documentación para Python 2 dice:

No existen relaciones implícitas entre los operadores de comparación. los verdad de x==y no implica que x!=y es falsa. En consecuencia, cuando definiendo __eq__(), también hay que definir __ne__() para que el operadores se comportan como se esperaba.

Por lo que significa que si definimos __ne__ en cuanto a la inversa de __eq__, podemos conseguir un comportamiento coherente.

Esta sección de la documentación se ha actualizado para Python 3:

Por defecto, los delegados __ne__() a __eq__() e invierte el resultado a menos que sea NotImplemented.

y en el "¿qué hay de nuevo" , vemos este comportamiento ha cambiado:

  • != devuelve ahora el opuesto de ==, a menos retornos == NotImplemented.

Para implementar __ne__, preferimos utilizar el operador == en lugar de utilizar el método __eq__ directamente, por lo que si self.__eq__(other) de una subclase vuelve NotImplemented para el tipo comprobado, Python apropiada comprobar other.__eq__(self) De la documentación :

El objeto NotImplemented

Este tipo tiene un valor único. Hay un solo objeto con este valor. Este objeto se accede a través de la incorporada en el nombre NotImplemented. métodos numéricos y métodos de comparación ricos puede devolver este valor si no implementan la operación de los operandos previsto. (El intérprete vuelva a intentar la operación reflejada, o otro de reserva, dependiendo del operador). Su valor de verdad es verdadera.

Cuando se le asigne un operador de comparación rica,si no son del mismo tipo, comprueba si el pitón other es un subtipo, y si tiene que definido por el operador, se utiliza el método de la primera other (inversa para <, <=, >= y >). Si se devuelve NotImplemented, después se utiliza el método de lo contrario. (Se hace no Comprobar el mismo método dos veces.) Usando el operador == permite esta lógica a tener lugar.


Las expectativas

punto de vista semántico, se debe aplicar __ne__ en cuanto a la verificación de la igualdad ya que los usuarios de su clase se esperan las siguientes funciones son equivalentes para todas las instancias 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

Es decir, tanto de las funciones anteriores debe siempre devolver el mismo resultado. Pero esto depende del programador.

Demostración de comportamiento inesperado al definir __ne__ basado en __eq__:

En primer lugar la configuración:

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

instanciar casos no equivalentes:

right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)

Comportamiento esperado:

(Nota: mientras que cada segundo de cada afirmación de los siguientes es equivalente, por lo que lógicamente redundante a la anterior, estoy ellos, incluyendo a demostrar que Para no importa cuando se trata de una subclase de la otra . )

Estas instancias han __ne__ implementado con ==:

assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1

estos casos, pruebas bajo Python 3, también funciona correctamente:

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

Y recordemos que estas han implementado con __ne__ __eq__ - si bien este es el comportamiento esperado, la aplicación es incorrecta:

assert not wrong1 == wrong2         # These are contradicted by the
assert not wrong2 == wrong1         # below unexpected behavior!

Comportamiento inesperado:

Tenga en cuenta que esta comparación contradice las comparaciones anteriores (not wrong1 == wrong2).

>>> assert wrong1 != wrong2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

y

>>> assert wrong2 != wrong1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

No se salte __ne__ en Python 2

Para obtener evidencia de que no se debe omitir __ne__ implementación en Python 2, ver estos objetos equivalentes:

>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True

El resultado anterior debe ser False!

Python 3 fuente

La aplicación por defecto para CPython __ne__ está en typeobject.c en 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);
            }
        }

Aquí vemos

Pero el defecto usos __ne__ __eq__?

detalle de implementación __ne__ por defecto de Python 3 en los usos de nivel C __eq__ porque el == más alto nivel ( PyObject_RichCompare ) sería menos eficiente -. y por lo tanto también debe manejar NotImplemented

Si __eq__ se implementa correctamente, entonces la negación de == también es correcto -. Y nos permite evitar detalles de bajo nivel de implantación en nuestro __ne__

s Uso == permite mantener nuestro nivel lógico bajo en un lugar, y evitar abordar NotImplemented en __ne__.

Uno podría asumir erróneamente que == puede devolver NotImplemented.

Es realmente utiliza la misma lógica que la implementación predeterminada de __eq__, que comprueba la identidad (ver do_richcompare y nuestra evidencia más abajo)

class Foo:
    def __ne__(self, other):
        return NotImplemented
    __eq__ = __ne__

f = Foo()
f2 = Foo()

Y las comparaciones:

>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True

Rendimiento

No tome mi palabra para ella, vamos a ver lo que es más performante:

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

Creo que estas cifras de rendimiento hablan por sí mismos:

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

Esto hace que sEnse si se considera que low_level_python está haciendo la lógica en Python que de otro modo se maneja en el nivel C.

Respuesta a algunos críticos

Otro escribe responde:

not self == other aplicación de Aaron Hall del método __ne__ es incorrecta, ya que nunca puede volver NotImplemented (not NotImplemented es False) y por lo tanto el método __ne__ que tiene prioridad nunca puede caer de nuevo en el método __ne__ que no tiene prioridad.

Tener __ne__ Nunca NotImplemented retorno no significa que sea incorrecta. En su lugar, nos ocupamos de priorización con NotImplemented a través de la verificación de la igualdad con ==. Suponiendo == se implementa correctamente, hemos terminado.

not self == other solía ser el valor por defecto de Python 3 implementación del método __ne__ pero fue un error y se corrigió en Python 3.4 en enero de 2015, como se notó ShadowRanger (ver la edición # 21408).

Bueno, vamos a explicar esto.

Como se señaló anteriormente, Python 3 por defecto asas __ne__ por primera comprobación de si los rendimientos self.__eq__(other) NotImplemented (un conjunto unitario) - que deben ser comprobadas para con is y devuelto si es así, de lo contrario debe devolver la inversa. Aquí es que la lógica escrita como un mixin clase:

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

Esto es necesario para la corrección para el nivel C API Python, y se introdujo en Python 3, haciendo

redundante. Se eliminaron todos los métodos __ne__ pertinentes, incluidos los de ejecución de su propio cheque, así como los que el delegado de __eq__ directamente oa través de == -. == y era la forma más común de hacerlo

Conclusión

En Python 2 Código compatibles, el uso == para implementar __ne__. Es más:

  • correcta
  • sencilla
  • performant

En Python 3 solamente, usar la negación de bajo nivel sobre el nivel C - incluso es más simple y performante (aunque el programador es responsable de determinar que se trata de correcta ).

Una vez más, hacer no la lógica de escritura bajo nivel de alto nivel Python.

Sólo para que conste, un __ne__ portátil canónicamente correcta y cruz Py2 / AP3 se vería así:

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

Esto funciona con cualquier __eq__ podría definir:

  • not (self == other) diferencia, no interfiere con, en algunos casos molestos / complejos que implican comparaciones en las que una de las clases involucradas no implica que el resultado de __ne__ es el mismo que el resultado de not en __eq__ (por ejemplo ORM de SQLAlchemy, donde tanto __eq__ y retorno __ne__ objetos proxy especiales, no True o False, y tratando de not el resultado de __eq__ volvería False, en lugar del objeto proxy correcto).
  • A diferencia de not self.__eq__(other), esto correctamente delegados a la __ne__ de la otra instancia cuando los rendimientos self.__eq__ NotImplemented (not self.__eq__(other) sería un error más, pues es NotImplemented Truthy, por lo que cuando __eq__ no sabía cómo llevar a cabo la comparación, __ne__ volvería False, lo que implica que los dos objetos eran iguales, cuando en realidad el único objeto preguntó tenía ni idea, lo que implicaría un defecto de no igual)

Si su __eq__ no utiliza retornos NotImplemented, esto funciona con una sobrecarga (sin sentido) que si lo hace el uso NotImplemented A veces, esto lo maneja adecuadamente. Y los medios de comprobación de la versión de Python que si la clase es import de opinión en Python 3, __ne__ se deja sin definir, permitiendo nativa, repliegue eficiente de Python aplicación __ne__ (una versión C de los anteriores) para tomar el relevo.


¿Por qué esto es necesario

Python sobrecarga reglas

La explicación de por qué haces esto en lugar de otras soluciones es algo arcano. Python tiene un par de reglas generales sobre los operadores de sobrecarga, y operadores de comparación en particular:

  1. (Se aplica a todos los operadores) Cuando se ejecuta LHS OP RHS, LHS.__op__(RHS) intento, y si eso vuelve NotImplemented, intente RHS.__rop__(LHS). Excepción: Si RHS es una subclase de la clase de LHS, entonces RHS.__rop__(LHS) prueba primero . En el caso de los operadores de comparación, __eq__ y __ne__ son su propia "RP" s (por lo que el orden de la prueba para __ne__ es LHS.__ne__(RHS), entonces RHS.__ne__(LHS), invierte si RHS es una subclase de la clase de LHS)
  2. Aparte de la idea de que el operador "intercambiado", no existe una relación implícita entre los operadores. Incluso, por ejemplo, de la misma clase, LHS.__eq__(RHS) True regresar no implica retornos LHS.__ne__(RHS) False (de hecho, los operadores no están obligados incluso a devolver valores booleanos; ORM como SQLAlchemy intencionalmente no lo hacen, lo que permite una sintaxis de consulta más expresivo). A partir de Python 3, la aplicación por defecto __ne__ comporta de esta manera, pero no es contractual; puede anular __ne__ en formas que no son estrictos opuestos de __eq__.

¿Cómo esto se aplica a la sobrecarga de los comparadores

Así que cuando se sobrecarga un operador, tiene dos trabajos:

  1. Si sabe cómo poner en práctica la operación de ti mismo, de hacerlo, utilizando solamente su propio conocimiento de cómo hacer la comparación (nunca delegar, implícita o explícitamente, al otro lado de la operación; que te arriesgas incorrección y / o recursividad infinita, dependiendo de cómo lo hace)
  2. Si No saber cómo implementar la operación usted mismo, siempre NotImplemented retorno, lo que Python puede delegar en la implementación del otro operando

El problema con not self.__eq__(other)

def __ne__(self, other):
    return not self.__eq__(other)

nunca delega a la otra parte (y no es correcta si __eq__ regresa adecuadamente NotImplemented). Cuando vuelve self.__eq__(other) NotImplemented (que es "Truthy"), que en silencio volver False, por lo que vuelve A() != something_A_knows_nothing_about False, cuando debería haber comprobado si something_A_knows_nothing_about sabía cómo comparar a los casos de A, y si no lo hace, debe tener True devuelto (ya que si ninguna de las partes sabe cómo comparar a la otra, no se les considera iguales entre sí). Si A.__eq__ se implementa de forma incorrecta (volviendo False en lugar de NotImplemented cuando no reconoce el otro lado), entonces este es "correcta" desde la perspectiva de A, True regresar (ya A no creo que sea igual, así que no es igual), pero podría estar equivocado desde la perspectiva de something_A_knows_nothing_about, ya que en ningún momento pidió something_A_knows_nothing_about; A() != something_A_knows_nothing_about extremos hasta True, pero podrían something_A_knows_nothing_about != A() False, o cualquier otro valor de retorno.

El problema con not self == other

def __ne__(self, other):
    return not self == other

es más sutil. Va a ser correcta para el 99% de las clases, incluyendo todas las clases para las que __ne__ es la inversa lógica de __eq__. Pero se rompe not self == other tanto de las normas mencionadas anteriormente, lo que significa para las clases donde __ne__ no es la inversa lógica de __eq__, los resultados son una vez más no reflexiva, ya que uno de los operandos no se preguntó si se puede aplicar __ne__ en absoluto, incluso si el otro operando no se puede. El ejemplo más simple es una clase de bicho raro que devuelve False para todos comparaciones, por lo tanto A() == Incomparable() y A() != Incomparable() False retorno. Con una correcta aplicación de la A.__ne__ (uno que vuelve NotImplemented cuando no sabe cómo hacer la comparación), la relación es reflexiva; A() != Incomparable() y Incomparable() != A() están de acuerdo en el resultado (ya que en el primer caso, los rendimientos A.__ne__ NotImplemented, a continuación, vuelve Incomparable.__ne__ False, mientras que en el segundo, vuelve Incomparable.__ne__ False directamente). Pero cuando se implementa como A.__ne__ return not self == other, devoluciones A() != Incomparable() True (porque los rendimientos A.__eq__, no NotImplemented, a continuación, vuelve Incomparable.__eq__ False, e invierte A.__ne__ que a True), mientras que los retornos Incomparable() != A() False.

Se puede ver un ejemplo de esto en acción aquí .

Obviamente, una clase que siempre devuelve False tanto para __eq__ y __ne__ es un poco extraño. Sin embargo, como se mencionó antes, __eq__ y __ne__ ni siquiera necesitan volver True / False; la SQLAlchemy ORM tiene clases con comparadores que devuelve un objeto proxy especial para construcción de consultas, no True / False en absoluto (que son "Truthy" si evalúan en un contexto booleano, pero nunca se supone que deben ser evaluados en este contexto ).

Al no sobrecargar __ne__ correctamente, clases de quiebre de ese tipo, como el código:

 results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())

funcionará (suponiendo SQLAlchemy sabe cómo insertar MyClassWithBadNE en una cadena SQL en absoluto, lo que se puede hacer con adaptadores de tipo sin MyClassWithBadNE tener que cooperar en absoluto), pasando el objeto proxy espera que filter, mientras que:

 results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)

acabará pasando filter un False llano, porque self == other devuelve un objeto proxy, y not self == other simplemente convierte el objeto Truthy proxy para False. Con suerte, filter se produce una excepción en la que se manejan argumentos no válidos como False. Aunque estoy seguro de que muchos argumentan que MyTable.fieldname debe ser consistentemente en el lado izquierdo de la comparación, los restos hecho de que no hay ninguna razón para hacer cumplir este programática en el caso general, y una correcta genérica __ne__ funciona en ambos sentidos, mientras que return not self == other sólo funciona en una disposición.

Respuesta corta: sí (pero leer la documentación para hacerlo bien)

implementación del método de __ne__ ShadowRanger es la correcta (en el sentido de que se comporta exactamente igual que la implementación de Python por defecto 3):

def __ne__(self, other):
    result = self.__eq__(other)

    if result is not NotImplemented:
        return not result

    return NotImplemented

not self == other aplicación de Aaron Hall del método __ne__ es incorrecta, ya que nunca puede volver NotImplemented (not NotImplemented es False) y por lo tanto el método __ne__ que tiene prioridad nunca puede caer de nuevo en el método __ne__ que no tiene prioridad. not self == other solía ser el defecto Python 3 implementación del método __ne__ pero era un error y se corrigió en Python 3.4 en enero de 2015, como se dio cuenta de ShadowRanger (ver edición # 21408 ).

Aplicación de los operadores de comparación

El Referencia del lenguaje Python para Python 3 estados en su capítulo III modelo de datos :

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

Estos son los llamados “métodos de comparación rica”. La correspondencia entre los símbolos de operador y nombres de método es el siguiente: las llamadas x<y x.__lt__(y), x<=y llama x.__le__(y), x==y llama x.__eq__(y), x!=y llama x.__ne__(y), llamadas x>y x.__gt__(y) y x>=y llama x.__ge__(y).

Un método de comparación rica puede devolver el NotImplemented Singleton si que no implementa la operación durante un par de argumentos dados.

No hay versiones-argumentos intercambiado de estos métodos (para ser utilizado cuando el argumento de la izquierda no es compatible con la operación, pero la derecha argumento hace); más bien, __lt__() y __gt__() son el uno al otro de reflexión, __le__() y __ge__() son reflejo del otro, y __eq__() y __ne__() son su propio reflejo. Si los operandos son de diferentes tipos, y el tipo de operando de la derecha es un directo o subclase indirecta del tipo del operando de la izquierda, el método reflejada de el operando de la derecha tiene prioridad, de lo contrario el método del operando de la izquierda tiene prioridad. subclases virtual no se considera.

Traducción de esto en código Python da (usando operator_eq para ==, operator_ne para !=, operator_lt para <, operator_gt para >, operator_le para <= y operator_ge para >=):

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

Por defecto la aplicación de los métodos de comparación

La documentación agrega:

Por defecto, los delegados __ne__() a __eq__() e invierte el resultado a menos que sea NotImplemented. No hay otra implícita las relaciones entre los operadores de comparación, por ejemplo, la verdad de (x<y or x==y) no implica x<=y.

La implementación por defecto de los métodos de comparación (__eq__, __ne__, __lt__, __gt__, __le__ y __ge__) puede pues ser dada por:

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

Así que esta es la aplicación correcta del método __ne__. Y no siempre devuelve el inverso del método __eq__ porque cuando el método __eq__ vuelve NotImplemented, su not NotImplemented inversa es False (como bool(NotImplemented)es True) en lugar de la NotImplemented deseado.

implementaciones incorrectas de __ne__

Como Aaron Hall demostró anteriormente, not self.__eq__(other) no es la implementación predeterminada del método __ne__. . Pero tampoco es not self == other Este último se demuestra a continuación, comparando el comportamiento de la aplicación por defecto con el comportamiento de la aplicación not self == other en dos casos:

  • el método __eq__ rendimientos NotImplemented;
  • el método __eq__ devuelve un valor diferente de NotImplemented.

aplicación por defecto

Vamos a ver lo que sucede cuando el método A.__ne__ utiliza la aplicación por defecto y el A.__eq__ devuelve el método NotImplemented:

class A:
    pass


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) == "B.__ne__"
  1. != llama A.__ne__.
  2. A.__ne__ llama A.__eq__.
  3. retornos A.__eq__ NotImplemented.
  4. != llama B.__ne__.
  5. retornos B.__ne__ "B.__ne__".

Esto muestra que cuando el método A.__eq__ rendimientos NotImplemented, el método A.__ne__ cae de nuevo en el método B.__ne__.

Ahora vamos a ver lo que sucede cuando se utiliza el método A.__ne__ la implementación por defecto y el método A.__eq__ devuelve un valor diferente 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. != llama A.__ne__.
  2. A.__ne__ llama A.__eq__.
  3. retornos A.__eq__ True.
  4. retornos != not True, que es False.

Esto demuestra que en este caso, el método A.__ne__ devuelve el inverso del método A.__eq__. Por lo tanto se comporta el método __ne__ gusta anuncian en la documentación.

Anulación de la implementación predeterminada del método A.__ne__ con la correcta aplicación dada anteriormente produce los mismos resultados.

not self == other aplicación

Vamos a ver lo que sucede cuando anulando la implementación predeterminada del método A.__ne__ con la implementación y la not self == other A.__eq__ devuelve el método 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. != llama A.__ne__.
  2. A.__ne__ llama ==.
  3. == llama A.__eq__.
  4. retornos A.__eq__ NotImplemented.
  5. == llama B.__eq__.
  6. retornos B.__eq__ NotImplemented.
  7. retornos == A() is B(), que es False.
  8. retornos A.__ne__ not False, que es True.

La implementación predeterminada del método __ne__ volvió "B.__ne__", no True.

Ahora vamos a ver lo que sucede cuando anulando la implementación predeterminada del método A.__ne__ con la implementación not self == other y el método A.__eq__ devuelve un valor diferente 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. != llama A.__ne__.
  2. A.__ne__ llama ==.
  3. == llama A.__eq__.
  4. retornos A.__eq__ True.
  5. retornos A.__ne__ not True, que es False.

La implementación predeterminada del método __ne__ también volvió False en este caso.

Dado que esta aplicación no se replica el comportamiento de la aplicación por defecto de la __ne__ método cuando el método __eq__ vuelve NotImplemented, es incorrecto.

Si todos __eq__, __ne__, __lt__, __ge__, __le__ y __gt__ tiene sentido para la clase, a continuación, sólo aplicar __cmp__ lugar. De lo contrario, hacer lo que está haciendo, debido al poco Daniel DiPaolo dijo (mientras estaba probando en lugar de mirar hacia arriba;))

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top