An iterator is an object that enables traversal through a collection of elements, one at a time, in a forward-only manner. It does not support backward movement or random access. In Python, iterators implement two essential methods: __iter__() and __next__(). Built-in types such as lists, tuples, and strings are inherently iterable and can be converted into iterators using the iter() functon. Example: ```
numbers = [10, 20, 30, 40]
iterator = iter(numbers)
for num in iterator: print(num, end=" ")
Output: 10 20 30 40
#### Creating Custom Ietrators
To make a class iterable, define the `__iter__()` and `__next__()` methods. The `__iter__()` method must return the iterator object itself (typically `self`), while `__next__()` returns the next value in the sequence. When no more values are available, it must raise `StopIteration` to signal termination. Here’s a class that generates an infinite sequence of integers starting from 1: ```
class Counter:
def __init__(self):
self.current = 0
def __iter__(self):
return self
def __next__(self):
self.current += 1
return self.current
counter = Counter()
it = iter(counter)
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
Controlling Iteration with StopIteration
To prevent infinite loops, limit iteration by raising StopIteration after a condition is met. Example: Generate numbers from 1 to 15 only. ```
class LimitedCounter:
def init(self, limit=15):
self.limit = limit
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current >= self.limit:
raise StopIteration
self.current += 1
return self.current
for value in LimitedCounter(15): print(value, end=" ")
Output: 1 2 3 ... 15
### Generators
A generator is a special type of iterator created using a function that contains one or more `yield` statements. Unlike regular functions that return a value and terminate, generators pause execution at each `yield`, retain their state, and resume from where they left off on subsequent calls. When called, a generator function returns a generator object — an iterater that produces values on demand. Example: A countdown generator ```
def countdown(start):
while start > 0:
yield start
start -= 1
gen = countdown(5)
print(next(gen)) # 5
print(next(gen)) # 4
print(next(gen)) # 3
for remaining in gen:
print(remaining, end=" ")
# Output: 2 1
Generators are memory-efficient because they compute values lazily — only when requested. This makes them ideal for processing large datasets or streams of data without loading everything into memory at once.