Python f-strings Deep Dive
f-strings (formatted string literals, PEP 498, Python 3.6+) are the fastest and most readable way to build strings. Prefix a string with ff and embed expressions in {}{}. This page goes deep into formatting; for the broader picture of %%, .format().format(), and templates, see String Formatting.
name = "Ada"
age = 36
print(f"{name} is {age}") # Ada is 36
print(f"Next year: {age + 1}") # Next year: 37 (any expression works)name = "Ada"
age = 36
print(f"{name} is {age}") # Ada is 36
print(f"Next year: {age + 1}") # Next year: 37 (any expression works)Expressions, not just variables
Anything that evaluates to a value can go inside the braces — arithmetic, method calls, indexing, conditionals.
items = ["a", "b", "c"]
print(f"{len(items)} items") # 3 items
print(f"upper: {'hi'.upper()}") # upper: HI
print(f"first: {items[0]}") # first: a
print(f"{'even' if 4 % 2 == 0 else 'odd'}") # evenitems = ["a", "b", "c"]
print(f"{len(items)} items") # 3 items
print(f"upper: {'hi'.upper()}") # upper: HI
print(f"first: {items[0]}") # first: a
print(f"{'even' if 4 % 2 == 0 else 'odd'}") # evenThe format spec mini-language
After a colon inside the braces comes a format spec that controls width, alignment, precision, and type. The general shape is:
{value:[fill][align][sign][width][,][.precision][type]}{value:[fill][align][sign][width][,][.precision][type]}Number precision
pi = 3.14159265
print(f"{pi:.2f}") # 3.14 (2 decimal places)
print(f"{pi:.0f}") # 3 (no decimals)
print(f"{1/3:.4f}") # 0.3333pi = 3.14159265
print(f"{pi:.2f}") # 3.14 (2 decimal places)
print(f"{pi:.0f}") # 3 (no decimals)
print(f"{1/3:.4f}") # 0.3333Width, alignment, and fill
| Spec | Meaning |
|---|---|
:<10:<10 | Left-align in width 10. |
:>10:>10 | Right-align in width 10. |
:^10:^10 | Center in width 10. |
:*^10:*^10 | Center, padding with **. |
:08:08 | Zero-pad to width 8. |
print(f"{'hi':<6}|") # hi |
print(f"{'hi':>6}|") # | hi -> right aligned
print(f"{'hi':^6}|") # | hi |
print(f"{42:08}") # 00000042
print(f"{'x':*^7}") # ***x***print(f"{'hi':<6}|") # hi |
print(f"{'hi':>6}|") # | hi -> right aligned
print(f"{'hi':^6}|") # | hi |
print(f"{42:08}") # 00000042
print(f"{'x':*^7}") # ***x***Thousands separators and percent
print(f"{1000000:,}") # 1,000,000
print(f"{1234.5:,.2f}") # 1,234.50
print(f"{0.1234:.1%}") # 12.3% (percent with 1 decimal)
print(f"{255:#x}") # 0xff (hex with prefix)print(f"{1000000:,}") # 1,000,000
print(f"{1234.5:,.2f}") # 1,234.50
print(f"{0.1234:.1%}") # 12.3% (percent with 1 decimal)
print(f"{255:#x}") # 0xff (hex with prefix)Number bases
| Type | Base |
|---|---|
:b:b | Binary |
:o:o | Octal |
:x:x / :X:X | Hex (lower/upper) |
:e:e | Scientific notation |
n = 255
print(f"{n:b}") # 11111111
print(f"{n:o}") # 377
print(f"{n:x}") # ff
print(f"{12345.678:.2e}") # 1.23e+04n = 255
print(f"{n:b}") # 11111111
print(f"{n:o}") # 377
print(f"{n:x}") # ff
print(f"{12345.678:.2e}") # 1.23e+04The == debug specifier (3.8+)
Add == inside the braces to print both the expression and its value — perfect for quick debugging.
x = 10
y = 3
print(f"{x=}") # x=10
print(f"{x + y=}") # x + y=13
print(f"{x / y=:.2f}") # x / y=3.33 (combine with a format spec)x = 10
y = 3
print(f"{x=}") # x=10
print(f"{x + y=}") # x + y=13
print(f"{x / y=:.2f}") # x / y=3.33 (combine with a format spec)Conversions: !r, !s, !a
Apply repr()repr(), str()str(), or ascii()ascii() before formatting with !r!r, !s!s, !a!a.
name = "Ada"
print(f"{name!r}") # 'Ada' (repr -> shows quotes)
print(f"{name!s}") # Ada (str)name = "Ada"
print(f"{name!r}") # 'Ada' (repr -> shows quotes)
print(f"{name!s}") # Ada (str)Nesting and dynamic specs
Braces can contain expressions that themselves come from variables — even the format spec.
value = 3.14159
places = 3
print(f"{value:.{places}f}") # 3.142 (precision taken from a variable)
width = 10
print(f"{'hi':>{width}}") # right-align in a width from a variablevalue = 3.14159
places = 3
print(f"{value:.{places}f}") # 3.142 (precision taken from a variable)
width = 10
print(f"{'hi':>{width}}") # right-align in a width from a variableMultiline f-strings and escaping braces
name, role = "Ada", "admin"
msg = (
f"User: {name}\n"
f"Role: {role}"
)
print(msg)
# To print literal braces, double them
print(f"{{not a placeholder}} but {name}") # {not a placeholder} but Adaname, role = "Ada", "admin"
msg = (
f"User: {name}\n"
f"Role: {role}"
)
print(msg)
# To print literal braces, double them
print(f"{{not a placeholder}} but {name}") # {not a placeholder} but AdaCommon pitfalls
- Forgetting the
ff—"{x}""{x}"prints the braces literally;f"{x}"f"{x}"substitutes. - Literal braces need doubling —
{{{{and}}}}to print{{and}}. - Quote clashes — don’t reuse the outer quote inside (or use the other quote):
f"{d['key']}"f"{d['key']}". - Heavy logic in braces hurts readability — compute first, then format.
Practice Exercises
Exercise 1 – Two decimal places
Exercise 2 – Thousands separator
Exercise 3 – The debug specifier
Summary
- f-strings embed any expression in
{}{}and are the fastest, clearest way to format strings. - The format spec (
:[fill][align][sign][width][,][.precision][type]:[fill][align][sign][width][,][.precision][type]) controls precision, padding, separators, and number bases. {expr=}{expr=}prints the expression and its value — handy for debugging.- Use
!r!r/!s!s/!a!afor conversions, nest{...}{...}for dynamic specs, and double{{ }}{{ }}for literal braces.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
