pyguides

__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__

ContextExample
Explicit conversionlen(obj)
Truthiness check fallbackif obj: (when __bool__ is not defined)
Sequence repetition[x * n for x in obj] or obj * n
Slice lengthobj[start:stop]
Constructor callslist(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