Skip to content
aviral gupta

// B5.2 · ~25 min · Beginner

Formatted output with f-strings

After this lesson you can print values in tidy columns with f-strings, round them to fixed decimals with thousands separators, and show exactly what a variable holds while debugging.

Lesson 2 of 5 in B5 Modules, files and errors

You will be able to

  • Put values into text with f-strings and str.format
  • Set width, alignment, decimal places and thousands separators with a format spec
  • Show values for debugging with = and !r, and tell str() from repr()
  1. Warm-up · Activity 1 of 7

    Warm-up from the last lesson: after which of these lines can you call floor(2.5) without writing math. in front? Pick all that apply.

    Select all that apply.

  2. Predict · Activity 2 of 7

    Predict before you read on: what does this print?

    price = 3.14159
    print(f"Total: {price:.2f} EUR")
  3. Practice · Activity 3 of 7

    Fill in the format spec so that the number prints as 84,607,016.

    population = 84607016
    print(f"{population:____}")
    {population:}
  4. Practice · Activity 4 of 7

    The | characters show where each field ends. What does this print?

    print(f"{'Tea':<6}|{2.5:>6.2f}|")
  5. Practice · Activity 5 of 7

    x = 1234.567. Match each f-string to the text it produces.

  6. Brain teaser · Activity 6 of 7

    Brain teaser. Both fields have the same width and no alignment. What does this print?

    print(f"[{'ab':5}][{42:5}]")
  7. Apply · Activity 7 of 7

    Mini-task. Print a receipt for items = [("Coffee", 3.2), ("Bagel", 2.75), ("Juice", 4.5)]: each name left-aligned in 10 characters, each price right-aligned in 8 characters with two decimal places, and a last line Total with the sum in the same columns.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

A monthly budget table

Three expenses are printed as a table: names left-aligned, amounts right-aligned with two decimal places, and a total with a thousands separator. Two debugging lines follow: the = specifier with a percentage format, and str.format with !r, which shows the quotes around a string.

main.py

items = [("Rent", 950.0), ("Groceries", 312.456), ("Internet", 39.9)]
total = sum(price for _, price in items)

print(f"{'Item':<12}{'EUR':>10}")
for name, price in items:
    print(f"{name:<12}{price:>10.2f}")
print("-" * 22)
print(f"{'Total':<12}{total:>10,.2f}")

rent_share = items[0][1] / total
print(f"{rent_share = :.1%}")
print("{} items, the first is {!r}".format(len(items), items[0][0]))

Run it with

python main.py

Output

Item               EUR
Rent            950.00
Groceries       312.46
Internet         39.90
----------------------
Total         1,302.36
rent_share = 72.9%
3 items, the first is 'Rent'
  • 312.456 prints as 312.46 and 39.9 as 39.90: .2f always shows exactly two decimals.
  • The headers are strings, so they need < and > to match the columns below them.
  • rent_share = keeps the spaces around =, and :.1% multiplies by 100 and adds a percent sign.
  • {!r} in str.format works like !r in an f-string: the string is shown with its quotes.
Change it and run it

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.

Exercises

Exercise 1 of 2

A receipt line

Write format_line(name, qty, price) that returns one receipt line: the name left-aligned in 10 characters, the quantity right-aligned in 3, then " x ", then the price right-aligned in 7 characters with two decimal places. format_line("Tea", 2, 3.5) returns "Tea 2 x 3.50", 23 characters in all.

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.

Hints
  1. Hint 1

    Each value gets its own format spec after a colon: {name:<10}, {qty:>3} and {price:>7.2f}.

  2. Hint 2

    Everything outside the braces is copied as it is, so " x " goes straight into the f-string between qty and price.

  3. Hint 3

    return f"{name:<10}{qty:>3} x {price:>7.2f}"

Show a solution

One way to solve it. Yours can look different and still pass the checks.

def format_line(name: str, qty: int, price: float) -> str:
    return f"{name:<10}{qty:>3} x {price:>7.2f}"


if __name__ == "__main__":
    print(format_line("Tea", 2, 3.5))
    print(format_line("Croissant", 12, 1.25))
Run it on your computer

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 format_line(name: str, qty: int, price: float) -> str:
    return f"{name} {qty} x {price}"


if __name__ == "__main__":
    print(format_line("Tea", 2, 3.5))
    print(format_line("Croissant", 12, 1.25))

test_main.py

from main import format_line


def test_tea():
    """Tea, 2, 3.5 gives the example line"""
    got = format_line("Tea", 2, 3.5)
    assert got == "Tea         2 x    3.50", f"format_line(\"Tea\", 2, 3.5) returned {got!r}"


def test_width():
    """Every line is 23 characters long"""
    got = format_line("Croissant", 12, 1.25)
    assert len(got) == 23, f"the line {got!r} has {len(got)} characters, expected 23"


def test_two_decimals():
    """Prices always show two decimal places, rounded"""
    got = format_line("Cake", 1, 12.349)
    assert got.endswith("  12.35"), f"for 12.349 the line ends with {got[-7:]!r}, expected '  12.35'"


def test_columns():
    """Name, quantity and price sit in their columns"""
    got = format_line("Jam", 10, 150.0)
    assert got == "Jam        10 x  150.00", f"format_line(\"Jam\", 10, 150.0) returned {got!r}"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Exercise 2 of 2

Money and percentages

Write two functions. money(amount) returns the amount with a thousands separator and two decimals, followed by " EUR": money(1234.5) is "1,234.50 EUR". percent(part, whole) returns part / whole as a percentage with one decimal place: percent(1, 8) is "12.5%". Use the format specs , .2f and .1% rather than rounding by hand.

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.

Hints
  1. Hint 1

    In a format spec the comma comes before the precision: {amount:,.2f}.

  2. Hint 2

    The % type multiplies by 100 and adds the percent sign itself, so pass the fraction part / whole, not part / whole * 100.

  3. Hint 3

    return f"{part / whole:.1%}"

Show a solution

One way to solve it. Yours can look different and still pass the checks.

def money(amount: float) -> str:
    return f"{amount:,.2f} EUR"


def percent(part: float, whole: float) -> str:
    return f"{part / whole:.1%}"


if __name__ == "__main__":
    print(money(1234.5), percent(1, 8))
Run it on your computer

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 money(amount: float) -> str:
    return str(amount) + " EUR"


def percent(part: float, whole: float) -> str:
    return str(part / whole * 100) + "%"


if __name__ == "__main__":
    print(money(1234.5), percent(1, 8))

test_main.py

from main import money, percent


def test_money_separator():
    """1234.5 becomes 1,234.50 EUR"""
    got = money(1234.5)
    assert got == "1,234.50 EUR", f"money(1234.5) returned {got!r}"


def test_money_small_and_large():
    """0.5 and 2500000 get two decimals and separators"""
    got = money(0.5), money(2500000)
    assert got == ("0.50 EUR", "2,500,000.00 EUR"), f"money(0.5) and money(2500000) returned {got!r}"


def test_percent():
    """1 of 8 is 12.5%, and 2 of 3 is 66.7%"""
    got = percent(1, 8), percent(2, 3)
    assert got == ("12.5%", "66.7%"), f"percent(1, 8) and percent(2, 3) returned {got!r}"

On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.

Run the program:

python main.py

Run the checks (needs learnrun.py in the same folder):

python learnrun.py test
Download learnrun.py

Common mistakes

Formatting a string as a number

value = "3.5"  # what input() gives you: a string
print(f"{value:.1f}")

What Python prints

ValueError: Unknown format code 'f' for object of type 'str'

Why, and the fix

The f type only works on numbers, and input() always returns a string. Convert first: value = float(input()). Then f"{value:.1f}" works.

Too few arguments for str.format

print("{} scored {}".format("Ada"))

What Python prints

IndexError: Replacement index 1 out of range for positional args tuple

Why, and the fix

Each empty {} takes the next argument, and the second one, index 1, has none. Pass one argument per placeholder, or use an f-string, where the values sit inside the braces and cannot go missing: f"{name} scored {score}".

Using d for a float

total = 2.5 * 4
print(f"{total:,d}")

What Python prints

ValueError: Unknown format code 'd' for object of type 'float'

Why, and the fix

d is for integers, and 2.5 * 4 is the float 10.0. Use .0f to show a float without decimals, f"{total:,.0f}", or convert it with int(total) if it must be a whole number.

Python in the browser: Pyodide 314.0.7, MPL-2.0. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

f-strings and str.format

Put an f before the quotes and write any expression in braces: f"{name} is {age + 1}" evaluates name and age + 1 and puts their text in place. Calls and arithmetic are fine inside the braces. For a literal brace, double it: f"{{x}}" prints {x}. The older str.format method does the same with empty placeholders filled in order: "{} and {}".format("tea", "cake"). Numbers or names in the braces pick arguments: "{1} {0}" swaps them, and "{name}" takes name=… .

The format spec after the colon

After a colon comes a format spec. .2f means a float with two decimal places, rounded: f"{3.14159:.2f}" is 3.14. A number sets the minimum width, padded with spaces; a longer value is never cut. < aligns left, > right and ^ centres; a character before it fills instead of spaces, as in *^9. Without an alignment, numbers go right and strings go left. A comma adds thousands separators: f"{1234567.891:,.2f}" is 1,234,567.89. The order is fill, align, width, comma, precision, type.

Debugging with = and !r

str() gives the readable form of a value, and repr() the form you would type in code: str("Ada") is Ada, repr("Ada") is 'Ada' with quotes. That makes invisible things visible, such as a trailing space or a "5" that is a string, not a number. In an f-string, !r uses repr: f"{word!r}". f"{x=}" prints the expression, an equals sign and the value's repr, such as x=7; spaces around = are kept, and a format spec after it is used instead of repr.

Sources

Last reviewed September 29, 2026