Technology  /  Python

๐Ÿ Python 78 guides ยท updated 2026

From first variable to OOP, generators, and real projects โ€” the language that runs everything from data pipelines to AI agents, taught the practical way.

String Formatting in Python: A Working Guide to f-strings and Their Alternatives

Python has three main string formatting mechanisms: the % operator (legacy), str.format() (Python 2.6+), and f-strings (Python 3.6+). All three appear in real codebases. Knowing all of them helps you read older code and make informed choices for new code.

f-strings โ€” The Modern Default

f-strings (formatted string literals) evaluate expressions directly inside {}. They are the fastest option and the most readable for most cases.

name = "Alice"
age = 30
balance = 12345.678
# Basic variable insertion
print(f"Name: {name}, Age: {age}")
# Name: Alice, Age: 30
# Any expression works inside {}
print(f"Next year: {age + 1}")
print(f"Upper: {name.upper()}")
print(f"Length: {len(name)}")
# Number formatting with format spec
print(f"Balance: ${balance:,.2f}") # $12,345.68 โ€” comma + 2 decimal places
print(f"Percent: {0.876:.1%}") # 87.6%
print(f"Padded: {name:>10}") # ' Alice' โ€” right-align in 10 chars
print(f"Padded: {name:<10}|") # 'Alice |' โ€” left-align
print(f"Padded: {name:^10}|") # ' Alice |' โ€” center

Format Spec Mini-Language

The format spec after : is the same for f-strings and str.format():

value = 3.14159265
# Decimal places
print(f"{value:.2f}") # 3.14
print(f"{value:.5f}") # 3.14159
# Scientific notation
print(f"{value:.2e}") # 3.14e+00
# Integer formatting
n = 255
print(f"{n:d}") # 255 โ€” decimal
print(f"{n:b}") # 11111111 โ€” binary
print(f"{n:o}") # 377 โ€” octal
print(f"{n:x}") # ff โ€” hex lowercase
print(f"{n:X}") # FF โ€” hex uppercase
print(f"{n:08b}") # 11111111 โ€” zero-padded to 8 chars
# Alignment and fill
print(f"{'left':<10}|") # 'left |'
print(f"{'right':>10}|") # ' right|'
print(f"{'center':^10}|") # ' center |'
print(f"{'fill':*^10}|") # '**fill****|' โ€” fill with *

str.format() โ€” Positional and Keyword Arguments

str.format() is more verbose but more portable (works in Python 2.6+ if you ever encounter it):

# Positional
print("Hello, {}! You are {} years old.".format("Bob", 25))
# Hello, Bob! You are 25 years old.
# Named
print("Name: {name}, Role: {role}".format(name="Carol", role="admin"))
# Reuse the same argument
print("{0} + {0} = {1}".format(5, 10))
# 5 + 5 = 10
# Format spec works the same way
price = 49.99
print("Price: ${:.2f}".format(price)) # Price: $49.99
# From a dict
data = {"city": "London", "temp": 18.5}
print("Weather in {city}: {temp:.1f}ยฐC".format(**data))
# Weather in London: 18.5ยฐC

% Formatting โ€” Legacy Style

You will see this in older code and in logging calls (where it has a specific performance advantage โ€” the string is only formatted if the log level is active).

name = "Dave"
age = 40
score = 98.765
# %s = string, %d = integer, %f = float
print("Name: %s, Age: %d" % (name, age))
print("Score: %.2f%%" % score) # doubled % to escape it
# Score: 98.77%
# Named placeholders
print("%(name)s is %(age)d years old." % {"name": name, "age": age})
# Logging โ€” % formatting deferred until the message is actually emitted
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("User %s logged in from %s", name, "192.168.1.1")

The logging module intentionally uses % formatting so that string interpolation is skipped when the log message will not be emitted (e.g. debug messages in production).

Multi-line f-strings

name = "Alice"
items = ["widget", "gadget", "thingamajig"]
total = 157.50
receipt = (
f"Customer: {name}\n"
f"Items: {len(items)}\n"
f"Total: ${total:.2f}\n"
f"Thank you for your purchase!"
)
print(receipt)
# Triple-quoted f-string
report = f"""
Name: {name}
Items: {', '.join(items)}
Total: ${total:.2f}
"""
print(report.strip())

Debugging with f-string = (Python 3.8+)

The = specifier prints both the expression and its value:

x = 42
y = [1, 2, 3]
print(f"{x=}") # x=42
print(f"{y=}") # y=[1, 2, 3]
print(f"{len(y)=}") # len(y)=3
print(f"{x * 2 + 1=}") # x * 2 + 1=85

This is much faster to type than print(f"x = {x}") and ensures the label always matches the variable name.

Choosing the Right Method

MethodPython versionReadabilitySpeedBest for
f-string3.6+HighestFastestNew code, most situations
str.format()2.6+MediumMediumTemplate strings, compatibility
% formattingAllLowSlowest*Legacy code, logging calls

*For logging, % is actually preferred because the formatting is lazy โ€” the string is only built if the log is actually emitted.

Use f-strings by default. Use str.format() when you need to separate the template from the values (e.g. loading format strings from config). Leave % formatting in old code unless you are refactoring the whole module.