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:
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):
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):
But that directly contradicts this answer that == False
(Option D) should never be used:
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.