Iterable Objects#


An iterable is a Python object capable of returning its members one at a time within a for loop.

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 yield statement.

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