Python: Iterators, Generators and Comprehensions

Iterators

An iterator is an object that implements the next protocol and raises StopIteration when exhausted.

A list is an iterable object but not an iterator.

In [60]:
xs = [1,2,3]
In [68]:
try:
    next(xs)
except Exception as e:
    print(e)
'list' object is not an iterator

We can make an iterator out of an iterable object by calling the iter function.

In [77]:
xsi = iter(xs)
In [78]:
next(xsi)
Out[78]:
1
In [79]:
next(xsi)
Out[79]:
2
In [80]:
next(xsi)
Out[80]:
3
In [89]:
try:
    next(xsi)
except Exception as e:
    print(type(e))
<class 'StopIteration'>

For loops

The for loop automatically constructs an iterator from an interator object, then repeatedly calls next until a StopIteration is raised.

In [90]:
for x in xs:
    print(x)
1
2
3

Looping over collections

Tuples and list preserve order

In [19]:
for x in ('a', 'b', 'c'):
    print(x)
a
b
c
In [21]:
for x in ['a', 'b', 'c']:
    print(x)
a
b
c

Sets do not preserve order

In [22]:
for x in set(['a', 'b', 'b', 'c']):
    print(x)
b
c
a

Dictionaries prserve order of entry (new)

In [11]:
d = {'c': 1, 'b': 2, 'a': 3}
for k in d:
    print(k)
c
b
a
In [12]:
for k in d.values():
    print(k)
1
2
3
In [13]:
for k, v in d.items():
    print(k, v)
c 1
b 2
a 3

Ranges

In [14]:
for i in range(3):
    print(i)
0
1
2
In [15]:
for i in range(1,4):
    print(i)
1
2
3
In [16]:
for i in range(1,6,2):
    print(i)
1
3
5

Enumerate

Traditional indexing

In [28]:
xs = ['a', 'b', 'c']
In [29]:
for i in range(len(xs)):
    print(i, xs[i])
0 a
1 b
2 c

Standard Python idiom is to use enumerate

In [25]:
for i, x in enumerate(xs):
    print(i, x)
0 a
1 b
2 c
In [26]:
for i, x in enumerate(xs, start=10):
    print(i, x)
10 a
11 b
12 c

Zip

In [32]:
list(zip(range(3), 'abc'))
Out[32]:
[(0, 'a'), (1, 'b'), (2, 'c')]
In [30]:
for i, x in zip(range(3), 'abc'):
    print(i, x)
0 a
1 b
2 c

Generators

A generator is a function that produces a sequence of results instead of a single value. As generators do not store the sequence that is generated, they are memory-efficient. They use the yield and yield from keywords to return values.

Many built in Python functions return generators to minimize use of memory - e.g. range, zip, map, filter , open - you need to use a for loop to evaluate them.

In [56]:
zip(range(10), 'abcdefghij')
Out[56]:
<zip at 0x104f778c8>
In [58]:
for i, c in zip(range(10), 'abcdefghij'):
    print(i, c)
0 a
1 b
2 c
3 d
4 e
5 f
6 g
7 h
8 i
9 j

Make sure that you have enough memory to do the conversion as a list stores all its contents in memory.

In [59]:
list(range(10))
Out[59]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Generator functions

In [36]:
def updown(n):
    """Generator that goes up to n and down again."""
    for i in range(n):
        yield i
    for i in range(n, -1, -1):
        yield i
In [38]:
for i in updown(5):
    print(i, end=', ')
0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0,
In [41]:
def updown2(n):
    """Alternative version using yield from."""
    yield from range(n)
    yield from range(n, -1, -1)
In [40]:
for i in updown2(5):
    print(i, end=', ')
0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0,
In [49]:
def fib(n):
    """Return the next Fibonacci number up to n."""
    a, b = 1, 1
    for i in range(n):
        yield a
        a, b = b, a + b
In [51]:
for i in fib(10):
    print(i, end=', ')
1, 1, 2, 3, 5, 8, 13, 21, 34, 55,

Generator expressions

Syntax sugar for simple generators

In [52]:
def odd(n):
    """Traditional generator."""
    for i in range(n):
        if i %2 == 1:
            yield i
In [54]:
for i in odd(10):
    print(i, end=', ')
1, 3, 5, 7, 9,

Using a generator expression

In [55]:
gs = (i for i in range(10) if i%2 == 1)
for i in gs:
    print(i, end=', ')
1, 3, 5, 7, 9,

Comprehensions

Syntax sugar similar to that of generator expressions can be used to create lists, sets and dictionaries.

List comprehension

In [92]:
[i for i in range(10)]
Out[92]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [91]:
[i**2 for i in range(10) if i % 2 == 1]
Out[91]:
[1, 9, 25, 49, 81]

Set comprehension

In [94]:
{i for i in updown(5)}
Out[94]:
{0, 1, 2, 3, 4, 5}
In [95]:
{i**3 for i in updown(5)}
Out[95]:
{0, 1, 8, 27, 64, 125}

Dictionary comprehension

In [97]:
subjects = ['ann', 'bob', 'charles']
ages = [23, 34, 45]
In [98]:
{subject: age for subject, age in zip(subjects, ages)}
Out[98]:
{'ann': 23, 'bob': 34, 'charles': 45}