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)