__bool__
Overview
__bool__ is a Python magic method (dunder method) that lets you define custom truthiness behaviour for objects. Python calls it whenever it needs to convert an object to True or False — in conditionals, loops, bool(), logical operators, and more.
If you don’t define __bool__, Python falls back to __len__. If neither is defined, the object is always truthy.
Signature
def __bool__(self) -> bool:
Python invokes __bool__ automatically — you never call it directly. The return type should be bool in Python 3. Returning a non-zero integer like 1 works (non-zero ints are truthy) but violates the type contract and produces a DeprecationWarning — always return True or False.
When Python Calls __bool__
| Context | Example |
|---|---|
| Explicit conversion | bool(obj) |
| Conditional statement | if obj: |
while loop | while obj: |
assert statement | assert obj |
not operator | not obj |
Logical and / or | obj and other, obj or other |
| Comprehension filter | [x for x in items if obj] |
__bool__ vs __len__
If __bool__ is not defined but __len__ is, Python uses len(self) > 0 as the truth value:
class Empty:
def __len__(self):
return 0
print(bool(Empty())) # False — uses __len__
# Output: False
If neither method is defined, the object is always True:
class Nothing:
pass
print(bool(Nothing())) # True — no __bool__ or __len__
# Output: True
This is why empty containers like [], {}, and "" are falsy — they all implement __len__.
Return Type
Python 3 requires __bool__ to return a bool. Returning a non-zero integer works due to Python’s truthiness rules, but it produces a DeprecationWarning:
class Bad:
def __bool__(self):
return 1 # Works, but wrong type — produces DeprecationWarning
print(bool(Bad())) # True
# DeprecationWarning: __bool__ should return bool, not int
Returning non-bool types like strings or lists is also technically possible but raises a stronger warning and is never correct:
class VeryBad:
def __bool__(self):
return "yes" # Wrong — don't do this
# DeprecationWarning: __bool__ must return bool, not str
Always return True or False.
Built-in Types and Truthiness
All built-in Python types use __len__ for truthiness when no custom __bool__ is defined:
| Type | Falsy when |
|---|---|
int | 0 |
float | 0.0 |
str | "" (empty string) |
list | [] (empty list) |
dict | {} (empty dict) |
tuple | () (empty tuple) |
None | Always False |
This is why checking if somelist: works — Python calls __len__ as a fallback.
Code Examples
Custom Database Connection
class DatabaseConnection:
def __init__(self, connected=False):
self.connected = connected
def __bool__(self):
return self.connected
conn = DatabaseConnection(connected=True)
if conn:
print("Executing query...") # Runs
conn = DatabaseConnection(connected=False)
print(bool(conn))
# Output: False
Task List with Custom Empty State
class TaskList:
def __init__(self, tasks=None):
self.tasks = tasks or []
self.completed = set()
def __bool__(self):
return len(self.incomplete()) > 0
def incomplete(self):
return [t for t in self.tasks if t not in self.completed]
todo = TaskList(tasks=["buy milk", "walk dog"])
while todo:
task = todo.incomplete().pop()
print(f"Doing: {task}") # buy milk, then walk dog
todo.completed.add(task)
print(bool(todo))
# Output: False — all tasks done
Order Status Validator
class OrderStatus:
PENDING = "pending"
CONFIRMED = "confirmed"
CANCELLED = "cancelled"
def __init__(self, status):
if status not in (self.PENDING, self.CONFIRMED, self.CANCELLED):
raise ValueError(f"Invalid status: {status}")
self.status = status
def __bool__(self):
return self.status in (self.PENDING, self.CONFIRMED)
order = OrderStatus(OrderStatus.PENDING)
assert order # Passes — truthy
order = OrderStatus(OrderStatus.CANCELLED)
print(bool(order))
# Output: False
Gotchas
NumPy Arrays
NumPy arrays with multiple elements cannot be converted to a single boolean:
import numpy as np
arr = np.array([1, 2, 3])
bool(arr) # ValueError: truth value of array is ambiguous
arr > 0 # array([ True, True, True]) — use element-wise
Use element-wise comparisons instead of bool() on NumPy arrays.
__len__ Fallback Can Hide Intent
If you define __len__ to return 0 for “empty” but also want a separate __bool__ concept, be explicit. Python will always prefer __bool__ over __len__ — but if you only define __len__, len(obj) == 0 becomes the falsy condition even if that wasn’t your intent.