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:
>>> 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"])
6Whatever 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:
>>> 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))
Traceback (most recent call last):
File "main.py", line 2, in <module>
print("+".join(numbers))
~~~~~~~~^^^^^^^^^
TypeError: sequence item 0: expected str instance, int foundItem 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()]))
The Lord Of The RingsBack 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")
my_summer_photos.zipObjects 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])
['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)])
[40, 30, 20, 10]