Warm-up · Activity 1 of 7
Warm-up from module B4: a list comprehension with a condition. What does this print?
print([n * 2 for n in range(4) if n % 2 == 0])// I3.3 · ~26 min · Intermediate
After this lesson you can feed generator expressions straight into sum, max, join, any and all, predict where any and all stop, and avoid reusing a used-up generator.
You will be able to
Warm-up · Activity 1 of 7
print([n * 2 for n in range(4) if n % 2 == 0])Predict · Activity 2 of 7
squares = (n * n for n in range(4))
print(sum(squares), sum(squares))Practice · Activity 3 of 7
words = ["hi", "there"]
print(sum(len(w) ____ w in words))Practice · Activity 4 of 7
nums = [3, 8, 12]
print(any(n > 10 for n in nums), all(n > 2 for n in nums))Practice · Activity 5 of 7
Brain teaser · Activity 6 of 7
def check(n):
print("checking", n, end="; ")
return n > 1
print(any(check(n) for n in [0, 2, 5]))Apply · Activity 7 of 7
Check your work against this list
Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.
Worked example
Each log line holds a method, a path, a status code and the bytes sent. Every statistic is one generator expression passed straight to sum, any, all, max or join, so no list of numbers is ever built. The program also sums the same generator twice, to show that the second pass finds nothing.
main.py
log = [
"GET /index.html 200 512",
"GET /missing 404 0",
"POST /login 200 128",
"GET /about.html 200 2048",
"GET /admin 403 0",
]
sizes = (int(line.split()[3]) for line in log)
print("bytes sent:", sum(sizes))
print("bytes again:", sum(sizes))
print("any errors:", any(int(line.split()[2]) >= 400 for line in log))
print("all GET:", all(line.startswith("GET") for line in log))
print("largest:", max(int(line.split()[3]) for line in log))
empty = ", ".join(line.split()[1] for line in log if line.endswith(" 0"))
print("empty replies:", empty)
Run it with
python main.pyOutput
bytes sent: 2688
bytes again: 0
any errors: True
all GET: False
largest: 2048
empty replies: /missing, /adminTab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.
Exercise 1 of 2
Finish three functions, each with one generator expression. total_length(words) returns the number of letters in all words together. has_long_word(words, limit) returns True if any word is longer than limit. all_lowercase(words) returns True if every word is all lowercase, which str.islower() tells you; it is True for an empty list.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.
Each body is a single return with sum, any or all around a generator expression.
sum(len(w) for w in words) adds the lengths; any(len(w) > limit for w in words) checks for one long word.
all(w.islower() for w in words) is already True for an empty list, so no extra if is needed.
One way to solve it. Yours can look different and still pass the checks.
def total_length(words: list[str]) -> int:
"""Return the number of letters in all words together."""
return sum(len(w) for w in words)
def has_long_word(words: list[str], limit: int) -> bool:
"""Return True if any word is longer than limit."""
return any(len(w) > limit for w in words)
def all_lowercase(words: list[str]) -> bool:
"""Return True if every word is all lowercase."""
return all(w.islower() for w in words)
if __name__ == "__main__":
words = ["tea", "Coffee", "water"]
print(total_length(words), has_long_word(words, 5), all_lowercase(words))
Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.py
def total_length(words: list[str]) -> int:
"""Return the number of letters in all words together."""
return 0
def has_long_word(words: list[str], limit: int) -> bool:
"""Return True if any word is longer than limit."""
return False
def all_lowercase(words: list[str]) -> bool:
"""Return True if every word is all lowercase."""
return False
if __name__ == "__main__":
words = ["tea", "Coffee", "water"]
print(total_length(words), has_long_word(words, 5), all_lowercase(words))
test_main.py
from main import all_lowercase, has_long_word, total_length
def test_total_length():
"""tea, Coffee and water have 14 letters together"""
got = total_length(["tea", "Coffee", "water"])
assert got == 14, f"total_length(['tea', 'Coffee', 'water']) returned {got!r}, expected 14"
def test_has_long_word():
"""Coffee is longer than 5 letters, but no word is longer than 6"""
words = ["tea", "Coffee", "water"]
got = has_long_word(words, 5), has_long_word(words, 6)
assert got == (True, False), f"has_long_word with limits 5 and 6 returned {got!r}, expected (True, False)"
def test_all_lowercase():
"""Coffee is not lowercase; tea and water are"""
got = all_lowercase(["tea", "Coffee"]), all_lowercase(["tea", "water"])
assert got == (False, True), f"all_lowercase returned {got!r}, expected (False, True)"
def test_empty_list():
"""For no words: 0 letters, no long word, and all lowercase"""
got = total_length([]), has_long_word([], 0), all_lowercase([])
assert got == (0, False, True), f"for [] the functions returned {got!r}, expected (0, False, True)"
On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.
Run the program:
python main.pyRun the checks (needs learnrun.py in the same folder):
python learnrun.py testDownload learnrun.pyExercise 2 of 2
summary(numbers) returns the smallest value, the largest value and the mean. The starter works for a list but fails when numbers is a generator, because min uses it up. Make it work for any iterable, with a single pass over numbers. For no numbers at all, it raises ValueError.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.
A generator can be read only once, but a list can be read as often as you like.
Start with values = list(numbers). That is the single pass; after it, use values everywhere.
if not values: raise ValueError("no numbers"). Then return min(values), max(values), sum(values) / len(values).
One way to solve it. Yours can look different and still pass the checks.
from collections.abc import Iterable
def summary(numbers: Iterable[float]) -> tuple[float, float, float]:
"""Return the smallest, the largest and the mean of numbers."""
values = list(numbers)
if not values:
raise ValueError("no numbers")
return min(values), max(values), sum(values) / len(values)
if __name__ == "__main__":
print(summary(n * 1.5 for n in [2, 4, 6]))
Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.py
from collections.abc import Iterable
def summary(numbers: Iterable[float]) -> tuple[float, float, float]:
"""Return the smallest, the largest and the mean of numbers."""
smallest = min(numbers)
largest = max(numbers)
mean = sum(numbers) / sum(1 for _ in numbers)
return smallest, largest, mean
if __name__ == "__main__":
print(summary(n * 1.5 for n in [2, 4, 6]))
test_main.py
from main import summary
def test_list():
"""A list [3, 1, 2] gives (1, 3, 2.0)"""
got = summary([3, 1, 2])
assert got == (1, 3, 2.0), f"summary([3, 1, 2]) returned {got!r}, expected (1, 3, 2.0)"
def test_generator():
"""A generator expression works too"""
try:
got = summary(n * 1.5 for n in [2, 4, 6])
except ValueError as error:
raise AssertionError(f"summary(<generator>) raised ValueError: {error}. The generator was used up by an earlier pass") from None
assert got == (3.0, 9.0, 6.0), f"summary(n * 1.5 for n in [2, 4, 6]) returned {got!r}, expected (3.0, 9.0, 6.0)"
def test_single_value():
"""One value is the smallest, the largest and the mean"""
got = summary(iter([7]))
assert got == (7, 7, 7.0), f"summary(iter([7])) returned {got!r}, expected (7, 7, 7.0)"
def test_empty():
"""No numbers raise ValueError"""
try:
got = summary([])
except ValueError:
return
raise AssertionError(f"summary([]) returned {got!r} instead of raising ValueError")
On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.
Run the program:
python main.pyRun the checks (needs learnrun.py in the same folder):
python learnrun.py testDownload learnrun.pyprices = [4.5, 12.0, 3.25]
doubled = (p * 2 for p in prices)
total = sum(doubled)
biggest = max(doubled)
print(total, biggest)
What Python prints
ValueError: max() iterable argument is emptyWhy, and the fix
sum used up the generator, so max got an empty iterator. When you need the values more than once, build a list with square brackets: doubled = [p * 2 for p in prices]. Or write a fresh generator expression for each use.
nums = [1, 2, 3]
print(sum(n * n for n in nums, 10))
What Python prints
SyntaxError: Generator expression must be parenthesizedWhy, and the fix
The brackets of a generator expression may only be left out when it is the only argument of the call. With a second argument it needs its own: sum((n * n for n in nums), 10). Here sum starts counting at 10 and gives 24.
ids = [7, 12, 30]
print(", ".join(n for n in ids))
What Python prints
TypeError: sequence item 0: expected str instance, int foundWhy, and the fix
join glues strings only, and the generator hands it ints. Convert inside the expression: ", ".join(str(n) for n in ids) prints 7, 12, 30.
Python in the browser: Pyodide 314.0.7, MPL-2.0. Licence and source
5 questions, no hints. Score 80% or more to complete the lesson.
Finish every activity above to unlock the exit ticket.