Building dynamic output strings is essential for user messages, reports, and data display. Python provides multiple formatting approaches: legacy %-formatting, the str.format() method, and modern f-strings. Understanding all three helps you read existing code and choose the best tool for each situation.

Old-style formatting (%)

The original C-style formatting using the % operator.

old_style.py
# Old-style % formatting

name = "Alice"
age = 30
height = 5.8

# Basic formatting
print("Name: %s" % name)
print("Age: %d" % age)
print("Height: %.1f" % height)

# Multiple values (tuple)
print("Name: %s, Age: %d" % (name, age))

# Format specifiers
print("Integer: %d" % 42)
print("Float: %f" % 3.14159)
print("Scientific: %e" % 1000000)
print("Percentage: %.2f%%" % 95.5)

# Width and precision
print("Padded: %10s" % "test")
print("Number: %05d" % 42)
print("Decimal: %8.2f" % 3.14159)

Use %s for strings, %d for integers, %f for floats. Pass values as tuple.

old_style_formatting Legacy %-based string formatting using format specifiers like %s and %d.

str.format() method

Flexible formatting with positional and named placeholders.

str_format.py
# str.format() method

name = "Bob"
age = 25
balance = 1234.56

# Positional arguments
print("Name: {}, Age: {}".format(name, age))

# Indexed arguments
print("Age: {1}, Name: {0}".format(name, age))

# Named arguments
print("Name: {n}, Age: {a}".format(n=name, a=age))

# Mixed
print("{0} is {1} years old. {0} has ${2:.2f}".format(name, age, balance))

# From dictionary
data = {'name': 'Charlie', 'age': 35}
print("Name: {name}, Age: {age}".format(**data))

# Attribute access
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

person = Person("David", 40)
print("Name: {p.name}, Age: {p.age}".format(p=person))

Use {} placeholders with .format(). Supports indexing and named arguments.

str_format The str.format() method with positional and named placeholders.

F-strings (modern)

Inline expressions directly in string literals using f-prefix.

fstring.py
# F-strings (formatted string literals)

name = "Eve"
age = 28
balance = 5432.10

# Basic f-string
print(f"Name: {name}, Age: {age}")

# Expressions inside f-strings
print(f"Next year {name} will be {age + 1}")
print(f"Double balance: ${balance * 2:.2f}")

# Method calls
text = "hello world"
print(f"Uppercase: {text.upper()}")
print(f"Title case: {text.title()}")

# Calculations
x, y = 10, 20
print(f"{x} + {y} = {x + y}")
print(f"{x} * {y} = {x * y}")

# Multiple lines
message = (
    f"User Profile:\n"
    f"  Name: {name}\n"
    f"  Age: {age}\n"
    f"  Balance: ${balance:.2f}"
)
print(message)

Prefix string with f, embed expressions in {braces}. Most readable approach.

fstring Formatted string literals with embedded expressions using f-prefix.

Number formatting

Format specifiers for decimal places, separators, and number bases.

number_format.py
# Number formatting with f-strings

pi = 3.14159265359
num = 

# Decimal places
print(f"Pi: {pi:.2f}")      # 2 decimals
print(f"Pi: {pi:.4f}")      # 4 decimals

# Width and alignment
print(f"Pi: {pi:10.2f}")    # Width 10, 2 decimals
print(f"Pi: {pi:012.2f}")   # Zero-padded

# Thousands separator
print(f"Number: {num:,}")
print(f"Number: {num:_}")   # Underscore separator

# Percentage
ratio = 0.756
print(f"Ratio: {ratio:.1%}")    # 75.6%
print(f"Ratio: {ratio:.2%}")    # 75.60%

# Scientific notation
big_num = 1234567890
print(f"Scientific: {big_num:e}")
print(f"Scientific: {big_num:.2e}")

# Binary, octal, hex
value = 42
print(f"Binary: {value:b}")
print(f"Octal: {value:o}")
print(f"Hex: {value:x}")
print(f"Hex (uppercase): {value:X}")
# Number formatting with f-strings

pi = 3.14159265359
num = 

# Decimal places
print(f"Pi: {pi:.2f}")      # 2 decimals
print(f"Pi: {pi:.4f}")      # 4 decimals

# Width and alignment
print(f"Pi: {pi:10.2f}")    # Width 10, 2 decimals
print(f"Pi: {pi:012.2f}")   # Zero-padded

# Thousands separator
print(f"Number: {num:,}")
print(f"Number: {num:_}")   # Underscore separator

# Percentage
ratio = 0.756
print(f"Ratio: {ratio:.1%}")    # 75.6%
print(f"Ratio: {ratio:.2%}")    # 75.60%

# Scientific notation
big_num = 1234567890
print(f"Scientific: {big_num:e}")
print(f"Scientific: {big_num:.2e}")

# Binary, octal, hex
value = 42
print(f"Binary: {value:b}")
print(f"Octal: {value:o}")
print(f"Hex: {value:x}")
print(f"Hex (uppercase): {value:X}")
# Number formatting with f-strings

pi = 3.14159265359
num = 

# Decimal places
print(f"Pi: {pi:.2f}")      # 2 decimals
print(f"Pi: {pi:.4f}")      # 4 decimals

# Width and alignment
print(f"Pi: {pi:10.2f}")    # Width 10, 2 decimals
print(f"Pi: {pi:012.2f}")   # Zero-padded

# Thousands separator
print(f"Number: {num:,}")
print(f"Number: {num:_}")   # Underscore separator

# Percentage
ratio = 0.756
print(f"Ratio: {ratio:.1%}")    # 75.6%
print(f"Ratio: {ratio:.2%}")    # 75.60%

# Scientific notation
big_num = 1234567890
print(f"Scientific: {big_num:e}")
print(f"Scientific: {big_num:.2e}")

# Binary, octal, hex
value = 42
print(f"Binary: {value:b}")
print(f"Octal: {value:o}")
print(f"Hex: {value:x}")
print(f"Hex (uppercase): {value:X}")

Use :.2f for decimals, :, for thousands, :b/:x/:o for binary/hex/octal.

number_format Format specifiers for decimal places, thousands separators, and number bases.

Alignment and padding

Control width, alignment, and fill characters for formatted output.

alignment.py
# String alignment and padding

text = "Python"
num = 42

# Left align (default for strings)
print(f"'{text:<15}'")

# Right align (default for numbers)
print(f"'{text:>15}'")

# Center align
print(f"'{text:^15}'")

# Custom fill character
print(f"'{text:*<15}'")
print(f"'{text:->15}'")
print(f"'{text:=^15}'")

# Number alignment
print(f"'{num:<10}'")
print(f"'{num:>10}'")
print(f"'{num:^10}'")

# Table formatting
print("\nTable Example:")
print(f"{'Name':<12} {'Age':>5} {'Balance':>10}")
print(f"{'-'*12} {'-'*5} {'-'*10}")
print(f"{'Alice':<12} {30:>5} {1234.56:>10.2f}")
print(f"{'Bob':<12} {25:>5} {987.65:>10.2f}")
print(f"{'Charlie':<12} {35:>5} {5432.10:>10.2f}")

Use < left, > right, ^ center. Add fill character before alignment.

alignment Width, alignment, and fill characters for formatted output.

Practical example

Combine formatting techniques to build formatted reports.

practical.py
# Practical example: Report generation

from datetime import datetime

class Transaction:
    def __init__(self, date, description, amount):
        self.date = date
        self.description = description
        self.amount = amount

# Sample data
transactions = [
    Transaction(datetime(2026, 1, 15), "Grocery Store", -45.67),
    Transaction(datetime(2026, 1, 18), "Salary Deposit", 2500.00),
    Transaction(datetime(2026, 1, 20), "Electric Bill", -87.50),
    Transaction(datetime(2026, 1, 22), "Restaurant", -32.40),
    Transaction(datetime(2026, 1, 25), "Online Purchase", -156.89),
]

# Generate report
print("=" * 70)
print(f"{'BANK STATEMENT':^70}")
print("=" * 70)
print()

print(f"{'Date':<12} {'Description':<25} {'Amount':>15}")
print("-" * 70)

balance = 1000.00
for txn in transactions:
    balance += txn.amount
    date_str = txn.date.strftime("%Y-%m-%d")
    
    # Color-code positive/negative (using symbols)
    sign = "+" if txn.amount >= 0 else "-"
    amount_str = f"{sign}${abs(txn.amount):.2f}"
    
    print(f"{date_str:<12} {txn.description:<25} {amount_str:>15}")

print("-" * 70)
print(f"{'FINAL BALANCE:':<37} ${balance:>15,.2f}")
print("=" * 70)

# Summary statistics
total_deposits = sum(t.amount for t in transactions if t.amount > 0)
total_withdrawals = sum(t.amount for t in transactions if t.amount < 0)

print()
print("SUMMARY")
print(f"  Total Deposits:    ${total_deposits:>10,.2f}")
print(f"  Total Withdrawals: ${abs(total_withdrawals):>10,.2f}")
print(f"  Net Change:        ${total_deposits + total_withdrawals:>10,.2f}")

Exercise: practical.py

Create a formatted receipt with aligned columns, currency formatting, and totals