Skip to content

← Pattern Matching, f-Strings and Newer Syntax step 5 of 23

Medium Primitives

Render a Text Table with Python 3.12 f-Strings

Python 3.12 removed three old limits on the code inside an f-string's braces:

row = {"name": "ada", "age": 36}
keys = ["name", "age"]

# The same quote character inside and outside the braces.
print(f"{row["name"]}")  # ada

# A backslash inside the braces.
print(f"{"\n".join(keys)}")  # name and age, on two lines

# An expression that spans lines, with a comment.
print(f"{
    " | ".join(keys)  # the header
}")  # name | age

Before 3.12 you had to switch quotes, as in row['name'], and move "\n" into a variable. A comment runs to the end of its line, so put the closing } on a later line.

What to write

Fill in solve(rows, keys). It returns a text table in three parts, joined by single newlines:

  1. The header. Join keys with " | ", in the given order, keeping any duplicates.
  2. The body. For each row, look up each key in order and join the values with " | ". Then join the row strings with newlines.
  3. The footer, f"{len(rows)} row(s)".

For two rows with names and ages, solve returns this, with no newline after the footer:

name | age
ada | 36
bob | 7
2 row(s)

Follow these rules:

  • Use "-" for a key the row does not have. row.get(key, "-") does this. A key that is present with the value None shows as None.
  • Show each value as str would, with no repr and no escaping. A list shows as ['x', 'y'], and a " | " inside a value stays as it is.
  • Don't sort the keys, remove duplicates or change the rows.

Use the new syntax. Return one expression in parentheses, made of three adjacent f-strings, one per part. Inside the body's braces, put the newline join and a generator expression that spans lines. Write row.get(key, "-") inside a double-quoted f-string.

Edge cases

  • With no rows, the body is empty. Keys ["name"] give "name\n\n0 row(s)".
  • With no keys, the header and every row string are empty. One row gives "\n\n1 row(s)". Two rows give "\n\n\n2 row(s)", because the body holds a newline between its two empty rows.

Going further

This syntax needs Python 3.12 or newer. An older Python rejects the whole file, even when the new syntax is in an if branch that never runs. A package that uses it should declare requires-python = ">=3.12" in its pyproject.toml.

A lambda or a := inside the braces needs its own parentheses, as in f"{(x := 10)}", because a bare colon starts the format spec. These changes come from PEP 701, which made f-strings part of Python's grammar.