Boolean Logic, Truthy and Falsy Values

Every Python object is either truthy or falsy, the falsy list is short and closed, and knowing it lets conditions read as plain English.

True and False

True and False are the only two values of type bool. They are singletons: every True in a program is the same object.

print(type(True))          # <class 'bool'>
print(True + True)         # 2, because bool subclasses int
print(int(False))          # 0

Truthiness

Python does not require a condition to be a boolean. Any object can be tested, and Python asks the object whether it considers itself true.

The complete falsy list

CategoryFalsy values
The booleansFalse
NothingNone
Zero0, 0.0, 0j, Decimal(0), Fraction(0)
Empty sequences"", (), [], b"", range(0)
Empty collections{}, set(), frozenset()

Everything else is truthy. That is not a simplification; the list above is the whole of it.

print(bool(0), bool(0.0), bool(""), bool([]), bool({}), bool(None))
# False False False False False False

print(bool(-1), bool(0.1), bool("0"), bool("False"), bool([0]), bool(" "))
# True True True True True True
Look carefully at "0", "False", [0] and " ". All four are truthy. A string containing the word False is a non empty string; a list containing zero is a non empty list; a single space is a character.

How Python decides

For an object of your own class, Python calls __bool__ if it exists. If not, it calls __len__ and treats a length of zero as false. If neither exists, the object is truthy.

class Basket:
    def __init__(self, items):
        self.items = items

    def __len__(self):
        return len(self.items)


empty = Basket([])
full = Basket(["pen"])

print(bool(empty))     # False, because len() is 0
print(bool(full))      # True

if not empty:
    print("nothing to check out")

This is why an empty list is falsy: list defines __len__. There is no special case in the language for containers; it falls out of one protocol.

Writing conditions the Python way

names = []

# Verbose
if len(names) == 0:
    print("empty")

# Idiomatic
if not names:
    print("empty")


value = None

# Verbose
if value != None and value != "":
    print("has a value")

# Idiomatic
if value:
    print("has a value")

When explicit is better

def apply_discount(percent=None):
    if percent is None:          # correct: 0 is a legitimate discount
        percent = 10
    return percent


def apply_discount_wrong(percent=None):
    percent = percent or 10      # wrong: a discount of 0 becomes 10
    return percent


print(apply_discount(0))         # 0
print(apply_discount_wrong(0))   # 10

Use truthiness when "empty or missing" are the same case. Use is None when zero, an empty string or False are meaningful values in their own right. That single distinction prevents a whole family of quiet bugs.

The logical operators as value selectors

print(0 or "fallback")        # fallback
print("set" or "fallback")    # set
print("a" and "b")            # b
print(None and "b")           # None
  • or returns the first truthy operand, or the last one if none are truthy.
  • and returns the first falsy operand, or the last one if none are falsy.
  • not always returns a real True or False.

any and all

scores = [55, 72, 48, 90]

print(any(s >= 80 for s in scores))    # True  - at least one
print(all(s >= 40 for s in scores))    # True  - every one
print(any([]))                          # False - nothing is true
print(all([]))                          # True  - nothing is false

The two empty cases catch people out. all([]) is True because there is no counterexample. It is the mathematically correct answer, and it is occasionally exactly the wrong one for your program, so guard the empty case explicitly when it matters.

fields = {"name": "Meera", "email": "", "phone": "9000000000"}

missing = [key for key, value in fields.items() if not value]
print(missing)                              # ['email']
print("Form complete" if all(fields.values()) else "Incomplete")

Comparison chains and boolean results

age = 30
is_working_age = 18 <= age < 60
print(is_working_age)          # True, a real bool

# Assign the condition to a name instead of repeating it.
if is_working_age and not is_retired:
    ...

Common mistakes

  • Writing if x == True:. Write if x:. The comparison form also breaks for truthy non boolean values, since 1 == True but 2 == True is False.
  • Believing bool("False") is False.
  • Using or for defaults where 0 or "" is valid data.
  • Forgetting that all([]) is True.
  • Writing if len(items) > 0: when if items: says the same thing.
  • Testing if value != None: instead of if value is not None:.

Best practices

  • Use truthiness for emptiness, and is None for absence.
  • Name a complicated condition and use the name; conditions with three or more clauses are hard to read inline.
  • Prefer any and all with a generator expression over a loop that sets a flag.
  • Return real booleans from predicate functions, so callers get a predictable type.

Practice

  1. Sort these into truthy and falsy from memory, then verify: 0, "0", [], [[]], {}, {"": 0}, " ", None, "None", 0.0, -1, range(0).
  2. Write a class whose instances are falsy when a field is empty, using __bool__.
  3. Explain the difference between if not value: and if value is None: with an example where they disagree.
  4. Rewrite a flag setting loop using any.
  5. Explain why all([]) is True and give a case where you would need to check for emptiness separately.

Conclusion

The falsy set is short: false, none, zero, and anything empty. Everything else is true. Once that list is memorised, Python conditions stop needing comparisons and start reading like sentences - and you will know the one place, distinguishing empty from missing, where the explicit test is still required.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Python notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.