Iterators are objects that implement the iterator protocol, consisting of two methods:
__iter__()
: Returns the iterator object itself.__next__()
: Returns the next item in the sequence. When no more items are available, it raises the StopIteration
exception.In Python, any object that implements these methods can be used as an iterator. Iterators are used in for
loops, comprehensions, and other contexts where sequential or lazy evaluation is required.
class Counter:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current > self.end:
raise StopIteration
else:
self.current += 1
return self.current - 1
# Usage:
counter = Counter(1, 5)
for num in counter:
print(num)
Generators are a type of iterable, but unlike regular classes implementing the iterator protocol, generators use the yield
statement to produce a series of values lazily. This means they generate values only when requested and maintain their state between calls.
Key characteristics of generators:
next()
is called.def
keyword with yield
statements inside the function body.def square_numbers(n):
for i in range(n):
yield i ** 2
# Usage:
gen = square_numbers(5)
for num in gen:
print(num)
()
instead of square brackets []
.gen_exp = (x ** 2 for x in range(5))
for num in gen_exp:
print(num)
send()
Method: Allows sending data into a generator.throw()
Method: Raises an exception at a specific point in the generator.close()
Method: Terminates the generator.send()
:def accumulator():
total = 0
while True:
value = yield total
if value is None:
break
total += value
acc = accumulator()
next(acc) # Prime the generator
print(acc.send(1)) # Output: 1
print(acc.send(2)) # Output: 3
acc.close()