Python, debería aplicar __ne __ operador () basado en __eq__?
-
08-10-2019 - |
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?
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 quex!=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 quex!=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 seaNotImplemented
.
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 volverNotImplemented
(not NotImplemented
esFalse
) 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
- los métodos
__ne__
en este parche para cerrar Emisión 21.408 y - los métodos
__ne__
en el seguimiento en la limpieza elimina aquí
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 ??li>
- 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 denot
en__eq__
(por ejemplo ORM de SQLAlchemy, donde tanto__eq__
y retorno__ne__
objetos proxy especiales, noTrue
oFalse
, y tratando denot
el resultado de__eq__
volveríaFalse
, 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 rendimientosself.__eq__
NotImplemented
(not self.__eq__(other)
sería un error más, pues esNotImplemented
Truthy, por lo que cuando__eq__
no sabía cómo llevar a cabo la comparación,__ne__
volveríaFalse
, 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:
- (Se aplica a todos los operadores) Cuando se ejecuta
LHS OP RHS
,LHS.__op__(RHS)
intento, y si eso vuelveNotImplemented
, intenteRHS.__rop__(LHS)
. Excepción: SiRHS
es una subclase de la clase deLHS
, entoncesRHS.__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__
esLHS.__ne__(RHS)
, entoncesRHS.__ne__(LHS)
, invierte siRHS
es una subclase de la clase deLHS
) - 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 retornosLHS.__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:
- 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)
- 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
llamax.__le__(y)
,x==y
llamax.__eq__(y)
,x!=y
llamax.__ne__(y)
, llamadasx>y
x.__gt__(y)
yx>=y
llamax.__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 seaNotImplemented
. No hay otra implícita las relaciones entre los operadores de comparación, por ejemplo, la verdad de(x<y or x==y)
no implicax<=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__
rendimientosNotImplemented
; - el método
__eq__
devuelve un valor diferente deNotImplemented
.
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__"
-
!=
llamaA.__ne__
. -
A.__ne__
llamaA.__eq__
. - retornos
A.__eq__
NotImplemented
. -
!=
llamaB.__ne__
. - 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
-
!=
llamaA.__ne__
. -
A.__ne__
llamaA.__eq__
. - retornos
A.__eq__
True
. - retornos
!=
not True
, que esFalse
.
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
-
!=
llamaA.__ne__
. -
A.__ne__
llama==
. -
==
llamaA.__eq__
. - retornos
A.__eq__
NotImplemented
. -
==
llamaB.__eq__
. - retornos
B.__eq__
NotImplemented
. - retornos
==
A() is B()
, que esFalse
. - retornos
A.__ne__
not False
, que esTrue
.
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
-
!=
llamaA.__ne__
. -
A.__ne__
llama==
. -
==
llamaA.__eq__
. - retornos
A.__eq__
True
. - retornos
A.__ne__
not True
, que esFalse
.
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;))