PythonString Formatting (f-strings)

String Formatting (f-strings)

Building strings that mix literal text with variable values is one of the most common tasks in any program. Python has evolved through three formatting styles over the years: old-style % formatting, the .format() method, and modern f-strings. This tutorial covers all three, but focuses on f-strings — the style you should use in new code.

The Evolution of String Formatting

All three styles below produce the exact same output for the exact same input. f-strings, introduced in Python 3.6, are shorter, faster, and easier to read because the variable appears right where it is used.

Python
name = "Ada"
age = 36

# 1. % formatting (old-style, inherited from C)
print("My name is %s and I am %d years old." % (name, age))

# 2. .format() method (Python 2.6+)
print("My name is {} and I am {} years old.".format(name, age))

# 3. f-strings (Python 3.6+) - the modern standard
print(f"My name is {name} and I am {age} years old.")
Comparing the Three Styles

Style

Syntax Example

Notes

% formatting

"%s is %d" % (name, age)

Oldest style; error-prone with many values; still seen in legacy code and logging.

.format()

"{} is {}".format(name, age)

More flexible than %; supports named placeholders; more verbose than f-strings.

f-strings

f"{name} is {age}"

Fastest and most readable; expressions evaluated inline; the recommended default.

f-strings: Expressions Inside Curly Braces
Anything you put inside `` in an f-string is a real Python expression, evaluated at run time — not just a variable name. You can call functions, do arithmetic, or access attributes and items directly.

Python
price = 19.99
quantity = 3

print(f"Total: {price * quantity}")           # Total: 59.97
print(f"Upper: {name.upper()}")                # Upper: ADA
print(f"Sum: {2 + 2}, is it even? {2 + 2 == 4}")  # Sum: 4, is it even? True
Format Specs: Controlling the Output

Adding a colon followed by a format spec lets you control decimal precision, width, alignment, and thousands separators — the same mini-language used by .format().

Python
pi = 3.14159265
big_number = 1234567

print(f"{pi:.2f}")        # 3.14   -- 2 decimal places
print(f"{big_number:,}")  # 1,234,567 -- thousands separator
print(f"{42:>10}")        # "        42" -- right-align in a 10-char field
print(f"{42:<10}|")       # "42        |" -- left-align in a 10-char field
print(f"{42:^10}|")       # "    42    |" -- center-align in a 10-char field
print(f"{0.85:.1%}")      # 85.0%  -- format as a percentage
The Debug Specifier: f"{x=}"

Adding = right after an expression prints both the expression’s source text and its value — a small but very handy trick for quick debugging, added in Python 3.8.

Python
user_count = 42
print(f"{user_count=}")          # user_count=42
print(f"{user_count * 2=}")      # user_count * 2=84
Quotes and Multi-line f-strings

You can nest quotes inside an f-string as long as you alternate quote characters, and f-strings can span multiple lines just like regular triple-quoted strings.

Python
user = {"name": "Ada"}
print(f"Hello, {user['name']}!")   # nested quotes: outer double, inner single

report = f"""
Name:  {name}
Age:   {age}
Price: {price:.2f}
"""
print(report)
Note
In Python 3.12+, the restriction on reusing the same quote character inside an f-string was lifted, but sticking to alternating quote styles (as above) keeps your code compatible with older versions and easier to read.
.format() Placeholders (for Reference)

You will still encounter .format() in existing codebases. It supports positional and named placeholders, plus the same format-spec syntax as f-strings.

Python
print("{0} is {1} years old".format(name, age))     # positional
print("{n} is {a} years old".format(n=name, a=age))  # named
print("Pi is {:.3f}".format(pi))                      # format spec still applies
When to Use Each Style
  • Use f-strings for all new code — they are the clearest and fastest option.

  • .format() is still fine for cases where the template string is built separately from the values (e.g. loaded from a config file).

  • % formatting mostly survives in logging calls (logging.info("%s", value)) and old codebases; avoid it in new code.

Tip
Logging libraries often prefer `%s` placeholders passed as separate arguments (e.g. `logger.info("User %s logged in", username)`) rather than an f-string, because it defers the (potentially expensive) string formatting until the log record is actually emitted.