Python f-strings: formatting that reads like text
Formatted string literals (PEP 498, Python 3.6+) embed expressions inside string literals with `{expr}` syntax, with optional format specs after a colon. Faster and more readable than %-formatting or .format().
Python f-strings, introduced in Python 3.6 (PEP 498), are string literals prefixed with f (or F) that allow embedded expressions inside curly braces. They evaluate at runtime and format inline.
Basic usage
name = "Kevin"
count = 3
msg = f"{name} has {count} active sessions."
# 'Kevin has 3 active sessions.'
Anything between { and } is a Python expression — not just a variable name:
items = [1, 2, 3]
print(f"Total: {sum(items)}") # Total: 6
print(f"Count: {len(items) * 2}") # Count: 6
Format specifiers — after the colon
A : inside the braces introduces a format spec, identical to the one used by format():
pi = 3.14159265
f"{pi:.2f}" # '3.14' — fixed-point, 2 decimal places
f"{pi:.4e}" # '3.1416e+00' — scientific notation
f"{1234567:,}" # '1,234,567' — thousands separators
f"{0.875:.1%}" # '87.5%' — percent with 1 decimal
f"{'hi':>10}" # ' hi' — right-aligned in 10 chars
f"{'hi':*^10}" # '****hi****' — center-aligned, * fill
Self-documenting = (Python 3.8+)
Adding = after the expression prints both the expression text and its value — invaluable for debugging:
x = 42
y = 7
print(f"{x=}, {y=}, {x*y=}")
# x=42, y=7, x*y=294
Multi-line and triple-quoted
f-strings work with triple quotes for multi-line templates:
report = f"""
User: {user.name}
Last seen: {user.last_seen:%Y-%m-%d %H:%M}
Sessions: {user.session_count}
"""
When to avoid f-strings
- Logging — use
logger.info("User %s logged in", username)instead oflogger.info(f"User {username} logged in"). The lazy%sstyle only formats if the log level is enabled; the f-string always evaluates. - SQL queries — never build SQL with f-strings (
f"SELECT * FROM users WHERE id = {user_id}"). That's an injection vulnerability. Use parameterized queries with?or%splaceholders. - User-facing strings that need translation — gettext can't extract f-string interpolations the way it can with
_("Hello %(name)s").
Performance note
f-strings are typically faster than both "...".format(...) and "%s ..." % (...) because the interpreter compiles them to direct calls instead of running a format-spec parser at runtime. For hot loops generating lots of strings, prefer them.