In the above case, both the iterable and iterator are the same object. Notice that
the __iter__ method returned self. It need not be the case always.

classzrange:def__init__(self,n):self.n=ndef__iter__(self):returnzrange_iter(self.n)classzrange_iter:def__init__(self,n):self.i=0self.n=ndef__iter__(self):# Iterators are iterables too.# Adding this functions to make them so.returnselfdefnext(self):ifself.i<self.n:i=self.iself.i+=1returnielse:raiseStopIteration()

If both iteratable and iterator are the same object, it is consumed in a single iteration.

So a generator is also an iterator. You don’t have to worry about the iterator protocol.

The word “generator” is confusingly used to mean both the function that
generates and what it generates. In this chapter, I’ll use the word “generator”
to mean the genearted object and “generator function” to mean the function that
generates it.

Can you think about how it is working internally?

When a generator function is called, it returns a generator object without
even beginning execution of the function. When next method is called for the
first time, the function starts executing until it reaches yield statement.
The yielded value is returned by the next call.

The following example demonstrates the interplay between yield and call to
next method on generator object.