__len__
__len__(self) Overview
__len__ is a Python magic method (dunder method) that lets you define what “length” means for your own objects. Python calls it automatically when you pass your object to the built-in len() function. The method must return an integer greater than or equal to zero.
If you try to call len() on an object that doesn’t implement __len__, Python raises TypeError.
Signature
def __len__(self) -> int:
Python invokes __len__ automatically — you never call it directly. The return type must be an int. Returning any other type raises TypeError.
When Python Calls __len__
| Context | Example |
|---|---|
| Explicit conversion | len(obj) |
| Truthiness check fallback | if obj: (when __bool__ is not defined) |
| Sequence repetition | [x * n for x in obj] or obj * n |
| Slice length | obj[start:stop] |
| Constructor calls | list(obj), tuple(obj), set(obj) |
Basic Example
Here’s a simple container that tracks items:
class TaskList:
def __init__(self, tasks=None):
self.tasks = tasks or []
def __len__(self):
return len(self.tasks)
tasks = TaskList(["write tests", "review PR", "deploy"])
print(len(tasks))
# Output: 3
tasks.tasks.clear()
print(len(tasks))
# Output: 0
Return Type
Python requires __len__ to return an integer. Returning a float, string, or any other type raises TypeError:
class Bad:
def __len__(self):
return "three" # Wrong type
# len(Bad()) # TypeError: __len__ must return int, not str
Negative values are technically allowed but semantically wrong — length can never be negative:
class Weird:
def __len__(self):
return -1 # Allowed by Python, but meaningless
# len(Weird()) # -1 — confusing and probably a bug
__len__ and __bool__ Relationship
Python checks __bool__ before __len__ when evaluating truthiness. If __bool__ is not defined, Python falls back to len(obj) > 0:
class Empty:
def __len__(self):
return 0
print(bool(Empty())) # False — len > 0 is False
# Output: False
If neither __len__ nor __bool__ is defined, the object is always truthy:
class Nothing:
pass
print(bool(Nothing())) # True — no methods defined
# Output: True
This fallback is why empty containers ([], {}, "") are falsy — they all implement __len__.
Practical Use Cases
Custom Container with Backing Store
class Stack:
def __init__(self):
self._items = []
def push(self, item):
self._items.append(item)
def pop(self):
return self._items.pop()
def __len__(self):
return len(self._items)
stack = Stack()
stack.push("first")
stack.push("second")
print(len(stack)) # 2
stack.pop()
print(len(stack)) # 1
Database Row Count
class QueryResult:
def __init__(self, rows):
self.rows = rows
def __len__(self):
return len(self.rows)
result = QueryResult([{"id": 1}, {"id": 2}, {"id": 3}])
print(len(result))
# Output: 3
Tree Node Children
class TreeNode:
def __init__(self, value, children=None):
self.value = value
self.children = children or []
def __len__(self):
return len(self.children)
root = TreeNode("root", [
TreeNode("child1"),
TreeNode("child2", [TreeNode("grandchild")])
])
print(len(root)) # 2 — direct children of root
print(len(root.children[1])) # 1 — child2 has one child
Gotchas
NotImplemented Is Not Valid
Unlike binary operators, returning NotImplemented from __len__ raises TypeError. NotImplemented only works for binary operators where Python can try a reflected version.
class Bad:
def __len__(self):
return NotImplemented
# len(Bad()) # TypeError
len() Does Not Fall Back to __iter__
Some operators fall back when missing (e.g., __contains__ falls back to __iter__). len() does not fall back to anything. If __len__ is missing, it raises TypeError directly.
class NoLen:
def __iter__(self):
return iter([])
# len(NoLen()) # TypeError — no fallback
Slices Don’t Use __len__
obj[start:stop] calls __getitem__, not __len__. The length of a slice is determined by the slice object itself, not by any protocol method on your class.
class MyList:
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
def __getitem__(self, index):
return self.data[index]
m = MyList([1, 2, 3, 4, 5])
print(len(m)) # 5 — __len__ called
print(m[1:4]) # [2, 3, 4] — __getitem__ called, not __len__
NumPy Arrays
NumPy arrays override __len__ to return the first dimension size:
import numpy as np
arr = np.array([[1, 2], [3, 4]])
print(len(arr)) # 2 — first dimension
print(arr.shape) # (2, 2) — full shape
This is consistent with Python’s general behavior: len() on multi-dimensional sequences returns the size of the outermost sequence.
See Also
__bool__— truthiness and the__len__fallback__getitem__— accessing items by index__contains__— membership test behavior