Overview

Python has four string formatting mechanisms. F-strings (3.6+) are the default choice for new code: they are readable, fast, and evaluated at the call site. Use str.format when the template lives in a variable or config file; use format_map when the values come from a dict and missing keys should raise clearly. Avoid percent-style (%) in new code; it survives in logging calls for lazy evaluation. This card covers syntax, specifiers, and the non-obvious edges.

F-string syntax

F-strings are string literals prefixed with f or F. Expressions inside {} are evaluated at runtime.

PatternExampleOutput
Simple variablef"{name}""Alice"
Expressionf"{2 + 2}""4"
Method callf"{name.upper()}""ALICE"
Attributef"{obj.value}"attribute value
Dict accessf"{d['key']}"dict value (use different quote inside)
Conditionalf"{'yes' if ok else 'no'}"conditional value
Self-documenting (3.8+)f"{value=}""value=42"
Nested f-stringf"{f'{x:.{prec}f}'}"dynamic precision
Multilinef"line1\n{var}"newline is literal \n not backslash

Backslash escapes are not allowed inside {} in Python < 3.12. Use a variable for complex expressions to stay readable.

str.format and format_map

Use str.format when the template is a runtime string. Use format_map to pass a mapping and allow custom __missing__ behavior.

PatternExampleNotes
Positional"{0} {1}".format(a, b)Index order explicit
Keyword"{x} {y}".format(x=1, y=2)Readable, order-independent
Reuse positional"{0} {0}".format(a)Same arg twice
Dict unpack"{x}".format(**d)Unpack a dict
format_map"{x}".format_map(d)Avoids copy; supports __missing__
Attribute access"{0.name}".format(obj)Dot access inside template
Index access"{0[key]}".format(d)Bracket access inside template

format_map with a defaultdict or custom class lets you silently skip missing keys, useful for partial template rendering.

Format specifiers

The format spec goes after : inside {}. The grammar is [[fill]align][sign][z][#][0][width][grouping][.precision][type].

SpecifierMeaningExampleOutput
dInteger decimalf"{42:d}""42"
fFloat fixed-pointf"{3.14159:.2f}""3.14"
eScientific notationf"{12345.6:.2e}""1.23e+04"
gGeneral (auto e/f)f"{0.00012:.2g}""0.00012"
%Percentagef"{0.42:.1%}""42.0%"
sStringf"{'hi':>10s}"" hi"
bBinaryf"{10:b}""1010"
x / XHex lower/upperf"{255:x}""ff"
oOctalf"{8:o}""10"
nLocale-aware numberf"{1000000:n}"locale-dependent
,Thousands separatorf"{1000000:,}""1,000,000"
_Underscore separatorf"{1000000:_}""1_000_000"
08dZero-pad width 8f"{42:08d}""00000042"
>10Right-align width 10f"{'hi':>10}"" hi"
<10Left-align width 10f"{'hi':<10}""hi "
^10Center width 10f"{'hi':^10}"" hi "
+dForce signf"{42:+d}""+42"
#xHex with prefixf"{255:#x}""0xff"

Dynamic width and precision: f"{value:{width}.{prec}f}" where width and prec are variables.

Percent-style formatting

Percent-style predates str.format. Retain it only in logging calls where lazy evaluation avoids formatting strings that are never emitted.

PatternExampleNotes
%s"Hello %s" % nameCalls str()
%d"Value: %d" % nInteger
%f"%.2f" % 3.14Float fixed
%r"%r" % objCalls repr()
Named"%(key)s" % dDict lookup
Logginglog.info("x=%s", val)Lazy; do not use f"x={val}" here

Never use percent-style for user-facing output in new Python 3 code. Use f-strings or str.format.

Datetime and custom __format__

format() calls __format__ on any object. Datetime objects accept strftime-style codes directly.

PatternExampleOutput
Datef"{dt:%Y-%m-%d}""2026-05-14"
Timef"{dt:%H:%M:%S}""09:30:00"
Custom objectformat(obj, "spec")Calls obj.__format__("spec")
!r conversionf"{obj!r}"repr(obj)
!s conversionf"{obj!s}"str(obj)
!a conversionf"{obj!a}"ascii(obj)

Implement __format__ on domain objects to make them directly embeddable in f-strings with custom specs.

Common gotchas

  • F-strings evaluate at definition time, not call time; wrapping in a lambda or function defers evaluation.
  • Single-quoted strings inside {} conflict with single-quoted f-strings in Python < 3.12. Use " outside or ''' to wrap.
  • format on a float rounds to the display precision but does not change the stored value. Use round() if you need the rounded number.
  • % with a single argument that is a tuple requires an extra wrap: "%s" % (value,), not "%s" % value when value is itself a tuple.
  • format_map does not copy the mapping; mutations to the source dict during formatting are visible.
  • Locale-aware specifiers (n) depend on locale.setlocale. Prefer , or _ for portable code.