Python Training - Lesson 4 - Python 'comprehensions'

Comprehensions are code patterns, or constructs, that allow collections to be built in a short, concise way, from other collections.

Advantages of using comprehensions

  • code is shorter and more expressive
  • execution of code written as comprehensions is more than 30% - 50% faster

Disadvantages

  • if you do not follow good readbility rules, such code is hard to understand

Types of comprehensions in Python 3

Python 3 comes with built-in comprehension syntax for lists, dictionaries and sets.

List comprehensions


In [43]:
# Creating a list
s = []

# Filling the list
for element in range(0,10):
    s.append(element)

print(s)


[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [44]:
# Using list comprehension
s = [element for element in range(0,10)]
print(s)


[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [45]:
# Additional conditions

# Skip odd numbers
s = [x for x in range(0,10) if x%2 == 0]
print(s)


[0, 2, 4, 6, 8]

In [46]:
# Multiple conditions
s = [x for x in range(0,10) if x%2==0 and x > 4]
print(s)


[6, 8]

In [47]:
# Nested comprehension

# Regular construction
s = []
for x in range(0,10):
    for y in range(0,5):
        s.append((x,y))
print(s)


[(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (6, 0), (6, 1), (6, 2), (6, 3), (6, 4), (7, 0), (7, 1), (7, 2), (7, 3), (7, 4), (8, 0), (8, 1), (8, 2), (8, 3), (8, 4), (9, 0), (9, 1), (9, 2), (9, 3), (9, 4)]

In [48]:
# Nested comprehension - implementation
s = [(x,y) for x in range(0,10) for y in range(0,5)]
print(s)


[(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (6, 0), (6, 1), (6, 2), (6, 3), (6, 4), (7, 0), (7, 1), (7, 2), (7, 3), (7, 4), (8, 0), (8, 1), (8, 2), (8, 3), (8, 4), (9, 0), (9, 1), (9, 2), (9, 3), (9, 4)]

In [49]:
# Visually clear formatting for more complicated situation
s = [(x,y) for x in range(0,10)
           for y in range(0,5)
           if x>3
           and y < 4]
print(s)


[(4, 0), (4, 1), (4, 2), (4, 3), (5, 0), (5, 1), (5, 2), (5, 3), (6, 0), (6, 1), (6, 2), (6, 3), (7, 0), (7, 1), (7, 2), (7, 3), (8, 0), (8, 1), (8, 2), (8, 3), (9, 0), (9, 1), (9, 2), (9, 3)]

In [50]:
# Stacking comprehensions
s = [x for x in [y for y in [z for z in range(0,3)]]]
print(s)


[0, 1, 2]

In [51]:
# You can apply all operations that work on lists, as comprehensions work on lists.
s = [x for x in range (0,3)] + [y for y in range(4,5)]
print(s)


[0, 1, 2, 4]

In [52]:
# Practical examples

# Producing cubed numbers for each item in list
l = range(0,15)
cubed = [x**3 for x in l]
print(cubed)


[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728, 2197, 2744]

Dictionary comprehensions

This patterns allow for creating dictionaries using other lists or dictionaries or to transform a dictionary.


In [53]:
# Create dictionary from two lists.

d = {a:b for a,b in zip(['a', 'b', 'c'], range(0,3))}
print(d)


{'a': 0, 'b': 1, 'c': 2}

In [54]:
# Transform dictionary.

transformed = {key: value*2 for (key,value) in d.items()}
print(transformed)

transformed_keys = {key*2: value for (key,value) in d.items()}
print(transformed_keys)


{'a': 0, 'b': 2, 'c': 4}
{'aa': 0, 'bb': 1, 'cc': 2}

In [55]:
# Conditions
d = {a:b for a,b
         in zip(['a', 'b', 'c'], range(0,3))
         if a != 'c'
         and b > 0}
print(d)


{'b': 1}

In [56]:
# Do you have a complex condition using if-else? No problem!

d = {a:(str(b) + " is even" 
        if b%2==0 
        else str(b) + " is odd")
     for a,b
     in zip(['a', 'b', 'c', 'd'], range(0,4))}
print(d)


{'a': '0 is even', 'b': '1 is odd', 'c': '2 is even', 'd': '3 is odd'}

In [57]:
# Using just one list
d = {a:a**2 for a in range(0,3)}
print(d)


{0: 0, 1: 1, 2: 4}

In [58]:
# Nested comprehensions
# Transform a nested dictionary into a different nested dictionary.

nested_dict = {'something':{'a':1}, 'something_else':{'b':2}}
squared = {k: {k2 + " " + str(v2**2) 
               for (k2, v2) 
               in v.items()} 
           for (k, v) 
           in nested_dict.items()}
print(squared)


{'something': {'a 1'}, 'something_else': {'b 4'}}

Set comprehensions

These patterns create or transform other collections into sets. A set is an unordered collection, without duplicates.

Let's say we need to create a set from a list of numbers:


In [59]:
s = [1,1,1,2,2,3,3,3,3,3,4,4,5]

normal_set = set(s)
print(normal_set)


{1, 2, 3, 4, 5}

In [60]:
# Using comprehension.

set_comprehension = {x for x in s}
print(set_comprehension)


{1, 2, 3, 4, 5}

In [61]:
# With transformation
set_comprehension = {x*2 + 1 for x in s}
print(set_comprehension)


{3, 5, 7, 9, 11}

In [62]:
# With conditions
set_comprehension = {x*2 + 1 for x in s if x>3}
print(set_comprehension)


{9, 11}

In [63]:
# With complex conditions
set_comprehension = {
    (x**3 if x < 4 
     else x**2 ) 
    for x in s}
print(set_comprehension)


{1, 8, 16, 25, 27}

Examples from real life


In [64]:
# Flatten a matrix

matrix = [range(0,5), range(5,10), range(10,15)]

flatten = [column for row in matrix for column in row]
print(flatten)


[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

In [68]:
# Removing a set of characters from a string

forbidden_chars = ["x", "z", "u"]
some_string = "Mr Garlax Underwood was a good man, but underpaid"
removed_list = [char for char in some_string if char.lower() not in forbidden_chars]
removed_as_string = "".join(removed_list)
print(removed_as_string)


Mr Garla nderwood was a good man, bt nderpaid