magic data __str__ __getitem__ __eq__ python python-3.x string boolean-expression equivalence

data - getattr python



¿Por qué `if None.__ eq__(" a ")` parece evaluarse como Verdadero(pero no del todo)? (4)

¿Por qué?

Devuelve un NotImplemented , sí:

>>> None.__eq__(''a'') NotImplemented >>>

Pero si nos fijamos en esto:

>>> bool(NotImplemented) True >>>

NotImplemented es en realidad un valor veraz, por eso es que devuelve b , todo lo que es True pasará, todo lo que sea False no lo hará.

¿Cómo resolverlo?

Debe comprobar si es True , así que tenga más sospechas, como ve:

>>> NotImplemented == True False >>>

Así que harías:

>>> if None.__eq__(''a'') == True: print(''b'') >>>

Y como ves, no devolvería nada.

Si ejecuta la siguiente declaración en Python 3.7, (de mis pruebas) se imprimirá b :

if None.__eq__("a"): print("b")

Sin embargo, None.__eq__("a") evalúa como No NotImplemented .

Naturalmente, "a".__eq__("a") evalúa como True , y "b".__eq__("a") 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?


Como ya ha calculado, None.__eq__("a") evalúa como No NotImplemented sin embargo, si intenta algo como

if NotImplemented: print("Yes") else: print("No")

el resultado es

esto significa que el valor de verdad de NotImplemented true

Por lo tanto el resultado de la pregunta es obvio:

None.__eq__(something) produce No NotImplemented

Y bool(NotImplemented) evalúa a True

Entonces, if None.__eq__("a") siempre es Verdadero


El resultado que está viendo es causado por el hecho de que

None.__eq__("a") # evaluates to NotImplemented

evalúa a NotImplemented , y el valor de verdad de NotImplemented está documentado como True :

https://docs.python.org/3/library/constants.html

Valor especial que debe ser devuelto por 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 usar simplemente == , debe estar preparado para lidiar con la posibilidad de que pueda devolver NotImplemented y que su valor verdadero sea verdadero.


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; debe utilizar el operador == lugar de comparaciones de igualdad, o en este caso especial, al marcar None , el uso is (pase a la parte inferior de la respuesta para obtener más información).

Has hecho

None.__eq__(''a'') # NotImplemented

Que devuelve No NotImplemented ya que los tipos que se comparan son diferentes. Considere otro ejemplo en el que dos objetos con tipos diferentes se comparan de esta manera, como 1 y ''a'' . Hacer (1).__eq__(''a'') tampoco es correcto, y devolverá NotImplemented . La manera correcta de comparar estos dos valores para la igualdad sería

1 == ''a'' # False

Lo que pasa aqui es

  1. Primero, se prueba (1).__eq__(''a'') , que devuelve NotImplemented . Esto indica que la operación no es compatible, por lo que
  2. Se llama a ''a''.__eq__(1) , que también devuelve el mismo NotImplemented . Asi que,
  3. Los objetos se tratan como si no fueran los mismos y se devuelve False .

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 se vuelve verdadera. Esto se debe a que NotImplemented es en realidad un valor verdadero:

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 del valor de verdad , así como esta respuesta . Vale la pena señalar aquí que NotImplemented es veraz, 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á comprobando que 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 una versión existe 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 hacen esto explícito:

Las comparaciones con singletons como None deben hacerse siempre con o is not , nunca con los operadores de igualdad.

En resumen, para singletons como None , una verificación de referencia es más apropiada, aunque ambas == y funcionarán bien.