What is the difference between iterators and generators? Some examples for when you would use each case would be helpful.
A Generator is an Iterator
A generator is a subtype of iterator:
>>> import collections >>> import types >>> def gen(): yield >>> g = gen() >>> isinstance(g, collections.Iterator) True >>> issubclass(types.GeneratorType, collections.Iterator) True An Iterator is an Iterable
An Iterator is an Iterable,
>>> issubclass(collections.Iterator, collections.Iterable) True which requires an __iter__ method that returns an Iterator:
>>> collections.Iterable() Traceback (most recent call last): File "<pyshell#79>", line 1, in <module> collections.Iterable() TypeError: Can't instantiate abstract class Iterable with abstract methods __iter__ Examples of iterables are tuples, lists, sets, dicts, strings, and range objects:
>>> all(isinstance(element, collections.Iterable) for element in ( (), [], {}, set(), '', range(0))) True Iterators require a next or __next__ method
In Python 2:
>>> collections.Iterator() Traceback (most recent call last): File "<pyshell#80>", line 1, in <module> collections.Iterator() TypeError: Can't instantiate abstract class Iterator with abstract methods next And in Python 3:
>>> collections.Iterator() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class Iterator with abstract methods __next__ We can get the iterators from the builtin objects (or custom objects) with the iter function:
>>> all(isinstance(iter(element), collections.Iterator) for element in ( (), [], {}, set(), '', range(0))) True The __iter__ function is what is invoked when you attempt to use an object with a for-loop. Then __next__ or next is called on the iterator object to get each item out for the loop. The iterator raises StopIteration when you have exhausted it, and it cannot be reused at that point.
From the docs:
From the Generator Types section of the Iterator Types section of the Built-in Types documentation:
Python’s generators provide a convenient way to implement the iterator protocol. If a container object’s
__iter__()method is implemented as a generator, it will automatically return an iterator object (technically, a generator object) supplying the__iter__()andnext()[__next__()in Python 3] methods. More information about generators can be found in the documentation for the yield expression.
(Emphasis added.)
So from this we learn that Generators are a (convenient) type of Iterator.
Example Iterator Objects
You might create object that implements the Iterator protocol by creating or extending your own object.
class Yes(collections.Iterator): def __init__(self, stop): self.x = 0 self.stop = stop def __iter__(self): return self def next(self): if self.x < self.stop: self.x += 1 return 'yes' else: # Iterators must raise when done, else considered broken raise StopIteration __next__ = next # Python 3 compatibility But it's easier to simply use a Generator to do this:
def yes(stop): for _ in range(stop): yield 'yes' Or perhaps simpler, a Generator Expression (works similarly to list comprehensions):
yes_expr = ('yes' for _ in range(stop)) They can all be used in the same way:
>>> stop = 4 >>> for i, y1, y2, y3 in zip(range(stop), Yes(stop), yes(stop), ('yes' for _ in range(stop))): ... print('{0}: {1} == {2} == {3}'.format(i, y1, y2, y3)) ... 0: yes == yes == yes 1: yes == yes == yes 2: yes == yes == yes 3: yes == yes == yes Conclusion
In the vast majority of cases, you are best suited to use yield to define a function that returns a Generator instead or consider Generator Expressions.
However, you can use the Iterator protocol directly when you need to extend a Python object as an object that can be iterated over.