Python Truth Value Testing: bool vs len

In Python, every object can be evaluated in a Boolean context, such as within an if or while statement. This article explains the underlying mechanism Python uses to determine truthiness, specifically detailing the priority order between the __bool__() and __len__() special methods, the fallback behavior when neither is implemented, and the strict constraints applied to their return values.

The Truth Testing Protocol

When Python evaluates an object x in a Boolean context, it implicitly calls the built-in bool(x) constructor. Under the hood, Python checks for specific magic methods in a strict hierarchy:

  1. __bool__() is checked first: If the object’s class defines __bool__(), Python invokes it directly to determine whether the object is True or False.
  2. __len__() acts as a fallback: If __bool__() is not implemented, Python checks for __len__(). If present, Python calls __len__(). The object is evaluated as True if the result is non-zero, and False if the result is 0.
  3. Default to True: If neither __bool__() nor __len__() is defined on the class, the object is considered truthy by default.

__bool__ Takes Absolute Precedence

When both methods are defined on an object, Python always calls __bool__() and completely ignores __len__().

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

    def __bool__(self):
        return False

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

container = AmbiguousContainer([1, 2, 3])

print(len(container))  # Outputs: 3
print(bool(container)) # Outputs: False

Even though the container holds three elements and returns a length of 3, the explicit __bool__() method forces the object to evaluate to False.

Falling Back to __len__

Standard collection types—such as lists, dictionaries, tuples, and sets—do not define __bool__(). Instead, they rely on __len__() to evaluate truthiness.

class CustomList:
    def __init__(self, data):
        self.data = data

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

empty_list = CustomList([])
populated_list = CustomList([1, 2])

print(bool(empty_list))     # Outputs: False (len is 0)
print(bool(populated_list)) # Outputs: True  (len is 2)

Because __bool__() is absent, Python looks to __len__(). A length of zero designates a falsy object, whereas any non-zero integer evaluates to True.

Default Behavior for Custom Classes

Instances of custom classes that implement neither method are always considered truthy:

class Item:
    pass

item = Item()
print(bool(item)) # Outputs: True

This ensures that distinct object references evaluate to True unless the developer explicitly provides a logic for emptiness or negation.

Type Constraints and Common Errors

Both methods enforce strict type constraints within Python:

class InvalidBool:
    def __bool__(self):
        return 1  # Raises TypeError: __bool__ should return bool, returned int

class InvalidLen:
    def __len__(self):
        return -5  # Raises ValueError: __len__() should return >= 0

By following this two-tier resolution process, Python maintains consistent behavior across all built-in collections while offering developers fine-grained control over how custom objects behave in conditional statements.