Table of Contents
what is an iterable ?
In python , objects are abstraction of data , they have methods that work with data , and help us to manipulate it . If we take a look at a list , and see all of its methods
>>> import json
>>> _list = []
# an empty list
>>> json_list = json.dumps(dir(_list), sort_keys=True, indent=4)
# dump the methods and properties of a list to a json object
>>> print(json_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"
]
# print the json object
we can see that we can add elements to a list using add , and that we can remove an element from a list using remove , and we can also see that we have some methods which start with a double underscore . These methods are called special methods , and they are implemented by default on some of python data types such as list , and dictionaries ..
in python everything is an object , an integer a string , a double , a function a class .. they are all objects . A instance of a class is also called an object . An object has a type , it has data [properties , attributes] , and it has some methods . An iterable object is an object that defines a way to iterate over its data .
But what does iterate mean ? Iterate simply means to loop over each of its elements , and do something with , like printing or calculating the sum , or whatever. Let us give an example of an iterable , let us say we have some electronics in our house , and we want to store them in python , where should we store them ?
We can store them in a list . A list is just a collection of elements , that we can iterate over , hence we can access the elements that we have stored through looping .
# what is an iterable ?
# want to print the gadgets that we have
# store them in an iterable ?
# yes
# ?
# -> an iterable enable looping through an object
gadgets = ['i7', 's8', 'samsung screen']
# store the gadget in a list , which is an iterable
# to loop through the gadgets , because we want to print
# them
for gadget in gadgets:
# print the gadget
print(f'{gadget}')
# output
i7
s8
samsung screen
Let us give another example , let us say , we have a list of pencil boxes , and each pencil box has a price . We want to store the pencil boxes with their price . Which python type should we use ? The answer is a dictionary : a dictionary is formed of a key and a value , and it helps us to loop through the data .
>>> pencilBoxes = {'pencilBoxA': 2.3, 'pencilBoxB': 1.5}
# pencil boxes ?
# price ?
# {pencilBoxA : price }
# ~ pencilBoxA : name of the pencil box
# ~ price : The price of the pencil in $
>>> print(f'The sum of the prices of the Pencil Boxes is :'
f' {sum(pencilBoxes.values())}'
# we use values to get the values inside the dictionary
)
The sum of the prices of the Pencil Boxes is : 3.8
# calculate total Price
What is an iterator ?
As we have seen in the previous section , an iterable is an object that define a way to loop over its data , so its properties or attributes or methods .
An iterator is an object that enable us to traverse the data of an iterable. An object might define more than one way to loop over its data , hence one or more iterator .
Let us give an example . we want to search for something by using google . for example we want to search for some slides , as such we enter the keyword slide , in google search .
Google has all the data that is related to the keyword slide , this is the search object slide . The search object slide is an iterable since it defines a way to loop over its data . Google provide various iterators to traverse the search object slide , we can traverse it and filter it by date , by language ..
What is the iter function ?
The iter function is used to get an iterator from an object in python . It is used by the for loop , the sum … to get an iterator which is used to loop over the data . the iter function syntax is the following
iter(iterable) -> iterator iter(callable, sentinel) -> iterator
How to create an iterator in python
In python everything is an object , an object has a type , it can can have some data and some methods . An iterable is an object which type defines a way to loop over its data by the use of an iterator which is an object that enable us to traverse the data of an iterable .
we can get an iterator from an iterable object in python through the use of the iter method .
if an object is an instance of a class
-
- we can implement in this class the __iter__ method . The __iter__ method must return an instance of a class that has the __next__ method. The __next__method returns the next data or raises a StopIteration exception when there is no more data to get . The iterator is gotten from this object by using iter(object)
- we can implement the __getitem__ method . __getitem__ returns data based on a provided index , the index will start from zero . __getitem__ raises a IndexError , when there is no more data. The iterator is gotten by using iter(obj)
if the object is a function or is a callable .
-
-
- A callable is an instance of a class that implements the __callable__ method , so a callable is an instance of a class that can be called like a regular function , a regular function is callable .
- we can get an iterator from this object by using the
iter(callable, sentinel) -> iteratormethod . This method takes the callable , and call it without providing any value , until the value returned by this callable is equal to the sentinel . A sentinel is just a regular value to stop the iteration .When the returned value is equal to the sentinel , the iterator will raise a StopIteration , to stop the iteration over this iterable . The iterable in this case is the function that we have provided , the iterator is created by using iter which specify when to stop the iteration through the sentinel .
-
Object is instance of a class
implement the __iter__ and __next__ methods
If we want to create an iterable from an object which is an instance of a class , we must define the __iter__ method . The __iter__ method is called by the iter function , and it must return an iterator . An iterator is simply an object which implements the __next__ method .
class Loop:
def __init__(self, maxLoop=5):
self.maxLoop = maxLoop
def __iter__(self):
self.loop = 0
# reset the iterator when __iter__
# is called
return self
def __next__(self):
if self.loop >= self.maxLoop:
raise StopIteration
else:
self.loop += 1
return self.loop
Let us say we want to create the loop object , that iterate over some numbers. The loop object is an instance of the Loop class which as such is an iterable .
The Loop class , defines the __iter__ function which creates a loop counter which starts at zero , and return the instance of this class itself . The iter function will call the __iter__ function in order to get an iterator . This __iter__function returns the class instance itself , as such on this class we have defined the __next__ method which returns the next data , in this case the data from 1 till 5 .
In order to get the data from the loop object , we can either use it with the next method.
>>> loopIterable = Loop() >>> next(loopIterable) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 10, in __next__ AttributeError: 'Loop' object has no attribute 'loop' # to use an class that has an iterator , # with next method , we must first # create the iterator , or else this will # throw an error >>> iterator_loop = iter(loopIterable) # create an iterator from loopIterable # we can now use next with either iterator_loop # or with loopIterable >>> next(iterator_loop) 1 >>> next(loopIterable) 2 >>> next(iterator_loop) 3 >>> next(loopIterable) 4 >>> next(loopIterable) 5 >>> next(loopIterable) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 11, in __next__ StopIteration # once we have iterated through all the elements # the next function will raise a StopIteration # if we want to reuse the next function we must # recreate an iterator >>> iter(loopIterable) <__main__.Loop object at 0x104e2b050> >>> next(loopIterable) 1 >>> next(iterator_loop) 2
or we can use it with a for loop or with a list .. this will automatically call the iter function in order to get an iterator from the iterable .
>>> loopIterable = Loop()
>>> iterator_loop = iter(loopIterable)
>>> next(loopIterable)
1
>>> list(loopIterable)
[1, 2, 3, 4, 5]
# this will automatically call iter(loopIterable)
# as such we restart the looping
>>> next(loopIterable)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in __next__
StopIteration
# there are no more items in the iterable
# as such next raises an error
for data in loopIterable :
print(data , end='')
if data == 2:
print()
break
# output : 12
# the for loop will call the iter function
# to create an iterator from the iterable
# we print the first two elements in the
# iterator and break
>>> next(loopIterable)
3
# we can use next to continue iterating
# through the iterator
>>> list(loopIterable)
[1, 2, 3, 4, 5]
# if we call list with the iterable
# it will call the iter function
# to get an iterator , and we start
# iterating from the start of the iterable
implement the __getitem__ methods
The iter function can also create an iterator from an object that has the __getitem__ method .
The __getitem__ methods simply returns an element from an object based on a numerical index , if the numerical index does not exist , it raises an IndexError .
The iter function will create an iterator from an instance of a class that has a __getitem__ method . it will pass the indexes starting 0 till an IndexError is raised , in this case it will raise a StopIteration error .
class Paper:
def __getitem__(self, key):
'''
a key can be between 0 and 2
or
a key can be larger than 7
'''
if 0 <= key < 2 or key > 7:
return key
raise IndexError
>>> paper = Paper()
>>> list(paper)
[0, 1]
# the iter function will create an iterator
# for data 0 , 1 only
>>> paper[8]
8
# paper have data which exists at index
# larger than 7 , but the iter function
# will stop at the first IndexError that
# is raised
>>> next(paper)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Paper' object is not an iterator
# we cannot use the paper instance
# with next ,
>>> iter_paper = iter(paper)
# create an iterator from paper
>>> next(iter_paper)
0
>>> next(paper)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Paper' object is not an iterator
# we cannot use the paper instance
# with next ,
>>> next(iter_paper)
1
>>> next(iter_paper)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
# when there are no more elements
# a StopIteration exception is raised
object is a Function
We can use the iter function with a function and a sentinel value in order to create an iterator . The sentinel value is used to stop the iteration .
import random
# import the random module
def choice():
# return a random choice from the list "yes" , "no" ,"perhaps"
return random.choice(["yes", "no", "perhaps"])
choice_iter = iter(choice , "no")
# create an iterator , which will stop
# when the value returned by the choice
# function is no
>>> list(choice_iter)
['yes', 'perhaps', 'yes', 'perhaps', 'yes', 'yes', 'yes']
list(choice_iter) #[]
>>> list(choice_iter)
[]
# once the iterator is used , we cannot reuse it
# we must create a new one
>>> choice_iter = iter(choice , "no")
>>> list(choice_iter)
['yes']
object is a callable
a callable is an object which is an instance from a class that implements the __call__ method. it can be used as a regular function .
we can use the iter function with a callable , and a sentinel value , the sentinel value is used to stop the iteration .
import random
class RandomWords:
''' return some random words
'''
def __init__(self):
self.words = ["statement", "has", "no", "effect", "stop"]
def __call__(self):
return random.choice(self.words)
randomWords = RandomWords()
>>> randomWords = RandomWords()
>>> iter_random_words = iter(randomWords , "stop")
>>> list(iter_random_words)
['no']
# an iterator can only be used once
>>> list(iter_random_words)
[]
>>> iter_random_words = iter(randomWords , "stop")
>>> list(iter_random_words)
['no', 'no']
Complex iterables & Real world usage
iterables can be used to iterate through data , or to generate data without statically storing it . let us give an example . Let us say that we want to generate data for series , so we decided to create a class to do that , this class can generate data for two series , the harmonic series

and the sum series .

This class name is the Series class and it has two inner classes , the HARMONIC and the SUM class , which are used to return an iterator based on if we want to calculate the sum of harmonic or a sum series .
class Series:
'''
return an iterator , that return the sum
of a harmonic and a sum series
'''
SERIES = ['SUM', 'HARMONIC']
# the series that we can calculate their sum
_SUM = "SUM"
_HARMONIC = "HARMONIC"
# Name of the series to be set by the user
def __init__(self, series='SUM'):
'''default series to return , its iterator
is the SUM Series'''
self.series = series
def __iter__(self):
# return a SUM or HARMONIC instance
return getattr(self, self.series)()
@property
# series property to set and get the series
def series(self):
return self.__serie
@series.setter
def series(self, series):
if series not in Series.SERIES:
# can only get the sum of series defined in SERIES
raise ValueError("series must be in SERIES")
self.__serie = series
class SUM:
'''
Iterator to return the sum of sum series
'''
def __init__(self):
self.n = -1
def __iter__(self):
# not necessary if we don't want SUM to be iterable
return self
def __next__(self):
# infinite series no StopIteration
self.n += 1
return self.n * (self.n + 1) / 2
class HARMONIC:
'''
Iterator to return the sum of
HARMONIC series
'''
def __init__(self):
self.currentSum = 0
self.n = 0
def __iter__(self):
# not necessary if we don't want SUM to be iterable
return self
def __next__(self):
# infinite series no StopIteration
self.n += 1
self.currentSum += 1 / self.n
return self.currentSum
>>> serie = Series()
>>> sum_iter = iter(serie)
# default iterator is SUM series iterator
>>> serie.serie = serie._HARMONIC
# set serie to harmonic
>>> harm_iter = iter(serie)
# create a HARMONIC series iterator
for _r in range(10):
print(f'SUM({_r}) : {next(sum_iter)} ')
print(f'HARMONIC({_r}) : {next(harm_iter)} ')
SUM(0) : 210.0
HARMONIC(0) : 210.0
SUM(1) : 231.0
HARMONIC(1) : 231.0
SUM(2) : 253.0
HARMONIC(2) : 253.0
SUM(3) : 276.0
HARMONIC(3) : 276.0
SUM(4) : 300.0
HARMONIC(4) : 300.0
SUM(5) : 325.0
HARMONIC(5) : 325.0
SUM(6) : 351.0
HARMONIC(6) : 351.0
SUM(7) : 378.0
HARMONIC(7) : 378.0
SUM(8) : 406.0
HARMONIC(8) : 406.0
SUM(9) : 435.0
HARMONIC(9) : 435.0
Iterator vs generator object
an iterator is created by using the iter function , while a generator object is created by either a generator function or a generator expression . A generator object can act like an iterator , and can be used wherever an iterator can be used .
