More comprehensions
Lists are perhaps the most common target for comprehensions, but comprehensions work on any series of items, including strings. Just like in the list examples in the previous section, if a list comprehension is performed on a string, the items (i.e. the characters) in the string are plucked one by one, processed according to the expression given, and stored in a list.
name = "Peter Python"
uppercased = [character.upper() for character in name]
print(uppercased)
['P', 'E', 'T', 'E', 'R', ' ', 'P', 'Y', 'T', 'H', 'O', 'N']
The result is indeed a list, as dictated by the bracket notation around the comprehension statement. If we wanted a string instead, we could use the string method join
to parse the list into a string. Remember, the method is called on the string we want to use as the "glue" between the characters. Let's take a look at some examples:
name = "Peter"
char_list = list(name)
print(char_list)
print("".join(char_list))
print(" ".join(char_list))
print(",".join(char_list))
print(" and ".join(char_list))
['P', 'e', 't', 'e', 'r'] Peter P e t e r P,e,t,e,r P and e and t and e and r
List comprehensions and the join
method make it easy to create new strings based on other strings. We could, for example, make a string which contains only the vowels from another string:
test_string = "Hello there, this is a test!"
vowels = [character for character in test_string if character in "aeiou"]
new_string = "".join(vowels)
print(new_string)
eoeeiiae
In the example above the list comprehension and the join
method are on separate lines, but they could be combined into a single expression:
test_string = "Hello there, this is a test!"
vowel_string = "".join([character for character in test_string if character in "aeiou"])
print(vowel_string)
Many Python programmers swear by these oneliners, so it is well worth your while to learn to read them. We could even add the split
method to the mix, so that we can process entire sentences efficiently with a single statement. In the example below the first character from each word in a sentence is removed:
sentence = "Sheila keeps on selling seashells on the seashore"
sentence_no_initials = " ".join([word[1:] for word in sentence.split()])
print(sentence_no_initials)
heila eeps n elling eashells n he eashore
Let's go through this step by step:
word[1:]
extracts a substring from the second character (at index 1) onwardssentence.split()
splits the sentence into sections at the given character. In this case there is no argument given to the method, so the sentence is split at space characters by default" ".join()
combines the items in the list into a new string using a space character between the items
A more traditional iterative approach could look like this
sentence = "Sheila keeps on selling seashells on the seashore"
word_list = []
words = sentence.split()
for word in words:
word_no_initials = word[1:]
word_list.append(word_no_initials)
sentence_no_initials = " ".join(word_list)
print(sentence_no_initials)
Own classes and comprehensions
Comprehensions can be a useful tool for processing or formulating instances of your own classes, as we'll see in the following examples.
First, let's have a look at the class Country
which is a simple model for a single country, with attributes for the name and the population. In the main function below we first create some Country objects, and then use a list comprehension to select only those whose population is greater than five million.
class Country:
""" This class models a single country with population """
def __init__(self, name: str, population: int):
self.name = name
self.population = population
if __name__ == "__main__":
finland = Country("Finland", 6000000)
malta = Country("Malta", 500000)
sweden = Country("Sweden", 10000000)
iceland = Country("Iceland", 350000)
countries = [finland, malta, sweden, iceland]
bigger_countries = [country.name for country in countries if country.population > 5000000]
for country in bigger_countries:
print(country)
Finland Sweden
In the list comprehension above we selected only the name attribute from the Country objects, so the contents of the list could be printed directly. We could also create a new list of the countries themselves and access the name attribute in the for
loop. This would be useful if the same list of countries was used also later in the program, or if we needed the population attribute in the for
loop as well:
if __name__ == "__main__":
finland = Country("Finland", 6000000)
malta = Country("Malta", 500000)
sweden = Country("Sweden", 10000000)
iceland = Country("Iceland", 350000)
countries = [finland, malta, sweden, iceland]
bigger_countries = [country for country in countries if country.population > 5000000]
for country in bigger_countries:
print(country.name, country.population)
In the next example we have a class named RunningEvent
which models a single foot race event with attributes for the length and the name of the race. We will use list comprehensions to create RunningEvent
objects based on a list of race lengths.
The parameter name
has a default value in the constructor of the RunningEvent
class, whIch is why we do not need to pass the name as an argument.
class RunningEvent:
""" The class models a foot race event of a length of n metres """
def __init__(self, length: int, name: str = "no name"):
self.length = length
self.name = name
def __repr__(self):
return f"{self.length} m. ({self.name})"
if __name__ == "__main__":
lengths = [100, 200, 1500, 3000, 42195]
events = [RunningEvent(length) for length in lengths]
# Print out all events
print(events)
# Pick one from the list and give it a name
marathon = events[-1] # the last item in the list
marathon.name = "Marathon"
# Print out everything again, including the new name
print(events)
[100 m. (no name), 200 m. (no name), 1500 m. (no name), 3000 m. (no name), 42195 m. (no name)] [100 m. (no name), 200 m. (no name), 1500 m. (no name), 3000 m. (no name), 42195 m. (Marathon)]
Now, let's find out what makes a series of items "comprehendible". In the previous part we learnt how to make our own classes iterable. It is exactly this same feature which also allows for list comprehensions. If your own class is iterable, it can be used as the basis of a list comprehension statement. The following class definitions are copied directly from part 10:
class Book:
def __init__(self, name: str, author: str, page_count: int):
self.name = name
self.author = author
self.page_count = page_count
class Bookshelf:
def __init__(self):
self._books = []
def add_book(self, book: Book):
self._books.append(book)
# This is the iterator initialization method
# The iteration variable(s) should be initialized here
def __iter__(self):
self.n = 0
# the method returns a reference to the object itself as
# the iterator is implemented within the same class definition
return self
# This method returns the next item within the object
# If all items have been traversed, the StopIteration event is raised
def __next__(self):
if self.n < len(self._books):
# Select the current item from the list within the object
book = self._books[self.n]
# increase the counter (i.e. iteration variable) by one
self.n += 1
# return the current item
return book
else:
# All books have been traversed
raise StopIteration
# Test your classes
if __name__ == "__main__":
b1 = Book("The Life of Python", "Montague Python", 123)
b2 = Book("The Old Man and the C", "Ernest Hemingjavay", 204)
b3 = Book("A Good Cup of Java", "Caffee Coder", 997)
shelf = Bookshelf()
shelf.add_book(b1)
shelf.add_book(b2)
shelf.add_book(b3)
# Create a list containing the names of all books
book_names = [book.name for book in shelf]
print(book_names)
Comprehensions and dictionaries
There is nothing intrinsically "listey" about comprehensions. The result is a list because the comprehension statement is encased in square brackets, which indicate a Python list. Comprehensions work just as well with Python dictionaries if you use curly brackets instead. Remember, though, that dictionaries require key-value pairs. Both must be specified when a dictionary is created, also with comprehensions.
The basis of a comprehension can be any iterable series, be it a list, a string, a tuple, a dictionary, any of your own iterable classes, and so forth.
In the following example we use a string as the basis of a dictionary. The dictionary contains all the unique characters in the string, along with the number of times they occurred:
sentence = "hello there"
char_counts = {character : sentence.count(character) for character in sentence}
print(char_counts)
{'h': 2, 'e': 3, 'l': 2, 'o': 1, ' ': 1, 't': 1, 'r': 1}
The principle of the comprehension statement is exactly the same as with lists, but instead of a single value, the expression now consists of a key and a value. The general syntax looks like this:
{<key expression> : <value expression> for <item> in <series>}
To finish off this section, lets take a look at factorials again. This time we store the results in a dictionary. The number itself is the key, while the value is the result of the factorial from our function:
def factorial(n: int):
""" The function calculates the factorial n! for integers above zero """
k = 1
while n >= 2:
k *= n
n -= 1
return k
if __name__ == "__main__":
numbers = [-2, 3, 2, 1, 4, -10, 5, 1, 6]
factorials = {number : factorial(number) for number in numbers if number > 0}
print(factorials)
{3: 6, 2: 2, 1: 1, 4: 24, 5: 120, 6: 720}
You can check your current points from the blue blob in the bottom-right corner of the page.