Iterable Objects#
An iterable is a Python object capable of returning its members one at a time within a
forloop.
We have already seen iterable objects:
for i in range(0, 3):
print( i )
0
1
2
List are already iterable objects:
cityList = ["Nijmegen", "Utrecht", "Amsterdam"]
for city in cityList:
print(city)
Nijmegen
Utrecht
Amsterdam
Zipping#
Iterable objects can be zippend to get a new iterator.
cityNames = ["Nijmegen", "Utrecht", "Amsterdam"]
cityUniversity = ["RU", "UU", "UvA"]
cityIterator = zip(cityNames, cityUniversity)
print(cityIterator)
<zip object at 0x7fbb9a688580>
for var in cityIterator:
print(var)
('Nijmegen', 'RU')
('Utrecht', 'UU')
('Amsterdam', 'UvA')
Note
The two lists do not necessarily have to be of the same length!
cityNames = ["Nijmegen", "Utrecht", "Amsterdam"]
cityUniversity = ["RU", "UU" ]
cityIterator = zip(cityNames, cityUniversity)
for var in cityIterator:
print(var)
('Nijmegen', 'RU')
('Utrecht', 'UU')
Mapping#
Interable objects can be mapped to a new iterable object by applying a given function:
a = [-1, 2j, -1+2j]
b = map( abs, a )
print( b )
<map object at 0x7fbb9a68a180>
for c in b:
print(c)
1
2.0
2.23606797749979
Use a lambda function:
a = [1, 2, 3, 4, 5]
b = map( lambda x: x**2, a )
for c in b:
print(c)
1
4
9
16
25
Iterators#
We can create so-called iterators from iterabale objects using iter().
See https://anandology.com/python-practice-book/iterators.html for more details.
cityList = ["Nijmegen", "Utrecht", "Amsterdam"]
myIterator = iter(cityList)
print( type(myIterator) )
<class 'list_iterator'>
An ordered list of elements can than be created by calling next():
cityList = ["Nijmegen", "Utrecht", "Amsterdam"]
myIterator = iter(cityList)
print( next(myIterator) )
print( next(myIterator) )
print( next(myIterator) )
print( next(myIterator) )
Nijmegen
Utrecht
Amsterdam
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In[10], line 7
5 print( next(myIterator) )
6 print( next(myIterator) )
----> 7 print( next(myIterator) )
StopIteration:
Create an iterator from range():
myIterator = iter( range(5) )
print( next(myIterator) )
print( next(myIterator) )
0
1
Note
Iteators save their state!
for i in myIterator:
print( i )
2
3
4
Let’s implement the range iterator from scratch. Therefore we define a new class and use a few magic methods:
class myRange:
def __init__(self, n):
self.i = 0 # state of the iterator
self.n = n # length / max. value
def __iter__(self): # makes an object iterable
return self
def __next__(self): # returns the next value
# in the list
if self.i < self.n:
i = self.i
self.i += 1
return i
else:
raise StopIteration() # and raises StopIteration
# when there are no more
# elements in the list
myIterator = myRange(5)
print( next(myIterator) )
print( next(myIterator) )
print( next(myIterator) )
print( next(myIterator) )
print( next(myIterator) )
print( next(myIterator) )
0
1
2
3
4
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In[14], line 28
26 print( next(myIterator) )
27 print( next(myIterator) )
---> 28 print( next(myIterator) )
Cell In[14], line 17, in myRange.__next__(self)
15 return i
16 else:
---> 17 raise StopIteration()
StopIteration:
Get elements in a for loop:
for i in myRange(5):
print( i )
0
1
2
3
4
Or directly as a list:
print( list(myRange(5)) )
[0, 1, 2, 3, 4]
Hint
What’s the benefit of using for i in myRange(5) instead of for i in [0,1,2,3,4]?
Generators#
Generators simplify the creation of iterators. A generator is a function that produces a sequence of results instead of a single value using the
yieldstatement.
See also: https://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/Generators_and_Comprehensions.html
def mySimpleGenerator():
yield 1
yield 10
yield 100
for x in mySimpleGenerator():
print(x)
1
10
100
Create a slightly more involved generator:
def myRange(n):
i = 0
while(i < n):
yield i
i += 1
for x in myRange(5):
print(x)
0
1
2
3
4
Generator Expressions#
Generator Expressions are generator version of list comprehensions. They look like list comprehensions, but return a generator back instead of a list.
myGenerator = (x*x for x in range(5))
print( myGenerator )
<generator object <genexpr> at 0x1489fa2bf8b0>
myGenerator = (x*x for x in range(5))
print( list( myGenerator ) )
[0, 1, 4, 9, 16]
myGenerator = (x*x for x in range(5))
for i in myGenerator:
print( i )
0
1
4
9
16
Itertools & for-loop “Tricks”#
Unpacking directly in the for statement:
cityNames = ["Nijmegen", "Utrecht", "Amsterdam"]
cityUniversity = ["RU", "UU", "UvA"]
cityIterator = zip(cityNames, cityUniversity, cityUniversity)
for c in cityIterator:
print(c)
('Nijmegen', 'RU', 'RU')
('Utrecht', 'UU', 'UU')
('Amsterdam', 'UvA', 'UvA')
cityNames = ["Nijmegen", "Utrecht", "Amsterdam"]
cityUniversity = ["RU", "UU", "UvA"]
cityIterator = zip(cityNames, cityUniversity, cityUniversity)
for city, university, var3 in cityIterator:
print(city, ":", university)
Nijmegen : RU
Utrecht : UU
Amsterdam : UvA
a, b, c = 1, 2, 3
print(a,b,c)
1 2 3
Numerating#
Numerate an iteration via enumerate():
for i, city in enumerate(cityList):
print( i, ":", city)
0 : Nijmegen
1 : Utrecht
2 : Amsterdam
Iterools is a powerful module which implements a variety of helpful iterator functions. For a full description see https://docs.python.org/3.3/library/itertools.html.
Nested iterations example:
for x in range(0, 2):
for y in range(0, 3):
print("x, y =", x, y)
x, y = 0 0
x, y = 0 1
x, y = 0 2
x, y = 1 0
x, y = 1 1
x, y = 1 2
Use itertools to get rid of the nested loops:
import itertools
for x, y in itertools.product( range(0, 2), range(0, 3) ):
print("x, y =", x, y)
x, y = 0 0
x, y = 0 1
x, y = 0 2
x, y = 1 0
x, y = 1 1
x, y = 1 2
for x, y, z in itertools.product( range(0, 2), repeat=3 ):
print("x, y, z =", x, y, z)
x, y, z = 0 0 0
x, y, z = 0 0 1
x, y, z = 0 1 0
x, y, z = 0 1 1
x, y, z = 1 0 0
x, y, z = 1 0 1
x, y, z = 1 1 0
x, y, z = 1 1 1