Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does `if None.__eq__("a")` seem to evaluate to True (but not quite)?

People also ask

Does none evaluate to TRUE?

As to the other part of your question, it appears to be a logical tautology: None is False because Python interprets the None as being the equivalent of a null for the reasons found above. By adding not to a None , you are inverting its boolean value, which turns it into a True .

Is none evaluated as false?

In Python, None has nothing to do with the boolean value False. None in Python is actually an object implemented by NoneType class. False is a boolean object implemented by the bool class.

Does none evaluate to true Python?

It's one of Python's Magic Methods. The confusing thing is, that bool(None) returns False , so if x is None, if x works as you expect it to. However, there are other values that are evaluated as False . The most prominent example is an empty list.

What is __ eq __ method in Python?

Summary. Implement the Python __eq__ method to define the equality logic for comparing two objects using the equal operator ( == )


This is a great example of why the __dunder__ methods should not be used directly as they are quite often not appropriate replacements for their equivalent operators; you should use the == operator instead for equality comparisons, or in this special case, when checking for None, use is (skip to the bottom of the answer for more information).

You've done

None.__eq__('a')
# NotImplemented

Which returns NotImplemented since the types being compared are different. Consider another example where two objects with different types are being compared in this fashion, such as 1 and 'a'. Doing (1).__eq__('a') is also not correct, and will return NotImplemented. The right way to compare these two values for equality would be

1 == 'a'
# False

What happens here is

  1. First, (1).__eq__('a') is tried, which returns NotImplemented. This indicates that the operation is not supported, so
  2. 'a'.__eq__(1) is called, which also returns the same NotImplemented. So,
  3. The objects are treated as if they are not the same, and False is returned.

Here's a nice little MCVE using some custom classes to illustrate how this happens:

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

Of course, that doesn't explain why the operation returns true. This is because NotImplemented is actually a truthy value:

bool(None.__eq__("a"))
# True

Same as,

bool(NotImplemented)
# True

For more information on what values are considered truthy and falsy, see the docs section on Truth Value Testing, as well as this answer. It is worth noting here that NotImplemented is truthy, but it would have been a different story had the class defined a __bool__ or __len__ method that returned False or 0 respectively.


If you want the functional equivalent of the == operator, use operator.eq:

import operator
operator.eq(1, 'a')
# False

However, as mentioned earlier, for this specific scenario, where you are checking for None, use is:

var = 'a'
var is None
# False

var2 = None
var2 is None
# True

The functional equivalent of this is using operator.is_:

operator.is_(var2, None)
# True

None is a special object, and only 1 version exists in memory at any point of time. IOW, it is the sole singleton of the NoneType class (but the same object may have any number of references). The PEP8 guidelines make this explicit:

Comparisons to singletons like None should always be done with is or is not, never the equality operators.

In summary, for singletons like None, a reference check with is is more appropriate, although both == and is will work just fine.


The result you are seeing is caused by that fact that

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

evaluates to NotImplemented, and NotImplemented's truth value is documented to be True:

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

Special value which should be returned by the binary special methods (e.g. __eq__(), __lt__(), __add__(), __rsub__(), etc.) to indicate that the operation is not implemented with respect to the other type; may be returned by the in-place binary special methods (e.g. __imul__(), __iand__(), etc.) for the same purpose. Its truth value is true.

If you call the __eq()__ method manually rather than just using ==, you need to be prepared to deal with the possibility it may return NotImplemented and that its truth value is true.


As you already figured None.__eq__("a") evaluates to NotImplemented however if you try something like

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

the result is

yes

this mean that the truth value of NotImplemented true

Therefor the outcome of the question is obvious:

None.__eq__(something) yields NotImplemented

And bool(NotImplemented) evaluates to True

So if None.__eq__("a") is always True


Why?

It returns a NotImplemented, yeah:

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

But if you look at this:

>>> bool(NotImplemented)
True
>>> 

NotImplemented is actually a truthy value, so that's why it returns b, anything that is True will pass, anything that is False wouldn't.

How to solve it?

You have to check if it is True, so be more suspicious, as you see:

>>> NotImplemented == True
False
>>> 

So you would do:

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


>>> 

And as you see, it wouldn't return anything.