pythonbooleannonetypeboolean-expression

Checking for False when variable can also be None or True


Update: please see my discussion if you want to delve further into this topic! Thank you everyone for your feedback on this!


I have a boolean(ish) flag that can be True or False, with None as an additional valid value. Each value has a different meaning.

(Edit for clarification: the variable in question, bool_flag is a custom dict class attribute, which has a value of None when uninitialized, as .get('bool_flag') returns None, and can be set to True or False, although True and None is generally sufficient for value checking for my needs.)

I understand the Pythonic way of checking for True and None and not None are:

if bool_flag: 
    print("This will print if bool_flag is True")
    # PEP8 approved method to check for True

if not bool_flag:
    print("This will print if bool_flag is False or None")
    # Also will print if bool_flag is an empty dict, sequence, or numeric 0

if bool_flag is None: 
    print("This will print if bool_flag  is None")

if bool_flag is not None: 
    print("This will print if bool_flag is True or False")
    # Also will print if bool_flag is initialized as anything except None

And if you need to check for all three in an if statement block, you would use a laddered approach, for example:

if bool_flag:
    print("This will print if bool_flag is True")
elif bool_flag is None:
    print("This will print if bool_flag  is None")
else:
    print("This will print if bool_flag is False")
    # Note this will also print in any case where flag_bool is neither True nor None

But what is the Pythonic way of just checking for a value of False (when only checking for False) when the flag can also be None or True as valid values? I have seen several questions but there doesn't seem to be a consensus.

Is it "more pythonic" to write:

# Option A:
if isinstance(bool_flag, bool) and not bool_flag:
    print("This will print if bool_flag is False")

# Option B:
if bool_flag is not None and not bool_flag:
    print("This will print if bool_flag is False")

## These two appear to be strictly prohibited by PEP8:
# Option C:
if bool_flag is False:
    print("This will print if bool_flag is False")

# Option D:
if bool_flag == False:
    print("This will print if bool_flag is False")

# Option E (per @CharlesDuffy):
match flag_bool:
    case False:
        print("This will print if bool_flag is False")

This topic has been discussed before:

  1. What is the correct way to check for False?
  2. In Python how should I test if a variable is None, True or False
  3. Is there a difference between "== False" and "is not" when checking for an empty string?
  4. Why does comparing strings using either '==' or 'is' sometimes produce a different result?
  5. Is there a difference between "==" and "is"?

This appears to be the closest answer to my question out of what is available (providing Option A above), but even this Answer is ambiguous (suggesting Option C as well):

  1. https://stackoverflow.com/a/37104262/22396214

However, this answer points out that if not flag_bool is equivalent to if bool(flag_value) == False which would imply that checking for False equivalence using the == operator is the official Pythonic method of checking for False (Option D):

  1. https://stackoverflow.com/a/36936790/22396214

But that directly contradicts this answer that == False (Option D) should never be used:

  1. https://stackoverflow.com/a/2021257/22396214

Solution

  • If you really want to do it :

    if bool_flag is False:
       pass
    

    PEP8 is a guideline. There may be style checkers which whine about it, and you may need to litter your code with #noqa to quiet them, but at the end of the day you need to decide what best represents what you are actually trying to do.

    In this case specifically, the checking of a value against True and False literal is discouraged by PEP8 largely because there are a number of other conditions which make a value Truthy or Falsey. When dealing with parameters or return values to/from elsewhere in your code or external libraries, there are going to be a number of instances when you get back what are actually different (and occasionally, surprising) types.

    In your case, you're not restricting your variable to true or false, or even to truthy or falsey. In fact, since you've got a trinary state possibility, one could argue that it isn't a boolean at all. At closest approach to a boolean, its an Optional[Boolean]. You could instead argue it's similar to an enum type with 3 possible values. From that perspective, you need to be checking for the actual value and not its truthiness. The latter is what PEP8 is talking about when it discourages testing against literals.

    Here's the thing to remember, though. All the arguments for not testing against boolean literals will apply to you as well. Six months from now, will you remember that the value needs to be tested against a literal instead of just being checked for truthiness? If someone else were looking at your code or using the return value from a function you've written, will they realize this?

    For maintainability, you may wish to use a different type entirely. An enum type, perhaps. That makes things more explicit, and it'll be a lot easier to explain, reason about, and deal with if you ever want to try type annotating your code.