Python F-String Usage: Syntax, Formatting, and Debugging
Learn how to use Python f-strings for clean string interpolation, formatting values, and debugging expressions with practical examples.
Python f-strings, available since Python 3.6, let you embed expressions directly inside string literals. Prefix a string with f or F, put an expression in curly braces, and Python evaluates it at runtime. This makes common interpolation and formatting tasks shorter and easier to read than the older % formatting or the .format() method.
Basic f-string Syntax
The simplest f-string replaces a variable's value directly:
name = "Ada" print(f"Hello, {name}")
This prints Hello, Ada. Unlike % formatting or .format(), the placeholder and the value appear in the same line, so the code is easier to scan. The placeholder can contain any valid Python expression, not just a variable name.
Embedding Expressions and Calling Functions
Because f-strings evaluate full expressions, you can compute values or call methods directly inside the braces:
a = 5 b = 3 print(f"{a} + {b} = {a + b}") print(f"{name.upper()} has {len(name)} letters")
The first line outputs 5 + 3 = 8, and the second outputs ADA has 3 letters. This is useful when you want a quick formatted result without assigning intermediate variables. If an expression becomes complex, compute it beforehand for readability.
Format Specifiers for Alignment and Numbers
F-strings support Python's format specification mini-language, giving you control over alignment, width, precision, and type-specific formatting:
price = 1234.5678 print(f"Price: {price:.2f}") print(f"Aligned: {name:>10}") print(f"Hex: {255:#x}")
{price:.2f}formats the float with two decimal places.{name:>10}right-aligns the string in a 10-character field.{255:#x}displays the integer as a hexadecimal literal (0xff).
Date and time formatting also works directly on datetime objects:
from datetime import datetime now = datetime.now() print(f"{now:%Y-%m-%d %H:%M:%S}")
The part after the colon follows the same format-spec rules as str.format() and datetime.strftime(), so existing knowledge transfers directly.
Debugging with the = Specifier
The = specifier, added in Python 3.8, prints an expression and its value together:
x = 42 print(f"{x=}") # prints x=42 print(f"{a + b=}") # prints a+b=8
This works with any expression, including function calls. You can combine it with a format specifier, for example {x=:>10}, to control alignment while still showing both the expression and its value.
Dynamic Format Specifiers and Nested Replacement Fields
A format specifier can itself contain replacement fields, which makes the width or precision dynamic:
width = 10 print(f"{'text':>{width}}")
Here the expression is the string literal 'text', and the format spec is >{width}; width is evaluated when the f-string runs. This is sometimes described as nesting f-strings, but the important feature is the nested replacement field inside the format specifier.
You can also compute the width:
value = 123 def get_width(value): return len(str(value)) + 2 print(f"{value:{get_width(value)}}")
This produces a field just wide enough for the value plus two spaces. Nested replacement fields are convenient for dynamic output, but they can be harder to read. If the logic becomes complicated, build the format string separately or use another formatting approach.
Performance and Runtime Considerations
F-strings are evaluated at runtime, so the expressions inside braces run every time the string is created. For most code, this overhead is negligible. In a tight loop, repeated expression evaluation and formatting can add up; if profiling shows it matters, precompute the formatted string outside the loop or use a simpler construction.
Because an f-string's template is parsed as source code, it is not designed for fully dynamic format strings loaded from user input or configuration files. If you need to apply a stored template, use a templating mechanism such as string.Template, or str.format() when you control the template and understand its behavior.
Compatibility and Migration Notes
F-strings were introduced in Python 3.6. For code that must run on Python 3.5 or earlier, use % formatting or .format().
When migrating existing code to f-strings, keep these points in mind:
- Conversion flags such as
!s,!r, and!awork in f-strings, for example{value!r}. - Before Python 3.12, backslashes were not allowed inside the expression part of an f-string. Python 3.12 lifted that restriction, allowing expressions such as
f"{'\n'}". For older versions, assign the value to a variable first. - Replacing
.format()calls can be straightforward, but check the format spec and any conversion flags for subtle differences.
Using F-Strings in Logging and Exception Messages
F-strings are common in exception messages:
class ValidationError(Exception): def __init__(self, field, value): super().__init__(f"Invalid value for {field}: {value!r}")
The !r conversion shows the value with quotes, which is helpful when debugging.
In logging, avoid f-strings when you need lazy evaluation. The logging module supports %s placeholders with arguments, so the message is only formatted when the record is actually emitted:
import logging logger = logging.getLogger(__name__) logger.debug("Retrying %s after error: %s", url, error)
Using an f-string here would build the message even when the debug level is disabled.
F-strings are now a core part of Python string handling. The syntax is simple, the formatting options are powerful, and debugging features like = save time. Keep the compatibility constraints and runtime behavior in mind, and f-strings can make your string interpolation cleaner and easier to maintain.