Search code examples
pythonbooleanboolean-expression

Why is a boolean comparison with "is" so terrible?


The style guide reads:

# Correct:
if greeting:

# Wrong:
if greeting == True:

# Worse:
if greeting is True:

See PEP 8, search for the word "worse"

Why is this? I am accustomed to checking conditions as explicit as possible, to make the code more readable, and to catch aberrations.

Consider this function:

def f(do_it):
   if do_it : print("doit")
   else : print("no don't") 

It is easy to abuse/oversee, with unexpected behaviour

>>> f("False")
doit
>>> f([False])
doit

This is a real problem when, for instance, you are checking a return value that could unintentionally pass an if clause. This could be avoided by using the is construct.

Clearly there's a good reason for the PEP recommendation, but what is it?

Further research, prompted by the commenters, lead me to the following findings:

if x:

invokes the __bool method of the class of x. The method should return True or False depending on which of the two the object deems itself to be.

if x==True:

invokes the __eq method of the class of x. The method should be able to compare itself to True (or False), and return True or False, as the case may be.

if x is True

invokes neither. This tests whether x is the "True" object. It completely circumvents the __eq and __bool methods.

Note: I am not asking about the difference between == and is. If that's why you are here, see Is there a difference between "==" and "is"?


Solution

  • On a theoretical level, is expresses the wrong thing. We care about the truthiness of the value, not the identity. On the rare cases where we do care about the identity, an is comparison is appropriate.

    On a practical level, is True doesn't even work the way people expect it to:

    In [1]: import numpy
    
    In [2]: x = numpy.array([1, 2, 3])
    
    In [3]: flag = x[0] == 1
    
    In [4]: flag
    Out[4]: True
    
    In [5]: if flag: print('true')
    true
    
    In [6]: if flag is True: print('true')
    
    In [7]:
    

    We compared a 1 to a 1, got a thing that looks like True, but the is comparison failed. That's because bool isn't the only boolean type. Libraries are free to define their own. flag is an instance of numpy.bool_, and it's a different object from True. (NumPy has a good reason for this - using their own boolean type allows them to provide more uniform handling of 0-dimensional values. This is the same reason NumPy also has its own numeric scalar types.)

    Also, is True doesn't even catch the problem in your example. It just switches one silent misbehavior for another. f("False") printing doit is a problem, but so is f("True") silently doing nothing. Neither version of the test produces an actual error message.