Python Operator Overloading: Container Methods

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.