Python Comprehensions
- List Comprehensions
- Using the if keyword inside a Comprehension
- Set Comprehensions
- Dictionary Comprehensions
- Comprehending Comprehensions with more than 1 for loop
- Generator Expressions
- Creating a Tuple from Comprehensions
- A word on usability of Comprehensions
List Comprehensions§
Comprehensions are an excellent Python feature to create lists, dictionaries & sets in an alternative way. For example, to create a list of numbers 0 to 9, you would normally write the following code:
>>> listOfNumbers = [] >>> for number in range(0, 10): listOfNumbers.append(number) >>> listOfNumbers # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
To create the same list, use the expression num for num in range(0, 10) in pair of square brackets.
>>> listOfNumbers = [ number for number in range(0, 10) ] # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Here are a couple of more examples.
>>> listOfSquares = [ x * x for x in range(0, 10) ] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # Using a list comprehension to emulate the working of constructor builtin map class. # The constructor of builtin map class takes two arguments: a function & an iterable each member of which is passed to the aforementioned function. The output of this constructor is an iterator. We can understand this with the help of list comprehensions. >>> listOfTags = ['a', 'strong', 'p', 'em'] >>> def createTag(tagName): return "<{}></{}>".format(tagName, tagName) >>> tags = map(createTag, listOfTags) >>> for tag in tags: print(tag) <a></a> <strong></strong> <p></p> <em></em> >>> listOfTags = ['a', 'strong', 'p', 'em'] >>> def createTag(tagName): return "<{}></{}>".format(tagName, tagName) >>> [ createTag(tagName) for tagName in listOfTags] ['<a></a>', '<strong></strong>', '<p></p>', '<em></em>']
Essentially, you are using an expression to construct the list rather than the conventional paradigm. It is a matter of discussion if the expression is readable or not.
Using the if keyword inside a Comprehension§
You can include an if statement to fine-tune the elements that make up the eventual list.
>>> [ number for number in range(0, 10) if number % 2 == 0 ] # [0, 2, 4, 6, 8] >>> [ number for number in range(0, 10) if not number % 2 ] # [0, 2, 4, 6, 8] # Above example creates a list of those numbers in 0 to 9 which do NOT leave a True value as remainder upon division by 2 (0 is considered False). >>> [ number for number in range(0, 10) if number % 2 ] # [1, 3, 5, 7, 9]
Set Comprehensions§
Set comprehensions work in a similar way to list comprehensions. The expression is surrounded by curly braces to create a set.
>>> setOfSquares = { number * number for number in range(0, 10) } # {0, 1, 64, 4, 36, 9, 16, 49, 81, 25}
Dictionary Comprehensions§
In order to create dictionary comprehensions, you need to specify the expression in terms of two variables i.e. key & value. Dictionary comprehensions are also surrounded by curly braces, like sets. The following expression creates a dictionary of numbers in 0 to 9 along with their squares.
>>> dictOfSquares = { number:number*number for number in range(0, 10) } # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
Comprehending Comprehensions with more than 1 for loop§
There will be occasions when you wish to create an expression with nested for loops. For example:
>>> linesFromAfile = ['line 1', 'line 2', 'line 3'] >>> for line in linesFromAfile: for character in line: print(character) l i n e 1 l i n e 2 l i n e 3
To create the equivalent expression, write the for loops in the same order as they appear in the nested loops, with the entity that you want to make an iterable of, placed at the beginning of the expression.
>>> [ character for line in linesFromAfile for character in line ] ['l', 'i', 'n', 'e', ' ', '1', 'l', 'i', 'n', 'e', ' ', '2', 'l', 'i', 'n', 'e', ' ', '3']
Generator Expressions§
In case you have wondered, why are there no tuple comprehensions? For starters, lists, sets & dicts are mutable i.e. once they are declared, you can add/remove elements to/from them. Comprehensions populate them one by one. Tuples, are immutable, i.e. once you declare a tuple, you cannot add/remove elements to/from them.
When you try to make a tuple comprehension by surrounding an expression with round brackets, you end up making a Generator instead.
>>> ( number for number in range(0, 10) ) <generator object <genexpr> at 0x0370ECD8>
A Generator is an object in Python which returns a sequence of elements, one at a time. The regular way to create a generator is by defining a function with the yield keyword. To know more about Generators, visit this link.
>>> generatorObjectOne = (x for x in range(5)) >>> generatorObjectOne <generator object <genexpr> at 0x03031738> >>> for number in generatorObjectOne: print(number) 0 1 2 3 4
So, these expressions work just like comprehensions of other sequences, the difference is in the way the elements are returned. For example, a list comprehension will store the entire list in memory at a single instant and return the entire list at the same time, and the equivalent generator will return one number at a time and hence, does not store the entire sequence in memory. The generator expression is assigned to a variable which stores the generator object, which can be used accordingly. Here is a demonstration:
>>> listOf10Numbers = [number for number in range(1, 11)] >>> listOf10Numbers [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> generatorOf10Numbers = ( number for number in range(1, 11) ) >>> generatorOf10Numbers.__next__() 1 >>> for number in generatorOf10Numbers: print(number) 2 3 4 5 6 7 8 9 10
The above example only has 10 numbers to output. If there were 100, or 1000 numbers to output, it is clear that using a generator will be wiser for the memory resources.
Here's another interesting example of generator expressions:
>>> def computePythogorasTripletsTill(upperBound): '''This function returns a generator of Pythogoras triplets lying between 0 and the number provided.''' return ( (x, y, z) for z in range(upperBound) for y in range(1, z) for x in range(1, y) if x*x + y*y == z*z ) >>> for triplet in computePythogorasTripletsTill(20): print(triplet) (3, 4, 5) (6, 8, 10) (5, 12, 13) (9, 12, 15) (8, 15, 17)
The above generator expression is as good as:
>>> for z in range(20): for y in range(1, z): for x in range(1, y): if x*x + y*y == z*z: print( (x, y, z ) ) (3, 4, 5) (6, 8, 10) (5, 12, 13) (9, 12, 15) (8, 15, 17)
So, the main purpose of Generators is to produce iterators which are not stored in the memory.
In fact, list comprehensions are created from generators of the expression i.e. [ number for number in range(0, 10) ] first creates a generator object from which a list is created. To know more about Generators, visit this link.
Creating a Tuple from Comprehensions§
In order to create a tuple from a comprehension, you can use the constructor of builtin tuple class.
>>> tuple( [ number for number in range(0, 10) ] ) (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) >>> tuple( (x for x in range(5)) ) (0, 1, 2, 3, 4)
A word on usability of Comprehensions§
The Zen of Python (>>> import this) states that
"Explicit is better than implicit."
Use comprehensions not just because they make your code compact. Rather, use them if they are not hampering the readability of your script. While it is tempting to design compound comprehensions such as ( (x, y, z) for z in range(upperBound) for y in range(1, z) for x in range(1, y) if x*x + y*y == z*z ), use them at your own discretion. It takes skill to design such complex comprehensions, but they should not come in the way of other people when they are trying to make out what your program is doing.
That's it for this one. I hope you are now familiar with Comprehensions in Python.