Iterable

What’s an Iterable

An object capable of returning its members one at a time. Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict, file objects, and objects of any classes you define with an iter() method […]

Iterables can be used in a for loop and in many other places where a sequence is needed […]. When an iterable object is passed as an argument to the built-in function iter(), it returns an iterator for the object. This iterator is good for one pass over the set of values.

Source

Iterable base class (without type hints)

class Iterable:

    def __iter__(self):
        """
        Returns an Iterator
        """

An Iterable is a class implementing the __iter__ method.

for … in loop

When showing the for loop semantics in the Iterator chapter the Iterator was left out.

Actually a simplified version of a function mimicking a for ... in loop behavior to print the values of an Iterator must be written as:

def for_loop(iterable):
    iterator = iter(iterable)
    while True:
        try:
            value = next(iterator)
            print(value)
        except StopIteration:
            return

for_loop(get_something_iterator())

When using iterables, it is usually not necessary to call iter() or deal with iterator objects yourself. The for statement does that automatically for you, creating a temporary unnamed variable to hold the iterator for the duration of the loop

Source

Therefore an Iterator can be represented as:

class Iterator(Iterable):
    def __next__(self):
        """Return the next item from the iterator.
        When exhausted, raise StopIteration
        """

    def __iter__(self):
        """
        Return the corresponding Iterator which is self
        """
        return self

And a Generator can be represented now as:

class Generator(Iterator):
    def send(self, value):
        """Send a value into the generator.
        Return next yielded value or raise StopIteration.
        """

    def throw(self, typ, val=None, tb=None):
        """Raise an exception in the generator.
        Return next yielded value or raise StopIteration.
        """

    def close(self):
        """Raise GeneratorExit inside generator.
        """
        try:
            self.throw(GeneratorExit)
        except (GeneratorExit, StopIteration):
            pass
        else:
            raise RuntimeError("generator ignored GeneratorExit")

    def __next__(self):
        return self.send(None)

    def __iter__(self):
        return self

    def __del__(self):
        try:
            self.close()
        except Exception:
            pass