Python has three ways to format strings. Here's when to use each.

The modern way. Available since Python 3.6.

name = "Owen"
age = 25
 
# Basic
message = f"Hello, {name}!"
 
# Expressions
message = f"In 10 years: {age + 10}"
 
# Method calls
message = f"Name: {name.upper()}"
 
# Format specifiers
price = 19.99
message = f"Price: ${price:.2f}"

Format Specifiers

# Numbers
f"{42:05d}"      # "00042" (zero-padded)
f"{42:>5d}"      # "   42" (right-aligned)
f"{42:<5d}"      # "42   " (left-aligned)
f"{42:^5d}"      # " 42  " (centered)
 
# Floats
f"{3.14159:.2f}"  # "3.14"
f"{3.14159:.0f}"  # "3"
f"{1234.5:,.2f}"  # "1,234.50"
f"{0.25:.0%}"     # "25%"
 
# Scientific
f"{1234567:.2e}"  # "1.23e+06"

Debug Mode

x = 42
print(f"\{x=\}")  # "x=42"
 
name = "Owen"
print(f"\{name=\}")  # "name='Owen'"

str.format()

Older but still useful.

# Positional
"Hello, {}!".format("Owen")
 
# Named
"Hello, {name}!".format(name="Owen")
 
# Indexed
"{0} + {1} = {2}".format(1, 2, 3)
 
# From dict
data = {"name": "Owen", "age": 25}
"Name: {name}, Age: {age}".format(**data)

When format() Is Better

# Template stored separately
template = "Hello, {name}!"
message = template.format(name="Owen")
 
# Reusing placeholders
"{0} {1} {0}".format("a", "b")  # "a b a"

% Formatting (Legacy)

Old C-style. Still works but avoid in new code.

# Basic
"Hello, %s!" % "Owen"
 
# Multiple values
"Name: %s, Age: %d" % ("Owen", 25)
 
# Floats
"Price: %.2f" % 19.99
 
# Dict
"%(name)s is %(age)d" % {"name": "Owen", "age": 25}

Common Specifiers

%s  # String
%d  # Integer
%f  # Float
%x  # Hex
%o  # Octal
%%  # Literal %

Comparison

name = "Owen"
age = 25
 
# f-string (best)
f"Name: {name}, Age: {age}"
 
# format()
"Name: {}, Age: {}".format(name, age)
 
# % formatting (avoid)
"Name: %s, Age: %d" % (name, age)

Template Strings

For untrusted input (user-provided templates):

from string import Template
 
# Safe - won't execute code
t = Template("Hello, $name!")
t.substitute(name="Owen")
 
# Missing key raises
t.safe_substitute(name="Owen")  # Returns partial result

Use templates when the format string comes from users.

Multiline Strings

# f-string
message = f"""
Name: {name}
Age: {age}
Status: Active
"""
 
# Cleaner with parentheses
message = (
    f"Name: {name}\n"
    f"Age: {age}\n"
    f"Status: Active"
)

Performance

f-strings are fastest:

# Fastest
f"{name} is {age}"
 
# Slower
"{} is {}".format(name, age)
 
# Slowest
"%s is %d" % (name, age)

Difference is small but f-strings win.

My Rules

  1. Use f-strings for new code
  2. Use format() for templates stored as data
  3. Use Template for user-provided format strings
  4. Avoid % in new code
  5. Use {x=} for debugging

f-strings are the answer 95% of the time.

React to this post: