← Home

039. Formatting Output

Align and format values for readability

039. Formatting Output

RAM Manager: Process Table Display

Aryan wants his RAM manager to print a table like ps aux β€” perfectly aligned columns regardless of process name length:

chrome          [  812]   544MB   6.8%
python3         [  421]   398MB   4.9%
Slack           [  234]   256MB   3.2%

Without format specifiers, long names push other columns off alignment. The {x:<15} (left-align in 15 chars), {n:>6} (right-align in 6 chars), and {f:.1f} (1 decimal) specs give him pixel-perfect columns.

The broken code has no format specs at all β€” just raw values crammed together β€” and format_currency shows $1234.56 without the thousands comma.


πŸ’‘ Fun fact: Python’s format specification mini-language (the :<15, :,.2f syntax) is directly inspired by the printf format strings from C, but extended with alignment, fill characters, and the , thousands separator. The , separator alone was added in Python 2.7 (2010) specifically because so many developers were manually formatting currency β€” it was one of the most requested features at the time.

⚠️ Watch out: f"{value:.1%}" and f"{value:.1f}%" look similar but behave very differently β€” :.1% multiplies by 100 automatically, so you pass 0.75 to get "75.0%", while :.1f% expects you to pass 75.0 already. Mixing these up produces values that are off by a factor of 100 with no error message.

πŸ€” Think about it: f"{name:<15}" pads name to 15 characters with spaces on the right. What happens if name is longer than 15 characters β€” does it truncate, overflow, or something else? How would you enforce a hard maximum column width in a table where process names could be arbitrarily long?

Learning objectives

  • Align text with < > ^ alignment specifiers
  • Format numbers with thousands separator (,) and decimal places (.Nf)
  • Use format specs for clean tabular output

Key concepts

  • format specifiers
  • alignment
  • number formatting

Try it

Concept detail

f-string format specifiers follow the format: {value:[[fill]align][width][,][.precision][type]}

Alignment: {x:<15} left-align in 15 chars (default for strings) {x:>10} right-align in 10 chars (default for numbers) {x:^20} center in 20 chars {x:*>10} right-align, pad with * instead of spaces

Number formatting: {n:,} thousands separator β†’ 1,000,000 {f:.2f} 2 decimal places β†’ 3.14 {n:05d} zero-padded integer β†’ 00042 {n:,.2f} both thousands separator AND 2 decimals β†’ 1,234.56 {f:.1%} percentage (multiplies by 100) β†’ β€œ75.0%” (note: f=0.75 not 75)

Combined: {n:>10,.2f} right-align 10 wide, thousands, 2 decimals

These same specs work in str.format(): β€œ{:,.2f}”.format(1234.5) And in % formatting (older): β€œ%.2f” % 3.14

Solution

def format_row(name, score, grade):
    return f"{name:<15}{score:>5}    {grade}"

def format_currency(amount):
    return f"${amount:,.2f}"

def format_percent(value, total):
    pct = value / total * 100
    return f"{pct:.1f}%"

Tests

def test_format_row_alignment():
    result = format_row("Alice", 95, "A")
    assert result.startswith("Alice")
    assert "95" in result
    assert "A" in result
    assert len(result) >= 20, "Row must be padded to fixed width"

def test_format_row_name_padded():
    short = format_row("Al", 95, "A")
    long_name = format_row("Alexander", 95, "A")
    # Score position should be consistent regardless of name length
    assert short.index("95") == long_name.index("95"), "Score column not aligned"

def test_format_currency():
    assert format_currency(1234.56) == "$1,234.56"

def test_format_currency_thousands():
    assert format_currency(1000000) == "$1,000,000.00"

def test_format_currency_no_thousands():
    result = format_currency(1234.56)
    assert "," in result, "Missing thousands separator β€” use :,.2f"

def test_format_percent():
    result = format_percent(3, 4)
    assert result == "75.0%"

def test_format_percent_precision():
    result = format_percent(1, 3)
    assert result == "33.3%", f"Got {repr(result)} β€” use :.1f for 1 decimal place"

Resources