String Formatting with f-strings

f-strings — string literals prefixed with f — are the modern way to build text from values. Beyond inserting variables, their format specifiers control decimal places, padding, alignment, thousands separators, percentages and dates, all inline.

The basics

PY basics.py
name = "Ana"
items = 3
price = 4.5

print(f"Hi {name}, you have {items} items.")
print(f"Total: {items * price}")
print(f"Shout: {name.upper()}!")
Output
Hi Ana, you have 3 items.
Total: 13.5
Shout: ANA!

Anything between braces is a Python expression, evaluated and converted to a string. To include a literal brace, double it: f"{{not a field}}".

Format specifiers

After the expression, a colon introduces a format spec:

PY specs.py
pi = 3.14159265
big = 1234567.891
ratio = 0.256

print(f"{pi:.2f}")
print(f"{big:,.2f}")
print(f"{big:_.0f}")
print(f"{ratio:.1%}")
print(f"{42:05d}")
print(f"{255:x} {255:b} {255:#x}")
print(f"{1e-7:.2e}")
Output
3.14
1,234,567.89
1_234_568
25.6%
00042
ff 11111111 0xff
1.00e-07
SpecMeaning
.2fFixed-point, 2 decimal places
, or _Thousands separator
%Multiply by 100 and add a percent sign
05dInteger, zero-padded to width 5
x b o eHex, binary, octal, scientific notation

Width and alignment

PY align.py
rows = [("Apples", 3, 1.2), ("Bananas", 12, 0.5), ("Cherries", 200, 0.05)]

print(f"{'Item':<10}{'Qty':>5}{'Price':>8}")
for item, qty, price in rows:
    print(f"{item:<10}{qty:>5}{price:>8.2f}")
print(f"{'centred':^20}|")
print(f"{'pad':*^11}")
Output
Item        Qty   Price
Apples        3    1.20
Bananas      12    0.50
Cherries    200    0.05
      centred       |
****pad****

<, > and ^ align left, right and centre within the given width; a character before them sets the fill. This makes neat text tables easy.

Debugging with =

Adding = after an expression prints the expression itself along with its value — perfect for quick debugging:

PY debug.py
width = 12
height = 5
print(f"{width=}, {height=}, {width * height=}")
print(f"{width / 7 = :.3f}")
Output
width=12, height=5, width * height=60
width / 7 = 1.714

repr and str conversions

PY conversions.py
text = "line\nbreak"
print(f"{text}")
print(f"{text!r}")
Output
line
break
'line\nbreak'

!r uses repr(), showing quotes and escape sequences — useful for spotting stray whitespace.

Dates and nested specs

PY dates.py
from datetime import date

d = date(2026, 10, 6)
print(f"{d:%A, %d %B %Y}")
print(f"{d:%Y-%m-%d}")

decimals = 3
print(f"{2 / 3:.{decimals}f}")
Output
Tuesday, 06 October 2026
2026-10-06
0.667

Objects can define their own format specs; dates use strftime codes (lesson 49). Specs can themselves contain fields, like {decimals} above.

Multi-line f-strings

PY multiline.py
order = {"id": 1042, "customer": "Rosa", "total": 7.8}

receipt = (
    f"Order #{order['id']}\n"
    f"Customer: {order['customer']}\n"
    f"Total: ${order['total']:.2f}"
)
print(receipt)
Output
Order #1042
Customer: Rosa
Total: $7.80

Adjacent string literals are joined automatically, which keeps long f-strings readable. Since Python 3.12, you can even reuse the same quote type inside the braces, e.g. f"{order["id"]}".

Older formatting styles

You'll see two older styles in existing code. They still work, but f-strings are clearer:

PY older.py
name, score = "Ana", 91.5
print("%s scored %.1f" % (name, score))
print("{} scored {:.1f}".format(name, score))
print(f"{name} scored {score:.1f}")
Output
Ana scored 91.5
Ana scored 91.5
Ana scored 91.5

One exception: when logging, pass values as arguments — logging.info("User %s logged in", name) — so the message is only formatted if it's actually logged.

Try it yourself

Try it yourself
Console output