Python | Difference between Iterable and Iterator
There are many good articles on Python Iterator objects and the difference between Iterables and Iterators. This is yet another humble effort to try and distinguish the difference.
Python List is an Iterable object but it is not an Iterator
Let’s try to understand this statement using a simple definition of the terms “Iterable” and “Iterator”.
If you are able to call the “iter” method on an object, technically it can be defined as “Iterable”. However, you must be able to call the “next” method on an object in order to define it as “Iterator”.
The follwing example attempts to describe the definition.
When I call the built-in “next” method on a Python list, the statement fails:
# python list is NOT an iterator
my_integer_list = [1, 2, 3, 4]
next(my_integer_list)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-25-989c3f956e6d> in <module>
2 my_integer_list = [1, 2, 3, 4]
3
----> 4 next(my_integer_list)
5
TypeError: 'list' object is not an iteratorHowever, I am able to call the built-in “iter” method on a Python list.
# python list is Iterable
my_integer_list = [1, 2, 3, 4]
iter(my_integer_list)The above example conforms to the definition and hence we conclude that Python list is an “Iterable” object, however, it is not an “Iterator”.
In the following sections we will try to examine the “iter” and “next” methods and how can we construct our own iterator object.
Sneak peak under the hood
The built-in “dir” method can be used to investigate the methods and attributes of an object.
In the following example, I have called the built-in “dir” method on the Python list object.
Get Saptarshi Basu’s stories in your inbox
Join Medium for free to get updates from this writer.
As can be seen from the output, the Python list object contains a method namely “__iter__” (dunder iter). However, it doesn’t contain “__next__” (dunder next) method.
dir(my_integer_list)
[
'__add__',
'__class__',
'__contains__',
'__delattr__',
'__delitem__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__gt__',
'__hash__',
'__iadd__',
'__imul__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__rmul__',
'__setattr__',
'__setitem__',
'__sizeof__',
'__str__',
'__subclasshook__',
'append', 'clear', 'copy', 'count',
'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort'
]
# Alternate way to call dunder dir
my_integer_list.__dir__()When I call the “iter” method on the list object, a new object of type “Iterator” is returned. This new object contains the dunder next (“__next__”) method.
new_object = iter(my_integer_list)
new_object
---------------------------------------------------------------------------
<list_iterator at 0x20b29191910>dir(new_object)
['__class__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__length_hint__',
'__lt__',
'__ne__',
'__new__',
'__next__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__setstate__',
'__sizeof__',
'__str__',
'__subclasshook__']Construct our own Iterator
Based on the initial definition, if I define the “__next__” (dunder next) method inside a class, it should become an iterator. Let’s examine if this is true.
In the following example, I have created a class ‘MyIterator’ and defined the __next__() method, which doesn’t do much but returns a string.
# class MyIterator defines __next__ method
class MyIterator:
def __init__(self):
pass
def __next__(self):
return "Hello"
# creating an object of MyIterator class
my_iterator_object = MyIterator()
print("Let's execute the built-in next method on my iterator object...\n\n")
print(next(my_iterator_object))
print(next(my_iterator_object))
print(next(my_iterator_object))
print("\n\nLet's try to use the Python for loop construct on my iterator object...")
for i in my_iterator_object:
print(i)
==: OUTPUT :==
Let's execute the built-in next method on my iterator object...
Hello
Hello
Hello
Let's try to use the Python for loop construct on my iterator object...
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-65-da9490bb8961> in <module>
18 print("\n\nLet's try to use the Python for loop construct on my iterator object...")
19
---> 20 for i in my_iterator_object:
21 print(i)
TypeError: 'MyIterator' object is not iterableAs can be seen from this example, I am able to execute the “next” method on my object, however, I am not able to use Python ‘for loop’ construct on this object. Python complains that the object is not ‘iterable’.
OK.
So let’s define __iter__ method. Technically, by defining __iter__() method, we would have fulfilled the criteria for ‘Iterable’ object.
# class MyIterator defines __next__ and __iter__ methods
class MyIterator:
def __init__(self):
pass
def __iter__(self):
pass
def __next__(self):
return "Hello"
# creating an object of MyIterator class
my_iterator_object = MyIterator()
print("Let's execute the built-in next method on my iterator object...\n\n")
print(next(my_iterator_object))
print(next(my_iterator_object))
print(next(my_iterator_object))
print("\n\nLet's try to use the Python for loop construct on my iterator object...")
for i in my_iterator_object:
print(i)
==: OUTPUT :==
Let's execute the built-in next method on my iterator object...
Hello
Hello
Hello
Let's try to use the Python for loop construct on my iterator object...
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-64-e65bd8229b91> in <module>
19
20 print("\n\nLet's try to use the Python for loop construct on my iterator object...")
---> 21 for i in my_iterator_object:
22 print(i)
23
TypeError: iter() returned non-iterator of type 'NoneType'Well! Even after defining the __iter__ method, I am not able to use the Python ‘for loop’ construct.
What is it that I am doing wrong here!!
The __iter__ method must return an object, and the object that it returns must have __next__ method.
Let me try to explain the concept by making a small change to the code. This time the __iter__ method returns “self”, which is basically a reference to itself. Since the returned object (an instance of MyIterator) also has __next__ method defined, the “for loop” works this time (even if it is an infinite loop).
# class MyIterator defines __next__ and __iter__ methods
# __iter__ method returns self reference
class MyIterator:
def __init__(self):
pass
def __iter__(self):
return self
def __next__(self):
return "Hello"
# creating an object of MyIterator class
my_iterator_object = MyIterator()
print("Let's execute the built-in next method on my iterator object...\n\n")
print(next(my_iterator_object))
print(next(my_iterator_object))
print(next(my_iterator_object))
print("\n\nLet's try to use the Python for loop construct on my iterator object...")
for i in my_iterator_object:
print(i)
==: OUTPUT :==
Let's execute the built-in next method on my iterator object...
Hello
Hello
Hello
Let's try to use the Python for loop construct on my iterator object...
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
...
...
...
...
(continued infinitely)An useful Iterator object
Now let’s try to create an Iterator class that is somewhat useful and doesn’t loop infinitely.
In the following example, I have created ArithmeticProgression class that stops iteration if the next item is greater than 100.
Refer — https://en.wikipedia.org/wiki/Arithmetic_progression
# Arithmetic progression Iterator with max val 100
class ArithmeticProgression:
def __init__(self, first_term=1, common_difference=1):
self.first_term = first_term
self.common_difference = common_difference
self._n = 0
self._max_term = 100
def __iter__(self):
return self
def __next__(self):
self._n += 1
nth_term = self.first_term + (self._n - 1) * self.common_difference
if nth_term > 100:
raise StopIteration()
else:
return nth_term
my_ap_series = ArithmeticProgression(first_term=1, common_difference=10)
for i in my_ap_series:
print(i)
==: OUTPUT :==
1
11
21
31
41
51
61
71
81
91





