Comprehensions and for-loops are the most frequently used language features for performing iteration. The both take items one by one from a source and do something with each in turn. However, both comprehensions and for-loops iterate over the whole sequence by default, whereas sometimes more fine-grained control is needed. In this section we'll see how you can exercise this kind of fine-grained control by investigating two important concepts on top of which a great deal of Python language behavior is constructed: iterable objects and iterator objects, both of which are reflected in standard Python protocols.
The iterable protocol defines an API that iterable objects must implement. That is, if you want to be able to iterate over an object using for-loops or comprehensions, that object must implement the iterable protocol. Built-in classes like list...