Warm-up · Activity 1 of 7
Warm-up from lesson B3.3: what does this print?
def f(*args, **kwargs):
print(len(args), len(kwargs))
f(1, 2, x=3)// B3.4 · ~30 min · Beginner
After this lesson you can sort with a lambda as the key, document a function with a proper docstring and annotations, and name and lay out code the way PEP 8 asks.
Warm-up · Activity 1 of 7
def f(*args, **kwargs):
print(len(args), len(kwargs))
f(1, 2, x=3)Predict · Activity 2 of 7
words = ["kiwi", "fig", "banana", "apple"]
print(sorted(words, key=lambda w: w[-1]))Practice · Activity 3 of 7
scores = [["ann", 82], ["bo", 95], ["cy", 78]]
print(sorted(scores, key=____ pair: pair[1]))Practice · Activity 4 of 7
def greet(name: str, times: int = 1) -> str:
return name * times
print(greet.__annotations__)Practice · Activity 5 of 7
Brain teaser · Activity 6 of 7
def double(n: int) -> int:
return n * 2
print(double("ab"))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
Plain sorted puts capital letters before small ones, so "Bo" comes before "ada". sort_names fixes that with a lambda key that compares lowercase names; name.lower() gives the name in small letters. With by_length=True it sorts again by length, and the stable sort keeps names of equal length in alphabetical order. The function is annotated and has a multi-line docstring.
main.py
def sort_names(names: list[str], *, by_length: bool = False) -> list[str]:
"""Return the names sorted alphabetically, ignoring case.
With by_length=True, shorter names come first; names of the
same length keep their alphabetical order.
"""
ordered = sorted(names, key=lambda name: name.lower())
if by_length:
ordered = sorted(ordered, key=len)
return ordered
team = ["maya", "Bo", "leon", "ada", "Christopher"]
print(sorted(team))
print(sort_names(team))
print(sort_names(team, by_length=True))
print(sort_names.__annotations__)
print(sort_names.__doc__)
Run it with
python main.pyOutput
['Bo', 'Christopher', 'ada', 'leon', 'maya']
['ada', 'Bo', 'Christopher', 'leon', 'maya']
['Bo', 'ada', 'leon', 'maya', 'Christopher']
{'names': list[str], 'by_length': <class 'bool'>, 'return': list[str]}
Return the names sorted alphabetically, ignoring case.
With by_length=True, shorter names come first; names of the
same length keep their alphabetical order.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.
Exercise 1 of 2
Complete two functions. sort_by_last_letter(words) returns the words sorted by their last letter, with a lambda as the key. sort_by_length(words, *, longest_first=False) returns them sorted by length, shortest first, or longest first when longest_first=True. Words of equal length keep their order. Both return new lists and leave the argument unchanged.
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.
sorted(words, key=…) returns a new sorted list and leaves words alone.
For the last letter, the key is lambda word: word[-1]. For the length, key=len is enough.
Pass the flag straight on: sorted(words, key=len, reverse=longest_first).
One way to solve it. Yours can look different and still pass the checks.
def sort_by_last_letter(words: list[str]) -> list[str]:
"""Return the words sorted by their last letter."""
return sorted(words, key=lambda word: word[-1])
def sort_by_length(words: list[str], *, longest_first: bool = False) -> list[str]:
"""Return the words sorted by length, shortest first by default."""
return sorted(words, key=len, reverse=longest_first)
print(sort_by_last_letter(["kiwi", "fig", "banana", "apple"]))
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 sort_by_last_letter(words: list[str]) -> list[str]:
"""Return the words sorted by their last letter."""
return list(words) # sort them instead
def sort_by_length(words: list[str], *, longest_first: bool = False) -> list[str]:
"""Return the words sorted by length, shortest first by default."""
return list(words) # sort them instead
print(sort_by_last_letter(["kiwi", "fig", "banana", "apple"]))
test_main.py
from main import sort_by_last_letter, sort_by_length
def test_last_letter():
"""Sorted by the last letter"""
got = sort_by_last_letter(["kiwi", "fig", "banana", "apple"])
assert got == ["banana", "apple", "fig", "kiwi"], f"sort_by_last_letter(['kiwi', 'fig', 'banana', 'apple']) returned {got!r}"
def test_shortest_first():
"""sort_by_length puts the shortest word first"""
got = sort_by_length(["pear", "fig", "banana"])
assert got == ["fig", "pear", "banana"], f"sort_by_length(['pear', 'fig', 'banana']) returned {got!r}, expected ['fig', 'pear', 'banana']"
def test_longest_first():
"""longest_first=True reverses the order"""
got = sort_by_length(["pear", "fig", "banana"], longest_first=True)
assert got == ["banana", "pear", "fig"], f"sort_by_length(['pear', 'fig', 'banana'], longest_first=True) returned {got!r}"
def test_equal_lengths_keep_order():
"""Words of equal length keep their order"""
got = sort_by_length(["bb", "aa", "c"])
assert got == ["c", "bb", "aa"], f"sort_by_length(['bb', 'aa', 'c']) returned {got!r}, expected ['c', 'bb', 'aa']"
def test_argument_unchanged():
"""The list passed in is not changed"""
words = ["kiwi", "fig"]
sort_by_length(words)
assert words == ["kiwi", "fig"], f"after sort_by_length(words), words is {words!r}; use sorted(), which returns a new list"
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
calc_total works, but breaks PEP 8 and has no documentation. Rename its parameters to prices and tax_rate, indent with 4 spaces, give each statement its own line, and add spaces around operators. Add a one-line docstring and annotations: prices is list[float], tax_rate is float with default 0.19, and it returns a float. The result must stay the same.
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.
Start with the def line: def calc_total(prices: list[float], tax_rate: float = 0.19) -> float:. PEP 8 wants spaces around = here, because the parameter is annotated.
Split for p in Prices:t=t+p into a for line and an indented body line, and use clear names: total and price.
The docstring goes directly under the def line, indented by 4 spaces, for example """Return the sum of the prices plus tax."""
One way to solve it. Yours can look different and still pass the checks.
def calc_total(prices: list[float], tax_rate: float = 0.19) -> float:
"""Return the sum of the prices plus tax."""
total = 0.0
for price in prices:
total = total + price
return total + total * tax_rate
print(calc_total([10, 20]))
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 calc_total(Prices,Tax_Rate=0.19):
t=0
for p in Prices:t=t+p
return t+t*Tax_Rate
print(calc_total([10, 20]))
test_main.py
from main import calc_total
def test_result_unchanged():
"""calc_total([10, 20]) is still 35.7"""
got = calc_total([10, 20])
assert round(got, 2) == 35.7, f"calc_total([10, 20]) returned {got!r}, expected 35.7"
def test_tax_rate_keyword():
"""The parameter is called tax_rate"""
got = calc_total([10.0], tax_rate=0.0)
assert got == 10.0, f"calc_total([10.0], tax_rate=0.0) returned {got!r}, expected 10.0"
def test_docstring():
"""It has a docstring: capital letter first, period last"""
doc = calc_total.__doc__
assert doc, "calc_total has no docstring"
first = doc.strip().splitlines()[0]
assert first[0].isupper() and first.endswith("."), f"the docstring's first line is {first!r}"
def test_annotations():
"""prices, tax_rate and the return value are annotated"""
names = sorted(calc_total.__annotations__)
assert names == ["prices", "return", "tax_rate"], f"calc_total.__annotations__ has the keys {names!r}"
assert calc_total.__annotations__["return"] is float, "the return annotation should be float"
def test_four_space_indent():
"""Every indented line uses a multiple of 4 spaces"""
with open("main.py", encoding="utf-8") as f:
lines = f.read().splitlines()
bad = [line for line in lines if line.startswith(" ") and (len(line) - len(line.lstrip(" "))) % 4 != 0]
assert not bad, f"these lines are not indented by a multiple of 4 spaces: {bad!r}"
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.pywords = ["kiwi", "fig", "banana"]
print(sorted(words, key=lambda w: return len(w)))
What Python prints
SyntaxError: invalid syntaxWhy, and the fix
A lambda body is a single expression, and its value is returned automatically. return is a statement, so it is not allowed there. Write lambda w: len(w), or simply key=len.
words = ["kiwi", "fig", "banana"]
print(sorted(words, key=len()))
What Python prints
TypeError: len() takes exactly one argument (0 given)Why, and the fix
key needs the function itself, which sorted calls once per item. len() calls len right away, with no argument. Pass the name without parentheses: key=len.
def greet(name: str) -> str:
return "Hello, " + name
print(greet.__annotation__)
What Python prints
AttributeError: 'function' object has no attribute '__annotation__'Why, and the fix
The attribute is __annotations__, plural, with two underscores on each side. Python 3.14 adds a suggestion to the message: Did you mean: '__annotations__'?
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.