Este es un gran ejemplo de por qué los __dunder__
métodos no deben usarse directamente, ya que a menudo no son reemplazos apropiados para sus operadores equivalentes; en su lugar, debe usar el ==
operador para las comparaciones de igualdad, o en este caso especial, al verificar None
, usar is
(pase al final de la respuesta para obtener más información).
Has hecho
None.__eq__('a')
# NotImplemented
Que regresa 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 volverá NotImplemented
. La forma correcta de comparar estos dos valores para la igualdad sería
1 == 'a'
# False
Lo que pasa aquí es
- Primero,
(1).__eq__('a')
se prueba, que vuelve NotImplemented
. Esto indica que la operación no es compatible, por lo que
'a'.__eq__(1)
se llama, que también devuelve lo mismo NotImplemented
. Entonces,
- Los objetos se tratan como si no fueran lo mismo y
False
se devuelven.
Aquí hay un pequeño 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 NotImplemented
que en realidad es un valor verdadero:
bool(None.__eq__("a"))
# True
Igual que,
bool(NotImplemented)
# True
Para obtener más información sobre qué valores se consideran verdadero y falso, 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 verdad, pero habría sido una historia diferente si la clase hubiera definido un método __bool__
o un __len__
método que regresó 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á buscando None
, use 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 NoneType
clase (pero el mismo objeto puede tener cualquier número de referencias). Las pautas PEP8 hacen esto explícito:
Las comparaciones con singletons como None
siempre deben hacerse con is
o
is 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 ambas ==
y is
funcionarán bien.