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:
| Part | Values | Effect |
|---|---|---|
| fill + align | any 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 | |
0 | zero-pad (numbers) — shorthand for fill=0, align== | |
| width | integer | minimum field width |
| grouping | , _ | thousands separator |
.precision | integer | decimals for f, significant digits for g, max chars for strings |
| type | d f e g % x X o b s c | how 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
.precisiontruncates strings. - The same quote inside the braces on 3.11 (
f"{d["k"]}"is aSyntaxErrorthere; used['k']). f"{x:,.2f}"versusf"{x:.2f,}"— grouping comes before precision.%formatting with a single tuple argument:"%s" % (1, 2)is aTypeError; wrap it((1, 2),).
Key takeaways
- f-strings evaluate expressions in braces;
!rfor repr,=for name=value,{{ }}for literal braces. - The spec is
[fill][align][sign][#][0][width][,][.precision][type]; numbers align right by default, strings left. .2fis the fixed-decimal spec;,groups thousands;%scales by 100;x/b/ochange the base.- Tables are the same specs applied to header and rows; widths can be variables.
str.formatis 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.75Number 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,567In this module: Strings and text
- String basics — an immutable sequence of characters
- Slicing and the string methods
- Formatting — f-strings and the format-spec mini-language (this lesson)
- Characters, code points, bytes and Unicode
- Parsing input — from lines and tokens to values
- Regular expressions — the re module
- Checkpoint — Strings and text
← Slicing and the string methods · Characters, code points, bytes and Unicode →