4

I know what iterators and generators are. I know the iteration protocol, and I can create both. I read the following line everywhere: "Every generator is an iterator, but not vice versa." I understand the first part, but I don't understand the "not vice versa" part. What does the generator object have that any simple iterator object does not?

I read this question but it does not explain why an iterator is not a generator. Is it just the syntax yield that explains the difference?

Thanks in advance.

André C. Andersen
  • 8,955
  • 3
  • 53
  • 79
Zohaib Ijaz
  • 21,926
  • 7
  • 38
  • 60

3 Answers3

4

I know what's iterator, what's generator, what's iteration protocol, how to create both.

What's an iterator?

Per the glossary, an iterator is "an object representing a stream of data". It has an __iter__() method returns itself, and it has a next() method (which is __next__() in Python 3). The next-method is responsible for returning a value, advancing the iterator, and raising StopIteration when done.

What is a generator?

A generator is a regular Python function containing yield. When called it returns a generator-iterator (one of the many kinds of iterator).

Examples of how to create generators and iterators

Generator example:

>>> def f(x):           # "f" is a generator
        yield x
        yield x**2
        yield x**3

>>> g = f(10)           # calling "f" returns a generator-iterator 
>>> type(f)             # "f" is a regular python function with "yield"
<type 'function'>
>>> type(g)                 
<type 'generator'>
>>> next(g)             # next() gets a value from the generator-iterator
10
>>> next(g)
100
>>> next(g)
1000
>>> next(g)             # iterators signal that they are done with an Exception

Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    next(g)
StopIteration
>>> dir(g)              # generator-iterators have next() and \__iter__
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']

Iterator using a class:

>>> class Powers:       # "Powers" is a class
        def __init__(self, base):
            self.base = base
            self.exp = 0
        def __iter__(self):
            return self
        def next(self):
            self.exp += 1
            if self.exp > 3:
                raise StopIteration
            return self.base ** self.exp

>>> g = Powers(10)      # calling "Powers" returns an iterator
>>> type(Powers)        # "Power" is a regular python class
<type 'classobj'>
>>> type(g)             # "g" is a iterator instance with next() and __iter__()
<type 'instance'>   
>>> next(g)             # next() gets a value from the iterator
10
>>> next(g)
100
>>> next(g)
1000
>>> next(g)             # iterators signal that they are done with an Exception

Traceback (most recent call last):
  File "<pyshell#34>", line 1, in <module>
    next(g)
StopIteration

Iterator from a sequence example:

>>> s = 'cat'               
>>> it = iter(s)            # creates an "iterator" from a sequence 
>>> type(s)                 # "s" is a string which is "iterable"
<type 'str'>
>>> type(it)                # An "iterator" with next() and __iter__()
<type 'iterator'>
>>> next(it)
'c'
>>> next(it)
'a'
>>> next(it)
't'
>>> next(it)

Traceback (most recent call last):
  File "<pyshell#43>", line 1, in <module>
    next(it)
StopIteration

Comparison and conclusion

An iterator is an object representing a stream of data. It has an __iter__() method and a next() method.

There are several ways to make an iterator:

1) Call a generator (a regular python function that uses yield) 2) Instantiate a class that has an __iter__() method and a next() method.

From this, you can see that a generator is just one of many ways to make an iterator (there are other ways as well: itertools, iter() on a regular function and a sentinel, etc).

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • one can send(value) to generators but not iterators. every call to iterator returns or raise while generators can 'return a value via yield multiple times for once entering – droid192 Oct 29 '20 at 11:02
  • That is a red-herring. An iterator class could easily add a *send()* method if desired. Also, generators and iterators both existing before we extended their capabilities with *send()*. – Raymond Hettinger Oct 29 '20 at 21:28
0

In python3 an iterator is an object with a __next__ method. That's all.

For an object to be a generator it needs __next__ method but it also use a yield statement.

So both object have a __next__ method and so are iterator but the first object doesn't always have a yield statement so an iterator is not necessarily a generator.

In fact it means that when you generate a generator all its code is not run at once. Meanwhile with a more classical iterator you will run only once the generation code.

Peni
  • 626
  • 1
  • 7
  • 18
-1

It's just that generators are a specific kind of iterators. Their two particular traits are the lazy evaluation (no value is computed in anticipation of it being requested), and the fact that once exhausted, they cannot be iterated once again.

On the other hand, an iterator is no more than something with a __next__ method, and an __iter__ method.

So lists, tuples, sets, dictionaries... are iterators. But those are not generators, because all of the elements they contain are defined and evaluated after the container initialization, and they can be iterated over many times.

Therefore, some iterators are not generators.

Right leg
  • 16,080
  • 7
  • 48
  • 81