Generators and Iterators
Generators and iterators are fundamental concepts that enable efficient iteration over data, especially when dealing with large datasets or infinite sequences. Let's break down each concept:
Iterators:
In Python, an iterator is an object that represents a stream of data. It implements the iterator protocol, which consists of two methods:
__iter__(): Returns the iterator object itself.
__next__(): Returns the next item in the stream. When there are no more items, it raises the StopIteration exception.
Iterators are used with constructs like for loops to iterate over elements in a sequence or collection.
Generators:
Generators are a special type of iterator .
They are defined using the yield keyword instead of the return keyword. When a generator function is called, it doesn't execute immediately. Instead, it returns a generator object.
Each time the next() function is called on the generator object, the generator function runs until it encounters a yield statement. The value yielded is returned by the next() function.
The state of the generator function is saved between calls, allowing it to resume execution where it left off.
Generators are useful for generating large sequences of data on-the-fly without storing them in memory.
Here's a simple example to illustrate the difference:
Python Code
# Iterator example
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index < len(self.data):
result = self.data[self.index]
self.index += 1
return result
else:
raise StopIteration
# Generator example
def my_generator(data):
for item in data:
yield item
# Using iterator
my_iter = MyIterator([1, 2, 3, 4, 5])
for num in my_iter:
print(num)
# Using generator
gen = my_generator([1, 2, 3, 4, 5])
for num in gen:
print(num)
Both examples produce the same output, but the generator version is more concise and doesn't require manually implementing the iterator protocol.