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.
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
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? TrueFormat 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().
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 percentageThe 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.
user_count = 42
print(f"{user_count=}") # user_count=42
print(f"{user_count * 2=}") # user_count * 2=84Quotes 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.
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).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.
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 appliesWhen 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.