Learn how to make your custom Python classes behave like built-in types using special methods like __len__, __iter__, __next__, and __contains__. This blog breaks down each method with real-world examples and practical tips for writing clean code.
If you've ever used a for
loop, checked if a value is in a list, or called len()
on something, you've been using Python's data model. Specifically, special methods like __len__
, __iter__
, __next__
, and __contains__
. These “dunder methods” let you make your own Python objects behave like built-in types. Want your custom class to work with len()
, in
, or a for
loop? These methods are the key.
Let’s break them down with examples, everyday explanations.
__len__
: Make len()
Work on Your ObjectWhen you call len(something)
, Python looks for a __len__()
method under the hood.
If you build a class and want len(my_obj)
to return something meaningful, like how many items are inside it, you define __len__
.
class ShoppingCart:
def __init__(self):
self.items = []
def add_item(self, item):
self.items.append(item)
def __len__(self):
return len(self.items)
cart = ShoppingCart()
cart.add_item("apple")
cart.add_item("banana")
print(len(cart)) # Output: 2
__iter__
: Make Your Object IterableThis method lets Python know how to start looping over your object.
With __iter__
, your object can be used in a for
loop, list comprehensions, or anything that expects something iterable.
class Numbers:
def __init__(self):
self.data = [10, 20, 30]
def __iter__(self):
return iter(self.data) # Could also return a custom iterator
nums = Numbers()
for n in nums:
print(n) # 10 20 30
__next__
: Define What Happens On Each Iteration StepThis method returns the next item from your object. It’s usually paired with __iter__
.
If you're building a custom iterator from scratch, __next__
is where the actual “step-by-step" happens.
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self # The object is its own iterator
def __next__(self):
if self.current < 0:
raise StopIteration
val = self.current
self.current -= 1
return val
for num in Countdown(3):
print(num) # 3 2 1 0
__contains__
: Control How in
WorksThis method is triggered when you write something like:
if "apple" in cart:
It lets you define what it means for an item to “be inside” your object.
class ShoppingCart:
def __init__(self):
self.items = ["apple", "banana"]
def __contains__(self, item):
return item in self.items
cart = ShoppingCart()
print("banana" in cart) # True
print("milk" in cart) # False
If you don’t implement __contains__
, Python will automatically fall back to looping over the object using __iter__
. That’s helpful!
Let’s look at a bigger picture. When you do this:
if "something" in my_obj:
Python tries the methods in this order:
__contains__
.__iter__
and compare each item.TypeError
.When you do this:
for x in my_obj:
Python does this behind the scenes:
__iter__()
to get an iterator.__next__()
on the iterator.StopIteration
is raised.__contains__
.You might just need __iter__
if you're building a class that behaves like a list.
You might only need __len__
if you're showing count in a UI or enforcing limits.
And sometimes, using yield
and generators is even easier for iteration than managing __next__
manually.
Method | Purpose | Used by... |
---|---|---|
__len__ |
Define what len(obj) returns |
len() |
__iter__ |
Make object iterable | for , in , list comps |
__next__ |
Get the next item in a loop | next() , for |
__contains__ |
Define in behavior |
"x" in obj |
These special methods are like magic doors into Python’s built-in behavior. Once you learn to use them, you can make your classes feel like real native Python types. If you're building something reusable or preparing for interviews, mastering __len__
, __iter__
, __next__
, and __contains__
is a great move.