Python Arena
Step 1 of 3

Strings and your own objects

A comprehension goes through anything a for loop can, and a string is a series of characters. So a comprehension over a string gets one character at a time. Here it picks the digits out of a code:

In the Python interpreter
>>> code = "a1b2c3"
>>> [c for c in code if c in "0123456789"]
['1', '2', '3']
>>> [int(c) for c in code if c in "0123456789"]
[1, 2, 3]
>>> sum([int(c) for c in code if c in "0123456789"])
6

Whatever it goes through, a comprehension in square brackets makes a list. When you want a string instead, hand the list of strings to the string method join from part 4. It makes one string of the pieces, with the string you call it on between each two of them:

In the Python interpreter
>>> digits = [c for c in "a1b2c3" if c in "0123456789"]
>>> "".join(digits)
'123'
>>> "+".join(digits)
'1+2+3'

The pieces must be strings, though. The digits as numbers stop join:

numbers = [int(c) for c in "a1b2c3" if c in "0123456789"]
print("+".join(numbers))
Python stops with
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    print("+".join(numbers))
          ~~~~~~~~^^^^^^^^^
TypeError: sequence item 0: expected str instance, int found

Item 0, the first one, is an int, not a str. Keep the pieces as strings for join, or turn each number back into a string with a comprehension: "+".join([str(n) for n in numbers]) gives 1+2+3.

Word by word

To work on whole words instead of characters, split the string first. split() gives a list of the words, a comprehension changes each of them, and join puts them back together with a space between each two. The string method capitalize gives a word with its first letter in capitals and the rest in small letters, so it also fixes the all-capitals LORD:

title = "the LORD of the rings"
print(" ".join([word.capitalize() for word in title.split()]))
Output
The Lord Of The Rings

Back at the level of characters, an if and else in front of the for, as in the previous module, changes some characters and keeps the rest. This makes a file name out of a title:

title = "My Summer Photos"
file_name = "".join(["_" if c == " " else c.lower() for c in title])
print(file_name + ".zip")
Output
my_summer_photos.zip

Objects of your own classes

A list of objects works like any other list. The expression and the condition can use the objects' attributes and methods. The expression decides what goes into the new list: below, the first comprehension collects names, and the second one, with just recipe in front of the for, keeps the Recipe objects themselves:

class Recipe:
    def __init__(self, name, minutes):
        self.name = name
        self.minutes = minutes


recipes = [Recipe("Pancakes", 20), Recipe("Toast", 5), Recipe("Lasagna", 90), Recipe("Salad", 10)]
print([recipe.name for recipe in recipes if recipe.minutes <= 15])

quick = [recipe for recipe in recipes if recipe.minutes <= 15]
for recipe in quick:
    recipe.name += " (quick)"
print([recipe.name for recipe in recipes])
Output
['Toast', 'Salad']
['Pancakes', 'Toast (quick)', 'Lasagna', 'Salad (quick)']

quick is a new list, but its items aren't new objects. As in part 9, a list holds references, so quick and recipes refer to the same two objects, Toast and Salad. The loop changes them through quick, and the change shows when the last line goes through recipes.

Your own iterable classes

The for inside a comprehension is the same for as in a loop, so it accepts the same things. That includes objects of a class with the methods __iter__ and __next__, the iterable classes of part 10. A Countdown hands out the numbers from its start down to 1, and the comprehension gets them one by one, just like items of a list:

class Countdown:
    def __init__(self, start):
        self.start = start

    def __iter__(self):
        self.current = self.start
        return self

    def __next__(self):
        if self.current == 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1


print([n * 10 for n in Countdown(4)])
Output
[40, 30, 20, 10]

Your task

Challenge 1 of 3

An acronym is made of the first letters of the words, in capitals. Fill in the blank so the program prints:

PNG

Challenge 2

Write a function double_letters(word) that returns a new string in which every character of word is doubled. Use a list comprehension and join.

For example, double_letters("hello") returns "hheelllloo", so this call:

print(double_letters("hello"))

prints:

hheelllloo

Challenge 3

The program has a class Song, with a title and a length in seconds. Write a function short_songs(songs, limit) that returns a list of the titles of the songs that are shorter than limit seconds, in their original order. Use a list comprehension.

For example, this program:

songs = [Song("Intro", 95), Song("Epic", 610), Song("Hit", 200)]
print(short_songs(songs, 300))

prints:

['Intro', 'Hit']