Skip to content

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.

quickstart.py
name = "Ada"
age = 36
print(f"{name} is {age}")          # Ada is 36
print(f"Next year: {age + 1}")     # Next year: 37  (any expression works)
quickstart.py
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.

expressions.py
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'}")  # even
expressions.py
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'}")  # even

The 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:

spec.txt
{value:[fill][align][sign][width][,][.precision][type]}
spec.txt
{value:[fill][align][sign][width][,][.precision][type]}

Number precision

precision.py
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.3333
precision.py
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.3333

Width, alignment, and fill

SpecMeaning
:<10:<10Left-align in width 10.
:>10:>10Right-align in width 10.
:^10:^10Center in width 10.
:*^10:*^10Center, padding with **.
:08:08Zero-pad to width 8.
align.py
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***
align.py
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

numbers.py
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)
numbers.py
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

TypeBase
:b:bBinary
:o:oOctal
:x:x / :X:XHex (lower/upper)
:e:eScientific notation
bases.py
n = 255
print(f"{n:b}")    # 11111111
print(f"{n:o}")    # 377
print(f"{n:x}")    # ff
print(f"{12345.678:.2e}")   # 1.23e+04
bases.py
n = 255
print(f"{n:b}")    # 11111111
print(f"{n:o}")    # 377
print(f"{n:x}")    # ff
print(f"{12345.678:.2e}")   # 1.23e+04

The == debug specifier (3.8+)

Add == inside the braces to print both the expression and its value — perfect for quick debugging.

debug.py
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)
debug.py
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.

conversions.py
name = "Ada"
print(f"{name!r}")    # 'Ada'   (repr -> shows quotes)
print(f"{name!s}")    # Ada     (str)
conversions.py
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.

nesting.py
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 variable
nesting.py
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 variable

Multiline f-strings and escaping braces

multiline_escape.py
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 Ada
multiline_escape.py
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 Ada

Common 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!a for conversions, nest {...}{...} for dynamic specs, and double {{ }}{{ }} for literal braces.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did