String Formatting Deep Dive
You've used f-strings since the first lessons to drop values into text. They can do much more: round numbers, line up columns, add thousands separators, show percentages and pad with zeros. All of it uses a small format specification after a colon inside the braces.
The format specifier
Inside an f-string, {value:spec} formats the value according to spec:
price = 1234.5678
print(f"{price}")
print(f"{price:.2f}") # 2 decimal places
print(f"{price:,.2f}") # thousands separator tooThe pieces of a spec always appear in this order, and each one is optional:
{value:[fill][align][sign][width][,][.precision][type]}
Let's go through them.
Decimal places
.Nf shows a number with exactly N digits after the decimal point, rounding as needed:
import math
print(f"{math.pi:.0f}")
print(f"{math.pi:.2f}")
print(f"{math.pi:.5f}")
print(f"{2:.2f}") # whole numbers get decimals tooThousands separators
A comma (or an underscore) groups digits in thousands:
population = 8_045_311_447
print(f"{population:,}")
print(f"{population:_}")
print(f"{1234567.891:,.2f}")Percentages
The % type multiplies by 100 and adds a percent sign:
ratio = 0.4567
print(f"{ratio:%}")
print(f"{ratio:.1%}")
print(f"{3 / 8:.0%}")Width and alignment
A number after the colon sets the minimum width. < aligns left, > aligns right, and ^ centres:
print(f"[{'left':<10}]")
print(f"[{'right':>10}]")
print(f"[{'centre':^10}]")
print(f"[{42:>6}]") # numbers align right by default
print(f"[{'text':10}]") # strings align left by defaultFill characters
Put a character before the alignment to fill the space with it instead of spaces:
print(f"{'Menu':*^20}")
print(f"{'Total':.<15}{9.5:.>8.2f}")
print(f"{7:0>3}") # pad with zeros
print(f"{42:05}") # zero-padding shortcutSigns
Control whether positive numbers show a sign:
for change in [3.5, -1.25, 0]:
print(f"{change:+.2f}") # always show + or -Other number formats
n = 255
print(f"{n:b}") # binary
print(f"{n:o}") # octal
print(f"{n:x}") # hexadecimal
print(f"{n:X}")
print(f"{n:#x}") # with 0x prefix
print(f"{1234567:e}")
print(f"{0.000123:.2e}")Building a neat table
Widths and alignment really pay off when you print data in columns:
items = [("Coffee", 2, 3.5), ("Blueberry muffin", 1, 2.75), ("Juice", 3, 4.0)]
print(f"{'Item':<18}{'Qty':>4}{'Price':>9}")
print("-" * 31)
total = 0
for name, qty, price in items:
line = qty * price
total += line
print(f"{name:<18}{qty:>4}{line:>9.2f}")
print("-" * 31)
print(f"{'Total':<22}{total:>9.2f}")Expressions and debugging
Any expression works inside the braces, and adding = prints the expression as well as its value, which is great for debugging:
width = 7
height = 3
print(f"{width * height = }")
print(f"{width=}, {height=}")
print(f"{'python'.upper()!r}") # !r shows the repr, with quotesDynamic widths
The spec itself can contain values in braces:
name = "Ada"
for w in [5, 8, 12]:
print(f"[{name:>{w}}]")
decimals = 3
print(f"{2 / 3:.{decimals}f}")Other formatting styles
You'll see two older styles in existing code. They use the same format specs:
name = "Ada"
score = 92.456
print("{} scored {:.1f}".format(name, score))
print("{n} scored {s:.1f}".format(n=name, s=score))
print("%s scored %.1f" % (name, score))
print(format(score, ".1f"))For new code, f-strings are the clearest choice.
Exercises
Exercise 1: Money
Print the salary with a currency sign, thousands separators and 2 decimal places: Salary: $52,750.50.
salary = 52750.5
, adds thousands separators and .2f gives two decimal places.
Combine them as {salary:,.2f}, with a $ just before the braces.
Exercise 2: Progress bar
Print the progress as a percentage with 1 decimal place, centred in a 20-character field filled with =. It should print =======62.5%========.
done = 25 total = 40
.1% shows a ratio as a percentage with one decimal place.
Put the fill and alignment in front: =^20 means fill with =, centred, 20 characters wide.
Exercise 3: Invoice numbers
Print each invoice number padded with zeros to 6 digits and prefixed with INV-, such as INV-000042.
invoice_ids = [42, 1337, 7]
06 pads a number with zeros to a width of 6.
Print f"INV-{invoice_id:06}" for each number.
Exercise 4: Leaderboard
Print the leaderboard as a table: the position right-aligned in 3 characters, the name left-aligned in 10, and the score right-aligned in 8 with thousands separators.
1 Leo 18,500
2 Mia 12,000
3 Ava 9,900
players = [("Leo", 18500), ("Mia", 12000), ("Ava", 9900)]
enumerate(players, start=1) gives the position, and you can unpack (name, score) in the same for line.
Use {position:>3}, {name:<10} and {score:>8,}.
Format specifiers at a glance
A quick reference for later. Results are shown with quotes, so you can see the spaces and padding.
| Spec | What it does | Example | Result |
|---|---|---|---|
.2f |
2 decimal places | f"{3.14159:.2f}" |
'3.14' |
.0f |
Rounds to a whole number | f"{2.718:.0f}" |
'3' |
, |
Thousands separators | f"{1234567:,}" |
'1,234,567' |
,.2f |
Separators and 2 decimal places | f"{1234.5:,.2f}" |
'1,234.50' |
_ |
Underscores as separators | f"{1234567:_}" |
'1_234_567' |
% |
Percentage | f"{0.256:.1%}" |
'25.6%' |
<6 |
Left-align in 6 characters | f"{'hi':<6}" |
'hi ' |
>6 |
Right-align in 6 characters | f"{'hi':>6}" |
' hi' |
^6 |
Centre in 6 characters | f"{'hi':^6}" |
' hi ' |
*^6 |
Centre in 6 characters, filling with * |
f"{'hi':*^6}" |
'**hi**' |
05 |
Pad a number with zeros to 5 characters | f"{42:05}" |
'00042' |
+ |
Always show the sign | f"{3.5:+.1f}" |
'+3.5' |
b, o, x |
Binary, octal, hexadecimal | f"{255:x}" |
'ff' |
#x |
Hexadecimal with the 0x prefix |
f"{255:#x}" |
'0xff' |
.2e |
Scientific notation | f"{1234567:.2e}" |
'1.23e+06' |
= |
Shows the expression and its value | f"{2 + 3 = }" |
'2 + 3 = 5' |
!r |
Shows the repr (strings get quotes) | f"{'hi'!r}" |
"'hi'" |
{w} inside a spec |
Takes the width from a variable | f"{'hi':>{4}}" |
' hi' |
Summary
{value:spec}formats a value inside an f-string..2fsets decimal places,,adds thousands separators, and%shows a percentage.- A width plus
<,>or^aligns text, and a fill character goes before the alignment. 05pads with zeros, and+always shows the sign.{expr=}prints the expression and its value, which is handy for debugging.str.format()and%are older styles with the same specs.