Python F-Strings Explained: Format Specs, Padding, Decimals

Python f-strings put expressions in braces, and a format spec after a colon sets width, alignment, padding, decimals and thousands separators. Plus str.format.

  • Course: Python study plan
  • Module: Strings and text
  • Kind: Lesson
  • Reading time: 14 min
  • Runtime: CPython 3.11

What is an f-string in Python?

An f-string, introduced in Python 3.6, is a string literal prefixed with f whose expressions inside braces are evaluated and inserted: f"{name} bought {n} items". After a colon, a format spec shapes the value — f"{x:.2f}" for two decimals, f"{n:05d}" for zero padding, f"{n:,}" for thousands separators — and f"{x=}" prints a name with its value.

Lesson

Every judged exercise ends with output that must match exactly, and almost every real program prints a table, a report or a message with values in it. Python's f-string does both: an expression inside braces, an optional conversion, and a format spec after a colon that controls width, alignment, padding, sign, grouping, precision and type. The spec is a small language shared by format(), str.format and f-strings, and this lesson learns it once. It ends with the older % style, which you will read in existing code and should not write.

f-strings

name, n, price = "Ada", 3, 2.5
print(f"{name} bought {n} items for {n * price}")   # any expression inside the braces
print(f"{name!r} {name!s} {name!a}")                # conversions: repr, str, ascii
print(f"{n=}, {price=}")                            # 3.8: name=value, for debugging
print(f"{'nested'}")                                # a different quote style inside (3.11)
print(f"{{literal braces}}")                        # doubled braces print once

The braces take an expression, so calls, indexing, arithmetic and conditional expressions all work — but keep them short; a complex expression belongs in a variable above the f-string. !r applies repr, which is how a string is printed with its quotes. Until 3.12 the same quote character as the enclosing string cannot appear inside the braces; use the other one.

The format spec

{value:spec} where spec is [[fill]align][sign][#][0][width][grouping][.precision][type], every part optional:

PartValuesEffect
fill + alignany char, then < > ^ =pad to the width: left, right, centre; = puts padding after the sign
sign+ - + shows a plus on positives; space leaves a space
#alternate form: 0x prefix, 0b, a decimal point on .0f
0zero-pad (numbers) — shorthand for fill=0, align==
widthintegerminimum field width
grouping, _thousands separator
.precisionintegerdecimals for f, significant digits for g, max chars for strings
typed f e g % x X o b s chow to render
f"{42:5d}"        # '   42'      width 5, right-aligned (numbers default right)
f"{42:<5d}|"      # '42   |'     left
f"{42:^7d}|"      # '  42   |'   centred
f"{42:05d}"       # '00042'      zero-padded
f"{-42:05d}"      # '-0042'
f"{42:+d}"        # '+42'
f"{1234567:,}"    # '1,234,567'
f"{1234567:_d}"   # '1_234_567'
f"{255:x} {255:X} {255:#x} {5:08b}"   # 'ff FF 0xff 00000101'
f"{'ab':>5}|"     # '   ab|'     strings default left; > forces right
f"{'ab':*^6}"     # '**ab**'     fill character then align
f"{'abcdef':.3}"  # 'abc'        precision truncates a string

The width is a minimum: a value wider than the field is printed whole. The width and precision can themselves be expressions: f"{x:{w}.{p}f}".

Floats

x = 3.14159
f"{x:.2f}"        # '3.14'       fixed, 2 decimals — the one every exercise uses
f"{x:8.3f}"       # '   3.142'   width 8
f"{x:e}"          # '3.141590e+00'
f"{x:.3g}"        # '3.14'       3 significant digits; switches to e-notation when large/small
f"{0.256:.1%}"    # '25.6%'      multiplies by 100
f"{2.0:.0f}"      # '2'
f"{1e6:,.2f}"     # '1,000,000.00'
f"{x}"            # '3.14159'    no spec: repr-like shortest form

.Nf rounds correctly to the nearest representable value (half-to-even on exact ties, but exact ties are rare in binary — Module 2). A float with no spec prints the shortest round-tripping form, which is never what a table wants; always give a precision.

Tables

rows = [("apple", 3, 0.5), ("watermelon", 1, 4.25)]
print(f"{'item':<12}{'qty':>4}{'price':>8}")
for name, qty, price in rows:
    print(f"{name:<12}{qty:>4}{price:>8.2f}")
item         qty   price
apple          3    0.50
watermelon     1    4.25

Column widths are decided once; the same specs apply to the header (strings) and the rows. When the widest value is only known after reading the data, compute w = max(len(r[0]) for r in rows) and use {name:<{w}}.

format() and str.format

format(value, spec) applies one spec to one value: format(255, "08b"). "{} and {}".format(a, b) is the pre-3.6 template style, still useful when the template is built separately from the values (a message loaded from a file, a format reused in a loop):

template = "{name:<10}{score:>5}"
for name, score in rows:
    print(template.format(name=name, score=score))

Positional {0}, named {name}, and attribute/index access {p.x}, {d[key]} all work inside str.format.

The % operator

"%s scored %d (%.2f%%)" % (name, score, pct) is the C-style formatting Python inherited; %s for anything, %d for integers, %f for floats, %% for a literal percent. It is what logging messages use (Module 15), because the formatting is deferred until the message is actually emitted, and it is everywhere in older code. Read it; write f-strings.

Pitfalls

  • A float printed without a precision.
  • f"{x:5}" for a string aligns left, for a number right; be explicit with < or >.
  • Expecting width to truncate: it does not; only .precision truncates strings.
  • The same quote inside the braces on 3.11 (f"{d["k"]}" is a SyntaxError there; use d['k']).
  • f"{x:,.2f}" versus f"{x:.2f,}" — grouping comes before precision.
  • % formatting with a single tuple argument: "%s" % (1, 2) is a TypeError; wrap it ((1, 2),).

Key takeaways

  • f-strings evaluate expressions in braces; !r for repr, = for name=value, {{ }} for literal braces.
  • The spec is [fill][align][sign][#][0][width][,][.precision][type]; numbers align right by default, strings left.
  • .2f is the fixed-decimal spec; , groups thousands; % scales by 100; x/b/o change the base.
  • Tables are the same specs applied to header and rows; widths can be variables.
  • str.format is for templates separated from values; % is legacy — read it, do not write it.

Common questions

How do I pad a number with zeros in Python?

Use the 0 flag and a width in a format spec: f"{42:05d}" gives 00042, and it is sign-aware, so f"{-42:05d}" gives -0042. For a string of digits, "42".zfill(5) does the same.

How do I align text in columns with f-strings?

Give each field a width and an alignment — < left, > right, ^ centre — as in f"{name:<12}{qty:>4}{price:>8.2f}". Numbers align right and strings left by default. The width is a minimum and never truncates, and it can come from a variable: f"{name:<{w}}".

How do I add thousands separators to a number in Python?

Put a comma in the format spec: f"{1234567:,}" gives 1,234,567, and f"{1e6:,.2f}" gives 1,000,000.00 — the grouping comes before the precision. An underscore, as in f"{n:_d}", groups with underscores instead.

What is the difference between f-strings, str.format and % formatting?

All three render values with the same kind of format specs. F-strings are the modern default. str.format suits a template kept apart from its values, such as one reused in a loop. The C-style % operator is legacy that you will read in older code and logging calls; write f-strings.

Why does an f-string with quotes inside raise SyntaxError?

Before Python 3.12, an expression inside the braces cannot use the same quote character as the enclosing string, so f"{d["k"]}" is a SyntaxError on Python 3.11. Use the other quote inside the braces: f"{d['k']}".

Exercises

Invoice table

Read n rows item qty price and print an aligned table: a header, one row per item, and a total line. Columns: item left-aligned in 10, qty right-aligned in 5, price and line total right-aligned in 10 with two decimals. The total line puts TOTAL in the item column and the grand total in the last column, with the middle columns blank.

Input: n, then n lines. Output: n + 2 lines.

2
apple 3 0.5
melon 1 4.25

prints

item        qty     price     total
apple         3      0.50      1.50
melon         1      4.25      4.25
TOTAL                          5.75

Number formats

For each integer print five renderings on one line separated by single spaces: decimal, lowercase hexadecimal, binary zero-padded to width 8, decimal zero-padded to width 5, and decimal with thousands separators.

Input: n, then n integers. Output: one line per integer.

2
255
1234567

prints

255 ff 11111111 00255 255
1234567 12d687 100101101011010000111 1234567 1,234,567

In this module: Strings and text

← Slicing and the string methods · Characters, code points, bytes and Unicode →