Python: Iterables and Iterators

Python: Iterables and Iterators

ยท

2 min read

I was teaching PythonToProject Bootcamp the other day. As an add-on module, I was teaching Iterators and how to implement custom iterators in Python.

To explain iterators as a concept, I introduced them to the following example.

x = [1, 2, 3]

print (next(x))
print (next(x))
print (next(x))
print (next(x))

Did you see it yet? I didn't. Let me show you what happened. On running the code, we got the following error.

Traceback (most recent call last):
  File "4_iterator.py", line 18, in <module>
    print (next(x))
TypeError: 'list' object is not an iterator

Now, this is where I started questioning my Python skills. Isn't list an iterator? But I can loop through it, right? right....???? It took a while to hit me, but I got there.

"List is an iterable not an iterator." Hence you need to make it iterable to know "iterate" over it.

Iterator vs. Iterable

So what's the difference?

Iterator is a class that loops over an iterable. It has the __next__ method.

Iterable is a data structure that can be looped over. It specifies how to loop over itself using __iter__ method.

class Reverse:
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return ReverseIterator(self)


class ReverseIterator:
    def __init__(self, iterable):
        self.iterable = iterable
        self.index = iterable.index

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.iterable.data[self.index]


r = Reverse([1, 2, 3])
for i in r:
    print(i)

Now that looks like overkill, can Reverse itself serve as an Iterator and Iterable? Looks like it can.

Iterators are themselves also iterable, with the distinction that their __iter__() method returns the same object (self), regardless of whether or not its items have been consumed by previous calls to the next()

class Reverse:
    """Iterator for looping over a sequence backwards."""

    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]


r = Reverse([1, 2, 3])
for i in r:
    print(i)

Iterable with __getitem__

Iterables are also implemented with __getitem__ method. The __getitem__ method takes the index as an attribute. In a typical list, we access the element at that index. In our Reverse example, we write our own logic to read the list backward

class Reverse:
    """Iterator for looping over a sequence backwards."""

    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __getitem__(self, key):
        if self.index == 0:
            raise StopIteration
        self.index -= 1
        return self.data[self.index]


r = Reverse([1, 2, 3])
for i in r:
    print(i)
ย