Warm-up · Activity 1 of 7
Warm-up from lesson B3.2: what does this print?
def label(text, prefix="*", suffix="*"):
return prefix + text + suffix
print(label("hi", suffix="!"))// B3.3 · ~30 min · Beginner
After this lesson you can control how each argument may be passed, write functions that accept any number of arguments, and unpack a list or dictionary straight into a call.
You will be able to
Warm-up · Activity 1 of 7
def label(text, prefix="*", suffix="*"):
return prefix + text + suffix
print(label("hi", suffix="!"))Predict · Activity 2 of 7
def total(*numbers):
result = 0
for n in numbers:
result += n
return result
print(total(1, 2, 3), total())Practice · Activity 3 of 7
def connect(host, ____, port=80, timeout=10):
print(host, port, timeout)Practice · Activity 4 of 7
def tag(name, **attributes):
print(name, attributes)
tag("img", src="cat.png", alt="A cat")Practice · Activity 5 of 7
Brain teaser · Activity 6 of 7
def f(a, b, *rest, c=0):
print(a, b, rest, c)
f(1, 2, 3, 4, 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
log takes a level, any number of message words and any number of name=value details, and prints them as one line. level is positional-only, so a detail may itself be called level, as the third call shows. The last call unpacks a list with * and a dictionary with **. str() turns a number into text so it can be joined.
main.py
def log(level, /, *messages, **context):
"""Print one log line: the level, the messages, then name=value pairs."""
line = "[" + level + "]"
for message in messages:
line = line + " " + message
for name in context:
line = line + " " + name + "=" + str(context[name])
print(line)
log("INFO", "server", "started")
log("ERROR", "upload failed", user="ada", size=12)
log("WARN", level="high")
parts = ["cache", "cleared"]
details = {"items": 42, "seconds": 0.8}
log("INFO", *parts, **details)
Run it with
python main.pyOutput
[INFO] server started
[ERROR] upload failed user=ada size=12
[WARN] level=high
[INFO] cache cleared items=42 seconds=0.8Tab 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
Rewrite total in main.py so that it accepts any number of values and a keyword-only start: total() is 0, total(1, 2, 3) is 6, and total(1, 2, start=10) is 13. A list unpacked with * must work too: total(*[4, 5]) is 9. Use a for loop to add them.
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.
Put a * in front of numbers, so it collects every positional argument into a tuple.
start now comes after *numbers, which makes it keyword-only by itself.
Start with result = start, add each n in numbers in a loop, and return result.
One way to solve it. Yours can look different and still pass the checks.
def total(*numbers, start=0):
"""Return start plus all the numbers."""
result = start
for n in numbers:
result += n
return result
print(total(1, 2, 3))
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(numbers, start=0):
"""Return start plus all the numbers."""
return start
# Try: print(total(1, 2, 3))
test_main.py
from main import total
def test_no_values():
"""total() is 0"""
got = total()
assert got == 0, f"total() returned {got!r}, expected 0"
def test_three_values():
"""total(1, 2, 3) is 6"""
got = total(1, 2, 3)
assert got == 6, f"total(1, 2, 3) returned {got!r}, expected 6"
def test_start_by_keyword():
"""total(1, 2, start=10) is 13"""
got = total(1, 2, start=10)
assert got == 13, f"total(1, 2, start=10) returned {got!r}, expected 13"
def test_unpacked_list():
"""total(*[4, 5]) is 9"""
got = total(*[4, 5])
assert got == 9, f"total(*[4, 5]) returned {got!r}, expected 9"
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
Write make_tag(name, /, *children, **attributes) that returns an HTML element as a string. The children are joined without spaces between the tags, and each attribute becomes key="value" in the opening tag, in the order given. make_tag("a", "Home", href="/") returns <a href="/">Home</a>. Because name is positional-only, make_tag("p", name="x") gives <p name="x"></p>.
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 from the signature in the prompt: name, then /, then *children and **attributes.
Build the opening tag in a loop: for key in attributes: add a space, the key, =" , attributes[key] and a closing ".
Add every child to a content string, then return opening + ">" + content + "</" + name + ">".
One way to solve it. Yours can look different and still pass the checks.
def make_tag(name, /, *children, **attributes):
"""Return an HTML element as a string."""
opening = "<" + name
for key in attributes:
opening = opening + " " + key + '="' + attributes[key] + '"'
content = ""
for child in children:
content = content + child
return opening + ">" + content + "</" + name + ">"
print(make_tag("a", "Home", href="/"))
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 make_tag(name):
"""Return an HTML element as a string."""
return "<" + name + "></" + name + ">"
print(make_tag("p"))
test_main.py
from main import make_tag
def test_empty_tag():
"""make_tag("br") has no content"""
got = make_tag("br")
assert got == "<br></br>", f"make_tag('br') returned {got!r}, expected '<br></br>'"
def test_children():
"""Children are joined inside the tag"""
got = make_tag("p", "Hi", " there")
assert got == "<p>Hi there</p>", f"make_tag('p', 'Hi', ' there') returned {got!r}, expected '<p>Hi there</p>'"
def test_one_attribute():
"""make_tag("a", "Home", href="/") has an href"""
got = make_tag("a", "Home", href="/")
assert got == '<a href="/">Home</a>', f"make_tag('a', 'Home', href='/') returned {got!r}, expected '<a href=\"/\">Home</a>'"
def test_attribute_order():
"""Attributes keep the order of the call"""
got = make_tag("img", src="c.png", alt="cat")
assert got == '<img src="c.png" alt="cat"></img>', f"make_tag('img', src='c.png', alt='cat') returned {got!r}"
def test_name_as_attribute():
"""name is positional-only, so name="x" is an attribute"""
got = make_tag("p", name="x")
assert got == '<p name="x"></p>', f"make_tag('p', name='x') returned {got!r}, expected '<p name=\"x\"></p>'"
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.pydef square(n, /):
return n * n
print(square(n=4))
What Python prints
TypeError: square() got some positional-only arguments passed as keyword arguments: 'n'Why, and the fix
The / after n makes it positional-only, so the call may not name it. Write square(4). If callers should be allowed to write n=4, remove the /.
def connect(host, *, port=80):
print(host, port)
connect("example.org", 8080)
What Python prints
TypeError: connect() takes 1 positional argument but 2 were givenWhy, and the fix
Everything after the bare * is keyword-only, so connect accepts one positional argument, host. Name the other one: connect("example.org", port=8080). The message counts only the positional arguments.
def area(width, height):
return width * height
size = [3, 4]
print(area(size))
What Python prints
TypeError: area() missing 1 required positional argument: 'height'Why, and the fix
area(size) passes the whole list as width, and height gets nothing. Unpack it with a star: area(*size) is the same as area(3, 4).
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.