W3docs

Python String Formatting

Learn all three Python string formatting methods: % operator, str.format(), and f-strings, with examples for numbers, alignment, dates, and more.

String formatting lets you build strings dynamically by inserting variables, expressions, and computed values into a template. Python offers three distinct approaches — the legacy % operator, the versatile str.format() method, and the modern f-string syntax — each with its own strengths and appropriate use cases.

This chapter covers:

  • When to use each formatting method and why
  • The % operator (legacy, still common in logs and older codebases)
  • str.format() with positional and named placeholders
  • f-strings (the recommended modern approach)
  • Format specifiers: width, alignment, precision, fill, sign, and base
  • Formatting numbers, percentages, dates, and times
  • Common gotchas and edge cases

For a deep dive into f-strings specifically — including expressions, conditional logic, and Python 3.12 improvements — see the Python f-Strings chapter.

Which Method Should You Use?

MethodPython versionWhen to use it
% operator2.x / 3.xLegacy code, logging module, very simple substitutions
str.format()2.6+When you need reusable format strings or keyword arguments
f-strings3.6+New code — fastest, most readable, supports expressions inline

Unless you are maintaining Python 2 code or an older Python 3 codebase, use f-strings. They are evaluated at runtime, produce no extra call overhead, and keep the variable name next to where it appears in the string.

The % Operator (Legacy Formatting)

The % operator substitutes values into a format string using C-style conversion specifiers.

Common conversion specifiers:

SpecifierMeaning
%sString (calls str() on the value)
%dSigned integer
%fFloating-point number
%rrepr() of the value
%%A literal % character
python— editable, runs on the server

Width and Precision with %

You can control field width and decimal precision directly in the specifier:

pi = 3.14159265
print("%10.3f" % pi)   # right-aligned in a 10-char field, 3 decimal places
#      3.142
print("%-10.3f|" % pi) # left-aligned
# 3.142     |
print("%010.3f" % pi)  # zero-padded
# 000003.142

Gotcha: Single Values vs. Tuples

When substituting a single value, passing a tuple is the safe approach:

value = "hello"
# Risky — if value were itself a tuple, this would fail:
# print("Got: %s" % value)

# Safe:
print("Got: %s" % (value,))
# Got: hello

str.format() — Flexible Placeholder Formatting

The str.format() method replaced the % operator in Python 2.6+. It uses {} as placeholders and supports positional, indexed, and keyword arguments.

Positional and Indexed Placeholders

python— editable, runs on the server

Named (Keyword) Placeholders

python— editable, runs on the server

Named placeholders make format strings self-documenting and are especially useful when the same value appears multiple times or when the format string comes from a config file.

Reusing Format Strings

Because str.format() takes a string as input, you can store the template in a variable and reuse it:

template = "Hello, {name}! You have {count} new messages."
print(template.format(name="Alice", count=3))
print(template.format(name="Bob", count=0))
# Hello, Alice! You have 3 new messages.
# Hello, Bob! You have 0 new messages.

f-Strings — Modern Python Formatting

f-strings (formatted string literals) prefix the string with f or F and evaluate any expression inside {} at runtime. They are available from Python 3.6 onwards and are the recommended approach for new code.

name = "John"
age = 25
print(f"My name is {name} and I am {age} years old.")
# My name is John and I am 25 years old.

f-strings support any valid Python expression inside the braces — arithmetic, method calls, conditionals, and more:

items = ["apple", "banana", "cherry"]
print(f"Cart has {len(items)} items. First: {items[0].upper()}.")
# Cart has 3 items. First: APPLE.

x = -7
print(f"Absolute value: {abs(x)}")
# Absolute value: 7

See Python f-Strings for the full guide, including debugging with =, multiline f-strings, and Python 3.12 enhancements.

Format Specifiers: The Mini-Language

Both str.format() and f-strings use the same format specification mini-language inside the {} after a colon:

{[field_name]:[fill][align][sign][#][0][width][grouping][.precision][type]}

The table below shows the most useful options:

OptionCharacterEffect
Alignment< > ^Left, right, center
Fillany charFills padding (used with alignment and width)
Sign+ - Show + for positive; default minus only; space for positive
Prefix#0x for hex, 0o for octal, 0b for binary
Zero-pad0Pad with zeros instead of spaces
WidthintegerMinimum field width
Grouping, or _Thousands separator
Precision.nDecimal places (floats) or max chars (strings)
Typed f e g x o b %Integer, float, scientific, general, hex, octal, binary, percent

Alignment and Width

text = "hello"
print(f"{text:<10}|")   # left-aligned in 10-char field
# hello     |
print(f"{text:>10}|")   # right-aligned
#      hello|
print(f"{text:^10}|")   # centered
#   hello   |
print(f"{text:*^10}|")  # centered, filled with *
# **hello***|

The same alignment specifiers work with str.format(). Here is an example combining alignment and precision:

python— editable, runs on the server

Number Formatting

x = 123.456789

print(f"{x:.2f}")        # two decimal places
# 123.46

print(f"{x:,.2f}")       # thousands separator, two decimal places
# 123.46

print(f"{x:+.2f}")       # explicit plus sign for positive numbers
# +123.46

print(f"{x:10.2f}")      # right-aligned in a 10-char field
#     123.46

print(f"{x:<10.2f}|")    # left-aligned
# 123.46    |

print(f"{x:010.2f}")     # zero-padded to 10 characters
# 0000123.46

Large Numbers and Percentages

population = 8_100_000_000
print(f"{population:,}")    # comma separator
# 8,100,000,000

print(f"{population:_}")    # underscore separator (Python 3.6+)
# 8_100_000_000

ratio = 0.8567
print(f"{ratio:.1%}")       # percentage, one decimal place
# 85.7%

Integer Bases

n = 255
print(f"{n:d}")   # decimal (default)
# 255
print(f"{n:x}")   # lowercase hexadecimal
# ff
print(f"{n:X}")   # uppercase hexadecimal
# FF
print(f"{n:#x}")  # hex with 0x prefix
# 0xff
print(f"{n:o}")   # octal
# 377
print(f"{n:b}")   # binary
# 11111111
print(f"{n:#b}")  # binary with 0b prefix
# 0b11111111

You can also use the same specifiers with str.format():

python— editable, runs on the server

Scientific Notation

avogadro = 6.02214076e23
print(f"{avogadro:.3e}")   # scientific notation, 3 decimal places
# 6.022e+23

print(f"{avogadro:.3E}")   # uppercase E
# 6.022E+23

print(f"{avogadro:.3g}")   # general: compact form, removes trailing zeros
# 6.02e+23

Formatting Strings with format() and f-Strings

All three approaches produce the same output for a simple substitution:

python— editable, runs on the server

String-specific specifiers let you control truncation and padding:

s = "Python"
print(f"{s:.3}")      # truncate to 3 characters
# Pyt

print(f"{s:10}")      # pad to width 10 (left-aligned by default for strings)
# Python    

print(f"{s:>10}")     # right-aligned
#     Python

print(f"{s:*^12}")    # centered, filled with *
# ***Python***

Formatting Dates and Times

Use strftime format codes inside the braces when formatting datetime objects with f-strings or str.format():

import datetime

date = datetime.datetime(2024, 3, 15, 10, 30, 0)

# Default string representation
print(f"Default: {date}")
# Default: 2024-03-15 10:30:00

# strftime codes inside the format spec
print(f"Formatted: {date:%B %d, %Y}")
# Formatted: March 15, 2024

print(f"Time only: {date:%H:%M:%S}")
# Time only: 10:30:00

print(f"ISO-style: {date:%Y-%m-%d}")
# ISO-style: 2024-03-15

The same syntax works with str.format():

import datetime
date = datetime.datetime(2024, 3, 15, 10, 30, 0)
print("The date is {:%B %d, %Y}".format(date))
# The date is March 15, 2024

For advanced date formatting, see the Python Modify Strings chapter which covers string manipulation techniques.

Advanced Techniques

Nested Expressions in f-Strings

f-strings allow you to compute the format specifier dynamically:

width = 10
precision = 3
value = 3.14159

print(f"{value:{width}.{precision}f}")
# output:      3.142  (right-aligned in a 10-char field, 3 decimal places)

Format Spec with format() and Variables

The same technique works with str.format() using nested {}:

width = 8
print("{:{width}}".format("left", width=width))
# left    

Conditional Formatting in f-Strings

Since f-strings evaluate any expression, you can use conditional expressions inline:

score = 73
label = f"{'pass' if score >= 60 else 'fail'}"
print(f"Score {score}: {label}")
# Score 73: pass

Debugging with = (Python 3.8+)

Append = inside an f-string to print both the expression and its value — very useful for debugging:

x = 42
y = x * 2
print(f"{x=}, {y=}")
# x=42, y=84

Common Gotchas

1. Brace escaping. To include a literal { or } in any formatted string, double it:

print(f"Use {{curly braces}} in f-strings")
# Use {curly braces} in f-strings

2. Quotes inside f-strings. In Python 3.11 and earlier, the expression inside {} cannot use the same quote type as the outer f-string:

names = ["Alice", "Bob"]
# Wrong in Python <= 3.11:
# print(f"First: {names[0].upper()}")  -- this is fine
# print(f"{'Alice'.upper()}")          -- single quotes inside double-quoted f-string is fine
# But nesting the same quotes fails:
# print(f"{names["Alice"]}")           -- SyntaxError in <= 3.11

# Safe approach for <= 3.11:
key = "Alice"
print(f"{key.upper()}")
# ALICE

3. The % tuple trap. When using % formatting with a single value that is itself a tuple, wrap it:

coords = (10, 20)
# This raises TypeError because % sees a 2-element tuple:
# print("Position: %s" % coords)

# Fix: wrap in a 1-element tuple
print("Position: %s" % (coords,))
# Position: (10, 20)

4. str.format() vs. security. Never pass untrusted user input as a format string to str.format() — a malicious template can access object attributes and leak data. f-strings are always hard-coded strings, so they are not vulnerable to this.

Practical Comparison

Here is the same output produced by all three methods side by side:

item = "widget"
qty = 42
unit_price = 4.5

# % operator
print("%-12s %4d  $%7.2f" % (item, qty, unit_price))

# str.format()
print("{:<12} {:>4}  ${:>7.2f}".format(item, qty, unit_price))

# f-string
print(f"{item:<12} {qty:>4}  ${unit_price:>7.2f}")

# All print:
# widget         42  $   4.50

Summary

  • Use f-strings for new Python 3.6+ code: they are the most readable and fastest.
  • Use str.format() when you need a reusable template string or must support Python 2.6+.
  • Use % only in legacy code or when working with the logging module (which defers formatting until the log entry is actually emitted).
  • The format specification mini-language (width, precision, align, fill, type) applies the same way to both str.format() and f-strings.

Related chapters:

Practice

Practice
Which of the following are valid methods for formatting strings in Python, as described in the content of the specified URL?
Which of the following are valid methods for formatting strings in Python, as described in the content of the specified URL?
Was this page helpful?