Alex Lin Wang 王帅

__bool__

Understanding Python's __bool__ dunder method for truthiness evaluation in custom classes. Learn how to make your objects work naturally with if statements and boolean operations.

Let's say for example you are building a cache class and you want to operate on this cache class. However, you want to fetch some user from the cache, only if it's there, but you're having problems with this.

class SimpleCache:
    def __init__(self):
        self._data = {}
    def set(self, key, value):
        self._data[key] = value
    def get(self, key, default=None):
        return self._data.get(key, default)
…

def fetch_user(cache, user_id):
    if not cache:  # natural, just like a dict or list
        print("Cache empty, querying DB...")
        data = {\"id\": user_id, \"name\": \"Alex\"'}'}
        cache.set(user_id, data)
    else:
        print("Using cache")
    return cache.get(user_id)

One core component of idiomatic python is evaluating truthfulness of objects in a Boolean way when using things like if, while, and not. For lots of existing types we see existing false groups:

  • Constants such as None and False
  • Zero of any type
  • Empty Sequence/collections

As well as existing True objects like:

  • Non-zero numbers: 1, -2, 0.5, 1+0j
  • Non-empty strings: "0", "False", " ", "hello"
  • Non-empty collections: [0], (None,), {0}, {\"k\": 1}, {1}, range(1)
  • Non-empty bytes-like: b"\x00", bytearray(b"\x00"), memoryview(b"x")
  • Objects & callables

For our custom object that we create, Python uses __bool__ (or __len__ if __bool__ doesn't exist) to determine object truthfulness.

__bool__ is a dunder (double underscore) which basically means it's a special function used to define your own python operations. Other examples include the widely used __init__, __len__, and __str__ or other operators.

To use it, we want to return True or False based on if the instance of that class is should be empty or not a valid instance such as in the example that a cache has no data then:

class SimpleCache:
    def __init__(self):
        self._data = {}

    def set(self, key, value):
        self._data[key] = value

    def get(self, key, default=None):
        return self._data.get(key, default)

    def __bool__(self):
        return len(self._data) > 0

# Now this function works perfectly!
def fetch_user(cache, user_id):
    if not cache:  # natural, just like a dict or list
        print("Cache empty, querying DB...")
        data = {\"id\": user_id, \"name\": \"Alex\"'}'}
        cache.set(user_id, data)
    else:
        print("Using cache")

    return cache.get(user_id)

Advantages of This Approach

  • Readability: Conditions like if not cache: are natural and concise when emptiness means "no useful state".
  • Simplified conditionals: You avoid sprinkling len(cache._data) == 0 or not cache._data throughout the codebase.
  • Encapsulation & consistency: One canonical definition of "empty/invalid" lives in the class, preventing ad-hoc checks that drift over time.

Questions or feedback? Feel free to reach out!