What are f-strings, and how do they compare to `%`-formatting and `.format()`?
Quick Answer
f-strings (f"{value}", PEP 498) embed expressions directly inside string literals and are evaluated at the point the string is defined. They're the fastest and most readable option, and support format specs ({value:.2f}) and debugging output ({value=}). %-formatting is the oldest, printf-style approach; str.format() is more flexible than % but more verbose than f-strings. Modern Python code should default to f-strings.
Detailed Answer
The three formatting styles
name, score = "Ada", 97.456
# printf-style (%) -- oldest, C-inspired
"%s scored %.2f%%" % (name, score)
# str.format() -- more flexible, more verbose
"{} scored {:.2f}%".format(name, score)
# f-string (PEP 498) -- modern default
f"{name} scored {score:.2f}%"
All three ultimately support the same format spec mini-language
(:.2f, :>10, :,, :%). But f-strings embed the expression and the
spec directly in the literal, which is both shorter and lets your editor/
type checker see the actual expression being formatted.
Why f-strings are generally preferred
- Any expression is allowed inline, not just a variable name:
f"{price * 1.08:.2f}",f"{obj.method()}". - The
=debug specifier prints both the expression and its value:f"{score=}"→"score=97.456"— handy for quick debugging without writingprint(f"score: {score}")by hand. - Performance: f-strings are compiled to efficient bytecode
(essentially a sequence of
BUILD_STRINGoperations) and are generally faster than%or.format()at runtime. - Readability: the value appears exactly where it's used in the string, rather than being separated into an argument list you have to cross-reference by position.
When you'd still see % or .format()
%-formatting is still common in logging calls (logging.info("x=%s", x)), because the logging module only formats the string if the log level is enabled, deferring the cost. An f-string is evaluated immediately regardless of whether the log line is emitted..format()is useful when the template string itself is not known until runtime (for example, loaded from a config file or translation catalog), since f-strings must be literal source-code strings, not read from data.
f-strings are the modern default — inline
expressions, a debug = specifier, and better performance than % or
.format(). The main exception is logging calls, where lazy %-style
formatting avoids the cost of building a string that might never be
emitted.