Sitemap
Saptarshi Basu

Saptarshi Basu

Python | Difference between Iterable and Iterator

5 min readSep 15, 2024

--

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 iterator

However, 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)
Press enter or click to view image in full size

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 iterable

As 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

--

--