10.5 The bool() Constructor and __bool__ / __len__
The bool() Constructor: Converting to Boolean
The bool() constructor is the primary mechanism for explicit boolean conversion in Python. It accepts a single argument and returns either True or False. This process is formally known as “truthiness” evaluation. The constructor doesn’t merely check if a value is literally True or False; it inspects the value to determine if it should be considered “truthy” (evaluates to True) or “falsy” (evaluates to False).
The standard falsy values in Python are well-defined:
NoneFalse(obviously)- Zero of any numeric type:
0,0.0,0j,0.0 - Empty sequences and collections:
'',(),[],{},set(),range(0) - Objects for which
obj.__bool__()returnsFalseorobj.__len__()returns0(explained in detail next)
Any object not explicitly on this list is typically considered truthy.
# Examples of explicit boolean conversion
print(bool(False)) # Output: False
print(bool(0)) # Output: False
print(bool([])) # Output: False
print(bool("")) # Output: False
print(bool(None)) # Output: False
print(bool(True)) # Output: True
print(bool(42)) # Output: True
print(bool([1, 2])) # Output: True
print(bool("Hello")) # Output: True
print(bool([[]])) # Output: True (the outer list is not empty!)
The __bool__() Method: Controlling Truthiness
For user-defined classes, the __bool__() method is the primary and preferred way to define an object’s truthiness. When the bool() function is called on an object, or the object is used in a boolean context (like an if or while statement), Python looks for this method. If it exists, Python calls it and uses its return value (True or False) to determine the object’s truthiness.
Defining __bool__() allows you to create objects whose truthiness is based on the object’s internal state, making your classes more intuitive and Pythonic.
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def __bool__(self):
# The account is considered truthy if it has a positive balance.
return self.balance > 0
# Usage
account1 = BankAccount(100)
account2 = BankAccount(0)
if account1:
print("Account 1 has funds.") # This will print
if account2:
print("Account 2 has funds.") # This will NOT print
print(bool(account1)) # Output: True
print(bool(account2)) # Output: False
The __len__() Method: The Fallback for Truthiness
If a class does not define a __bool__() method, Python falls back to calling the __len__() method to determine truthiness. If __len__() is defined, the object is considered truthy if the method returns a non-zero integer. If it returns 0, the object is falsy. This behavior is why empty sequences ([], "", etc.) are falsy—their __len__() method returns 0.
This fallback mechanism exists for historical reasons and to provide a sensible default for container-like objects. However, for most user-defined classes, explicitly defining __bool__ is clearer and more intentional.
class ShoppingCart:
def __init__(self, items):
self.items = list(items)
def __len__(self):
return len(self.items)
# Since this class has no __bool__, __len__ is used.
full_cart = ShoppingCart(['apple', 'book'])
empty_cart = ShoppingCart([])
if full_cart:
print("Full cart has items!") # This will print
if empty_cart:
print("Empty cart has items!") # This will NOT print
print(bool(full_cart)) # Output: True (len is 2, which is > 0)
print(bool(empty_cart)) # Output: False (len is 0)
Precedence and Best Practices
The order of operations for truthiness evaluation is crucial:
__bool__()is checked first. If it exists, its result is used.- If
__bool__()is not defined,__len__()is checked. If it exists, a non-zero result makes the object truthy. - If neither method is defined, the object is always considered truthy (as almost all objects in Python are by default).
The key best practice is to always define __bool__() for your classes if you want to customize their truthiness. Relying on the __len__() fallback can be less explicit and potentially confusing for readers of your code. For example, a Player class might have a __len__() method that returns the number of items in their inventory, but you might want its truthiness to be based on whether the player is still alive. Using __bool__() for the “is alive” check makes the intent unambiguous.
class Player:
def __init__(self, name, is_alive=True, inventory=None):
self.name = name
self.is_alive = is_alive
self.inventory = inventory or []
def __len__(self):
return len(self.inventory)
def __bool__(self):
# Truthiness is explicitly about life, not inventory size.
return self.is_alive
# Usage
player = Player("Alice", is_alive=False, inventory=['sword', 'potion'])
# The player is falsy because they are dead, even though their inventory isn't empty.
if player:
print(f"{player.name} is alive!")
else:
print(f"{player.name} has been defeated.") # This will print
Common Pitfalls and Edge Cases
A significant pitfall is creating an object that defines __len__() but for which a length of zero should not imply falsiness. For instance, a class representing a single file on disk might have __len__() return the file’s size in bytes. A file with zero bytes (an empty file) is still a valid file object and should likely be truthy. If you only defined __len__(), the empty file would be falsy, which is probably incorrect behavior. Defining __bool__() to always return True would solve this.
class File:
def __init__(self, size):
self.size = size
def __len__(self):
return self.size
# Without this __bool__, an empty file (size=0) would be falsy!
def __bool__(self):
return True # A file object is always truthy, regardless of size.
empty_file = File(0)
if empty_file:
print("File exists!") # Now this correctly prints.