Si ejecuta la siguiente declaración en Python 3.7, (de mi prueba) imprimirá b
:
if None.__eq__("a"): print("b")
Sin embargo, None.__eq__("a")
se evalúa como NotImplemented
.
Naturalmente, "a".__eq__("a")
se evalúa como True
, y "b".__eq__("a")
se evalúa como False
.
Inicialmente descubrí esto al probar el valor de retorno de una función, pero no devolví nada en el segundo caso, por lo que la función devolvió None
.
¿Que está pasando aqui?
Este es un gran ejemplo de por qué los métodos __dunder__
no deben usarse directamente, ya que a menudo no son reemplazos apropiados para sus operadores equivalentes; en su lugar, debe usar el operador ==
para comparaciones de igualdad, o en este caso especial, al verificar None
, use is
(salte al final de la respuesta para obtener más información).
Has hecho
None.__eq__('a') # NotImplemented
Que devuelve NotImplemented
ya que los tipos que se comparan son diferentes. Considere otro ejemplo en el que dos objetos con diferentes tipos se comparan de esta manera, como 1
y 'a'
. Hacer (1).__eq__('a')
tampoco es correcto y devolverá NotImplemented
. La forma correcta de comparar estos dos valores para la igualdad sería
1 == 'a' # False
Lo que pasa aquí es
(1).__eq__('a')
, que devuelve NotImplemented
. Esto indica que la operación no es compatible, por lo que'a'.__eq__(1)
, que también devuelve el mismo NotImplemented
. Asi que,False
.Aquí hay un pequeño y agradable MCVE que usa algunas clases personalizadas para ilustrar cómo sucede esto:
class A: def __eq__(self, other): print('A.__eq__') return NotImplemented class B: def __eq__(self, other): print('B.__eq__') return NotImplemented class C: def __eq__(self, other): print('C.__eq__') return True a = A() b = B() c = C() print(a == b) # A.__eq__ # B.__eq__ # False print(a == c) # A.__eq__ # C.__eq__ # True print(c == a) # C.__eq__ # True
Por supuesto, eso no explica por qué la operación devuelve verdadero. Esto se debe a que NotImplemented
es en realidad un valor real:
bool(None.__eq__("a")) # True
Igual que,
bool(NotImplemented) # True
Para obtener más información sobre qué valores se consideran verdaderos y falsos, consulte la sección de documentos sobre Pruebas de valor de verdad , así como esta respuesta . Vale la pena señalar aquí que NotImplemented
es cierto, pero habría sido una historia diferente si la clase hubiera definido un método __bool__
o __len__
que devolviera False
o 0
respectivamente.
Si desea el equivalente funcional del operador ==
, use operator.eq
:
import operator operator.eq(1, 'a') # False
Sin embargo, como se mencionó anteriormente, para este escenario específico , donde está verificando None
, el uso is
:
var = 'a' var is None # False var2 = None var2 is None # True
El equivalente funcional de esto es usar operator.is_
:
operator.is_(var2, None) # True
None
es un objeto especial y solo existe 1 versión en la memoria en cualquier momento. IOW, es el único singleton de la clase NoneType
(pero el mismo objeto puede tener cualquier número de referencias). Las pautas de PEP8 lo hacen explícito:
Las comparaciones con singletons como
None
siempre deben hacerse conis
ois not
, nunca con los operadores de igualdad.
En resumen, para singletons como None
, una verificación de referencia con is
es más apropiada, aunque tanto ==
como is
funcionarán bien.
El resultado que está viendo es causado por el hecho de que
None.__eq__("a") # evaluates to NotImplemented
se evalúa como NotImplemented
, y el valor de verdad de NotImplemented
se documenta como True
:
https://docs.python.org/3/library/constants.html
Valor especial que deben devolver los métodos especiales binarios (por ejemplo,
__eq__()
,__lt__()
,__add__()
,__rsub__()
, etc.) para indicar que la operación no está implementada con respecto al otro tipo; puede ser devuelto por los métodos especiales binarios en el lugar (por ejemplo,__imul__()
,__iand__()
, etc.) para el mismo propósito. Su valor de verdad es verdadero.
Si llama al __eq()__
manualmente en lugar de solo usar ==
, debe estar preparado para lidiar con la posibilidad de que devuelva NotImplemented
y que su valor de verdad sea verdadero.
Como ya pensó que None.__eq__("a")
se evalúa como NotImplemented
sin embargo, si intenta algo como
if NotImplemented: print("Yes") else: print("No")
el resultado es
sí
esto significa que el valor de verdad de NotImplemented
true
Por lo tanto, el resultado de la pregunta es obvio:
None.__eq__(something)
produce NotImplemented
Y bool(NotImplemented)
evalúa como True
Entonces, if None.__eq__("a")
es siempre True