This is not a play on words — these are fundamental Python concepts that may not be entirely clear to everyone learning the language. Let’s briefly summarize their essence below.
An iterator is an object that, upon each request (via the built-in next() function or by calling its
__next__() method), produces the next element in a sequence.
When there are no more values to provide, it signals this by raising a StopIteration exception.
The data flow between the iterator and the client code is one-way: data always flows from the iterator to the client, which consumes it.
A generator is a special kind of iterator.
It can not only yield values to the client but also receive data from the client through its
send() method.
This means that the data flow between a generator and its client can be two-way, allowing the client to influence the generator’s behavior during execution.
A generator object can be created in two ways, by using:
– a generator function, or
– a generator expression.
A generator function is any function whose definition contains the yield keyword at least once, regardless of whether it also has a return statement.
When called, a generator function returns a generator object.
A generator expression is a special kind of expression that also returns a generator object.
Syntactically, it resembles a list comprehension, except that it’s enclosed in parentheses instead of square brackets.
Because of this, it’s sometimes called a generator comprehension.
Since a generator is a special form of iterator, it’s often referred to as a generator-iterator.
The values it yields can be arbitrary Python objects, depending on how it’s implemented.
In contrast, an iterator generator is a special kind of generator that, on each next() call, yields a new iterator.
Iterators and generators are key language constructs in Python.
That’s why the e-book Python Knowledge Building Step by Step discusses them in depth, illustrating their properties and usage with practical examples and metaphors.
