In Python, container methods let your custom classes behave like built-in containers such as lists, dictionaries, and sets. By overloading these special methods, your objects can support features like len()
, in
, indexing ([]
), and iteration (for
loops) in a natural and readable way.
This makes your classes easier to work with, since they respond to familiar Python operations. Instead of writing awkward custom code to get, set, or loop through elements, you can use standard Python syntax—just like you would with a list or a dict.
To make things fun and practical, we’ll use a real-world example: a ShoppingCart
class. It will behave like a collection that holds items, lets you check what’s inside, and supports indexing and iteration. By the end, your custom container will feel just like a built-in one!
Overloading len()
with __len__
Python lets you use the built-in len()
function on your own objects by defining the __len__
method. This makes your class behave like a list, dictionary, or any other container. When someone calls len(obj)
, Python looks for obj.__len__()
under the hood.
Let’s say we have a ShoppingCart
class that keeps track of items a customer wants to buy. We can use __len__
to return how many items are currently in the cart.
class ShoppingCart:
def __init__(self):
self.items = []
def add(self, product):
self.items.append(product)
def __len__(self):
return len(self.items)
# Example usage
cart = ShoppingCart()
cart.add("apple")
cart.add("banana")
cart.add("chocolate")
print("Items in cart:", len(cart)) # Output: Items in cart: 3
In this example, __len__
returns the length of the internal list self.items
. Now len(cart)
works just like len(list)
, which is what users expect. This makes the object easier and more natural to use, especially when building real applications like e-commerce sites or inventory systems.
Overloading Membership Test with __contains__
(in
)
The in
keyword in Python is used to check if something exists inside a container. You can make your custom objects support this by defining the __contains__
method.
Let’s build a fun Library
class. We’ll use __contains__
to check if a certain book is available on the shelf.
class Library:
def __init__(self):
self.books = ["1984", "The Hobbit", "Pride and Prejudice"]
def __contains__(self, book):
return book in self.books
# Example usage
lib = Library()
print("1984" in lib) # True
print("Harry Potter" in lib) # False
In this example, __contains__
looks into the internal list of books. Now, using "1984" in lib
feels just like working with a regular list or set. It’s clean, readable, and perfect for real-world scenarios like checking inventory, available seats, active users, or even ingredients in a recipe.
Overloading Item Access with __getitem__
([]
get)
The __getitem__
method lets your custom object respond to obj[key]
or obj[index]
, just like a list or dictionary. It’s how Python handles square bracket access under the hood.
Let’s use a ShoppingCart
class where you can access items by their position (like a list).
class ShoppingCart:
def __init__(self):
self.items = ["apple", "banana", "carrot", "dates"]
def __getitem__(self, index):
return self.items[index]
# Example usage
cart = ShoppingCart()
print(cart[0]) # apple
print(cart[2]) # carrot
print(cart[1:3]) # ['banana', 'carrot']
In this example, __getitem__
just passes the index (or slice) directly to the internal list. So whether you’re fetching one item or a group of them, it works smoothly.
This method is great for creating custom containers like decks of cards, playlists, or even rows in a spreadsheet—where you want to make access as natural as using a list.
Overloading Item Assignment with __setitem__
([]
set)
The __setitem__
method lets your object respond to assignment using square brackets like obj[key] = value
. This is how lists, dicts, and other containers let you change or add elements.
Let’s expand the ShoppingCart
class so we can update items by index:
class ShoppingCart:
def __init__(self):
self.items = ["apple", "banana", "carrot", "dates"]
def __setitem__(self, index, value):
self.items[index] = value
def __repr__(self):
return f"ShoppingCart({self.items})"
# Example usage
cart = ShoppingCart()
print(cart) # ShoppingCart(['apple', 'banana', 'carrot', 'dates'])
cart[1] = "blueberry" # Replacing banana with blueberry
print(cart) # ShoppingCart(['apple', 'blueberry', 'carrot', 'dates'])
With __setitem__
, your class behaves just like a list or dictionary when it comes to changing data. It’s a natural way to update things in-place—perfect for custom data containers like shopping carts, inventories, or even pixel maps.
Overloading Item Deletion with __delitem__
(del obj[key]
)
The __delitem__
method lets your object support deleting elements using del obj[key]
. This is how you can remove items by index or key, just like with lists or dictionaries.
Let’s add this to our ShoppingCart
so we can remove items using del
:
class ShoppingCart:
def __init__(self):
self.items = ["apple", "banana", "carrot", "dates"]
def __delitem__(self, index):
del self.items[index]
def __repr__(self):
return f"ShoppingCart({self.items})"
# Example usage
cart = ShoppingCart()
print(cart) # ShoppingCart(['apple', 'banana', 'carrot', 'dates'])
del cart[2] # Removes 'carrot'
print(cart) # ShoppingCart(['apple', 'banana', 'dates'])
By adding __delitem__
, your class becomes even more flexible. You can let users clean up or manage content inside the object just like they would with built-in containers. It’s clean, readable, and powerful.
Overloading Iteration with __iter__
The __iter__
method makes your object work in for
loops, list comprehensions, and any context that expects something iterable. When you define __iter__
, Python knows how to loop over your custom object.
Let’s update the ShoppingCart
class so we can loop over its items:
class ShoppingCart:
def __init__(self):
self.items = ["apple", "banana", "carrot"]
def __iter__(self):
return iter(self.items)
def __repr__(self):
return f"ShoppingCart({self.items})"
# Example usage
cart = ShoppingCart()
for item in cart:
print("Item:", item)
Here, __iter__
returns an iterator over the internal list of items. This makes your custom object behave like a built-in list. You can also customize it to yield transformed data or even filter items while iterating. This keeps your class clean and easy to use in loops.
Overloading Iterator’s __next__
The __next__
method tells Python what to return each time you call next()
on an iterator. It defines how your object moves from one item to the next during a loop.
To use __next__
, your class must also have __iter__
, and it must return self
as the iterator.
Here’s how we can make ShoppingCart
act as its own iterator:
class ShoppingCart:
def __init__(self):
self.items = ["apple", "banana", "carrot"]
self.index = 0 # To keep track of where we are
def __iter__(self):
self.index = 0 # Reset for new loop
return self
def __next__(self):
if self.index < len(self.items):
item = self.items[self.index]
self.index += 1
return item
else:
raise StopIteration
def __repr__(self):
return f"ShoppingCart({self.items})"
# Example usage
cart = ShoppingCart()
print(next(cart)) # apple
print(next(cart)) # banana
# Using in loop
for item in cart:
print("Looped:", item)
Here, __next__
returns one item at a time, and raises StopIteration
when done. This pattern gives you full control over how iteration works. It’s handy when you want to generate items on the fly, apply filters, or build custom stepping logic.
ShoppingCart
Class with All Container Methods
Here’s a complete ShoppingCart
class that includes all the container methods we covered. It lets you use Python’s built-in container features naturally, like len()
, in
, indexing, setting, deleting, and iteration — even next()
!
class ShoppingCart:
def __init__(self):
self.items = {
"apple": 3,
"banana": 5,
"carrot": 2
}
self._iter_keys = []
self._iter_index = 0
def __len__(self):
# Total unique items in the cart
return len(self.items)
def __contains__(self, item):
# Check if item is in the cart
return item in self.items
def __getitem__(self, key):
# Get quantity of a given item
return self.items[key]
def __setitem__(self, key, value):
# Set or update quantity for an item
self.items[key] = value
def __delitem__(self, key):
# Remove an item from the cart
del self.items[key]
def __iter__(self):
# Prepare iteration over item names
self._iter_keys = list(self.items.keys())
self._iter_index = 0
return self
def __next__(self):
# Return next item name or stop
if self._iter_index < len(self._iter_keys):
item = self._iter_keys[self._iter_index]
self._iter_index += 1
return item
else:
raise StopIteration
def __repr__(self):
return f"ShoppingCart({self.items})"
# Usage example:
cart = ShoppingCart()
print("Cart length:", len(cart)) # 3 items
print("Is 'apple' in cart?", 'apple' in cart) # True
print("Banana quantity:", cart['banana']) # 5
cart['banana'] = 10 # Update quantity
print("Updated banana quantity:", cart['banana'])
del cart['carrot'] # Remove carrot
print("After deletion:", cart)
print("Iterating over items:")
for item in cart:
print("-", item)
# Using next() explicitly
cart_iter = iter(cart)
print("Next item:", next(cart_iter))
print("Next item:", next(cart_iter))
This ShoppingCart
class is a practical example of how container methods make your custom objects feel natural in Python. You can easily check lengths, test membership with in
, access and update quantities by keys, delete items, and iterate over the items one by one — just like with real Python containers. This makes your code clean and intuitive while managing your shopping list!
Conclusion
Overloading container methods allows your custom classes to act just like Python’s built-in containers such as lists, dictionaries, and sets. This makes your objects intuitive to use and lets you write clear, expressive code with familiar syntax like len()
, in
, indexing, and iteration. By adding these methods, you help your classes fit smoothly into Python programs and make them easier to work with.
Try experimenting with these container methods in your own projects—whether you’re managing collections, building custom data structures, or creating complex containers. It’s a great way to improve your code’s usability and take full advantage of Python’s powerful, readable style.