5

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
5
  • "These two appear to be strictly prohibited by PEP8" - What makes you think that? Commented May 9, 2024 at 15:39
  • The part in PEP8 where it says "Don’t compare boolean values to True or False using ==:"? peps.python.org/pep-0008 Commented May 9, 2024 at 15:43
  • @CharlesDuffy I specifically only want to check for the False case in this Question. There is already a sufficient solution cited for checking for multiple values in an if statement block. Commented May 9, 2024 at 16:01
  • 1
    You might enjoy this previous answer of mine which has an implementation of something similar as a class. Commented May 9, 2024 at 19:42
  • @kindall the attribute getter is generic at this time and only a small sunset of attributes are Boolean. I will consider whether to initialize Boolean attributes explicitly in class ... __init__, however I would then need to add another flag to indicate whether the Boolean flag is to be read or not. Commented May 9, 2024 at 20:29
  • A follow-up discussion was started:
    “Trinary state variables” Join the conversation

3 Answers 3

4

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.

Sign up to request clarification or add additional context in comments.

8 Comments

The trinary state is an artifact of boolean attributes being initialized (to True or False) or not initialized (.get(attr) returns None). Perhaps I should note in hte Question that bool_flag is an attribute. I could make it a property, but that would require some additional code I am not prepared to implement, as I am currently relying on __slots__ being available and comprehensive.
I understand. There are plenty of places in my code in which I check for None or False as well, and for similar reasons. You need to be careful about doing so, though, and I will admit I've been bitten by the problem many a time. The effort to fix it early on is worth it unless its ephemeral code, or so deep within the codebase that no user ever touches it. When dealing with uninitialized variables, I've found throwing exceptions can sometimes be a useful approach.
@AutumnKome what do you mean .get(attr)? What is .get? trying to access an attribute that hasn't been initialized raises an AttributeError. It sounds like you probably shouldn't be relying on whatever .get is
Safe defaults are the easy way out, if you don't need the uninitialized state. I'd say that would cover a majority of the usual use cases. I know there are cases when the uninitialized state has its own and distinct meaning, though. You need to decide what works best for you and your codebase.
@AutumnKome I'm sorry, I'm a little busy at the moment. I've kept the tab open. I will try to put down my thoughts in a few days when I get some time.
|
3

First of all, given the number of traps that this data representation holds for the unwary, it might be worth considering whether it’s a good idea at all.

With that out of the way, I think it’s safe to assume that the “ban” on explicit comparisons in PEP8 is there to discourage beginners, who are only really interested in truthiness, from writing things like if (a != b) != False which, for some reason, they always seem to be doing.

If you really want to distinguish True, False and None, using the is operator is clearly the way to go.

Comments

1

@SIGHUP in a deleted Answer suggested that this question is an artifact of the phrasing of PEP 8.

Here is a breakdown of the issue presented by PEP 8:

# PEP 8:
# Wrong:
if greeting is True:

# Cited by others (see link 8 after second suggestion) as also implying:
# Wrong:
if greeting is False:

However, a few paragraphs above this statement, PEP 8 states:

# PEP 8:
# Correct:
if foo is not None:

# This implies (widely accepted - see link 9 below):
# Correct:
if foo is None:
  1. What is the difference between "is None" and "== None"

Therefore, the Pythonic way to check for False equivalency is Option C from the second suggestion in link 6:

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

Update: Per PEP 634, match ... case is implemented via the following:

The singleton literals None, True and False are compared using the is operator.

This confirms that if...is False: is "Pythonic".

7 Comments

You might also want to check what match ... case False: does. How does it decide whether a value matches False?
@nocomment the match ... case False: merely checks if the value of bool_flag is False. I just verified that it works for False, None, and True. match is ever so slightly faster that if, but it's not supported in the Python version I am using (I do have an environment with it to test it though).
But how does it check? You said "is". Did you mean is?
I'm saying look into what test match actually does to do its job. How does it determine whether a value "matches" False? You can find the answer in the PEP and in the bytecode, and it would enrich your answer.
Or maybe you already did that. Your "merely checks if the value of bool_flag is False" does sound like you know and meant that it checks identity, not equality or truth. My point is that the fact that match under the hood does an is check is further evidence that that's the right thing to do.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.