Background: The concept of iterability appeared in Python to unify the handling of collections: lists, dictionaries, sets, etc. Any object that can be traversed using a for loop is considered iterable. This is implemented through certain magic methods.
The Problem: Python requires certain protocols for the correct functioning of loops and functions related to sequences. If the user incorrectly implements these protocols in their class, standard mechanisms (for, list(), sum(), etc.) will not work or will behave unexpectedly.
The Solution:
An object is iterable if it implements the __iter__ method. An iterator is one that has the __next__ and __iter__ methods, with __iter__ returning itself. Usually, the object returned by __iter__ is an iterator, but it doesn't have to be. Example:
class MyRange: def __init__(self, start, end): self.start = start self.end = end def __iter__(self): self.current = self.start return self def __next__(self): if self.current < self.end: val = self.current self.current += 1 return val raise StopIteration for x in MyRange(1, 4): print(x) # 1, 2, 3
Key Features:
__iter__ method.__next__ and __iter__ (returning self).__iter__ is effectively implemented if multiple independent passes over the collection are needed.Is the __next__ method mandatory for any iterable object?
No. An iterable object only requires __iter__, which returns an iterator. __next__ is only implemented by the iterator itself. For example, the list does not have a __next__ method, but it is iterable: its __iter__ returns an instance of an iterator.
lst = [1, 2, 3] print(hasattr(lst, '__next__')) # False
Can an object be an iterator by itself?
Yes, if it implements both methods — __iter__ and __next__.
Can multiple iterators with independent states be created for one collection?
Yes, if __iter__ returns a new iterator object each time.
class MyList: def __init__(self, data): self.data = data def __iter__(self): return iter(self.data)
__next__ without __iter__ (or vice versa).Negative Case: An iterator class stores state (e.g., current index) at the class level instead of the instance level, causing parallel traversals to interfere with each other. Pros:
Positive Case:
Each iterator stores its state in the instance created in __iter__.
Pros: