Kodewick
Lesson 24 of 31Contents

String formatting

Lesson 24 of 31 · Part 4 · Code and data · about 15 minutes

You have used f-strings since lesson 2, and :.2f since lesson 3. That colon opens a small language of its own: the format specifier. With it you choose decimals, separators, widths, alignment and more, without changing the value itself. This lesson goes through it piece by piece and ends with a neat table.

The format specifier

Inside the curly braces, the value comes first and the specifier comes after a colon: {value:spec}. The specifier only changes how the value is shown.

main.py
1price = 1234567.8912print(f"{price}")3print(f"{price:.2f}")4print(f"{price:.0f}")5print(f"{price:,.2f}")6print(f"{price:_.2f}")
Output
1234567.891
1234567.89
1234568
1,234,567.89
1_234_567.89

.2f means "fixed point, two decimals". A comma before it adds a thousands separator; an underscore does the same with _. The separators also work on whole numbers: f"{5000000:,}" gives 5,000,000.

A % at the end turns a fraction into a percentage: it multiplies by 100, adds the sign, and takes the decimals you ask for.

main.py
1done = 72total = 83print(f"{done / total:%}")4print(f"{done / total:.1%}")5print(f"{done / total:.0%}")
Output
87.500000%
87.5%
88%
Guess first

What does this print?

1print(f"{2 / 3:.0%}")

Width, alignment and fill

A number in the specifier is a width: the smallest number of characters to use. Shorter values get padded with spaces. The vertical bars below show where each value starts and ends.

main.py
1print(f"|{'oil':8}|")2print(f"|{42:8}|")3print(f"|{'oil':>8}|")4print(f"|{'oil':^8}|")5print(f"|{42:<8}|")6print(f"|{'lighthouse':4}|")
Output
|oil     |
|      42|
|     oil|
|  oil   |
|42      |
|lighthouse|

Text is left-aligned by default, numbers right-aligned. <, > and ^ choose left, right and center. The width is only a minimum: 'lighthouse' is longer than 4, so it is shown whole.

Put any character in front of the alignment sign and it is used instead of spaces. That is the fill character.

main.py
1print(f"{'Stock':*^15}")2print(f"{'oil':.<12}340")3print(f"{7:0>3}")4print(f"{7:03}")5print(f"{-7:05}")
Output
*****Stock*****
oil.........340
007
007
-0007

0>3 pads with zeros on the left. For numbers there is a shortcut: a 0 right before the width, as in 03, pads with zeros and keeps a minus sign in front.

Signs and other bases

By default only negative numbers get a sign. + shows a sign for both; a space leaves room for one, so positive and negative numbers line up.

main.py
1print(f"{5:+} {-5:+}")2print(f"{5: } {-5: }")3print(f"{255:b} {255:o} {255:x} {255:X}")4print(f"{255:#x} {255:#b}")5print(f"{5:08b}")6print(f"{1234567:.2e}")
Output
+5 -5
 5 -5
11111111 377 ff FF
0xff 0b11111111
00000101
1.23e+06
LetterShows the number as255 becomes
bbinary (base 2)11111111
ooctal (base 8)377
x / Xhexadecimal (base 16)ff / FF
escientific notation2.550000e+02

# adds the prefix Python uses in code (0x, 0b, 0o). The parts combine in a fixed order: fill and alignment, sign, #, 0, width, separator, .decimals, letter. 08b is "zero-pad to width 8, in binary".

A neat table

Give every column the same width and alignment in the header and in each row, and the columns line up. Text columns usually go left, number columns right.

main.py
1stock = [("lamps", 12, 1.5), ("oil", 340, 0.25), ("wicks", 5, 12.0)]2print(f"{'Item':<8}{'Qty':>6}{'Price':>8}")3print("-" * 22)4for name, qty, price in stock:5    print(f"{name:<8}{qty:>6}{price:>8.2f}")
Output
Item       Qty   Price
----------------------
lamps       12    1.50
oil        340    0.25
wicks        5   12.00

The header has no decimals, but uses the same widths: 8, 6 and 8. Each row adds .2f to the price so the decimal points line up too. Press Step to watch each row being built.

Tip: Width counts characters. Chinese, Japanese and Korean characters usually take two columns on screen, so a table with them may not line up even when the code is right.

Debugging with =

Add = after the expression and the f-string shows the expression's text, an equals sign, and its value. It is the quickest way to check what a variable holds.

main.py
1lamps = 122name = "Wick"3share = 2 / 34print(f"{lamps=}")5print(f"{name=}")6print(f"{lamps * 3 = }")7print(f"{share=:.2f}")
Output
lamps=12
name='Wick'
lamps * 3 = 36
share=0.67

Strings come out with quotes, so you can spot a stray space. Spaces around the = are kept in the output, and a specifier still goes after the colon.

Widths from variables

The width or the number of decimals can come from a variable: put it in its own curly braces inside the specifier.

main.py
1names = ["oil", "lamps", "lighthouse"]2w = max([len(n) for n in names])3for n in names:4    print(f"{n:>{w}}|")5digits = 36print(f"{3.14159:.{digits}f}")
Output
       oil|
     lamps|
lighthouse|
3.142

Here the widest name decides the width, so the table fits whatever names you put in the list.

The older styles

You will meet two older ways in other people's code. format() and str.format use the same specifiers as f-strings; % has its own, similar codes like %.2f and %5d.

main.py
1print(format(1234.5, ",.2f"))2print("{} has {} lamps".format("Wick", 12))3print("{0}, {1}, {0}".format("on", "off"))4print("{name:>6}|".format(name="oil"))5print("%s has %d lamps" % ("Wick", 12))6print("%.2f and %5d|" % (3.14159, 42))
Output
1,234.50
Wick has 12 lamps
on, off, on
   oil|
Wick has 12 lamps
3.14 and    42|
StyleExample
f-string (use this)f"{x:.2f}"
format() functionformat(x, ".2f")
str.format"{:.2f}".format(x)
% operator"%.2f" % x

format(value, spec) formats one value. str.format fills each {} in order, or by number or name. The % style is the oldest: %s for any value, %d for whole numbers, %.2f for decimals, and a tuple when there is more than one value. For new code, f-strings are the shortest and clearest.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

A big total

Show the total with a thousands separator and two decimals.

It should print
Total: $1,234,567.89
exercise1.py
1total = 1234567.8912print(f"Total: ${total}")
Output
Press Run or Ctrl+Enter
Exercise 2

Progress

Print Done: followed by how much of the work is done, as a percentage with one decimal.

It should print
Done: 87.5%
exercise2.py
1done = 72total = 83print(done, total)
Output
Press Run or Ctrl+Enter
Exercise 3

A starred heading

Center title in a line of stars. Take the width from width, so changing it changes the line.

It should print
***Stock***
exercise3.py
1title = "Stock"2width = 113print(title)
Output
Press Run or Ctrl+Enter

What you learned

  • {value:spec} changes only how a value is shown.
  • .2f sets decimals, , or _ adds thousands separators, % shows a percentage.
  • A number sets the width; < > ^ align; a character before them is the fill; 03 pads with zeros.
  • + or a space controls signs; b, o, x, e show other bases and scientific notation.
  • Same widths in the header and rows make a table line up.
  • f"{x=}" shows the expression and its value, handy for debugging.
  • {n:>{w}} takes the width from a variable.
  • format() and str.format are older styles with the same specifiers; % uses similar codes like %.2f.