Marcus Kazmierczak

Home mkaz.blog

Working With Python

Python String Formatting: Complete Guide

This comprehensive guide covers everything you need to know about Python string formatting, from basic f-strings to advanced formatting techniques. I started this as a quick reference and it has grown into a complete tutorial covering all Python string formatting methods.

Quick Start / TL;DR

  • Use f-strings (Python 3.6+) - Modern, fastest, most readable: f"Hello {name}"
  • Basic formatting: f"Value: {variable:.2f}" for 2 decimal places
  • Expressions allowed: f"Result: {x * y}" or f"Name: {name.upper()}"
  • Debugging: f"{variable=}" shows both name and value (Python 3.8+)
  • Avoid % formatting - Legacy method, use only for compatibility
# Most common usage patterns
name = "Alice"
score = 95.67
print(f"Hello {name}! Your score is {score:.1f}%")
# Output: Hello Alice! Your score is 95.7%

How Do I Format Strings in Python?

Python 3.6 introduced f-strings (formatted string literals) as the modern standard for string formatting. F-strings are prefixed with f or F and allow you to embed expressions inside curly braces {}.

Why f-strings are recommended: - Fastest performance among all formatting methods - Most readable and concise syntax - Support for expressions and method calls - Less error-prone than other methods

Here's a comparison of the three main formatting methods:

name = "Python"
version = 3.11

# F-strings (recommended)
message = f"Welcome to {name} {version}!"

# .format() method
message = "Welcome to {} {}!".format(name, version)

# % formatting (legacy)
message = "Welcome to %s %s!" % (name, version)

# All output: Welcome to Python 3.11!

Common Questions

What's the Difference Between F-strings, .format(), and % Formatting?

Method Python Version Performance Readability Use Case
f-strings 3.6+ Fastest Best Modern Python (recommended)
.format() 2.7+ Medium Good Legacy code, complex positioning
% formatting All Medium Poor Very old code, compatibility

Performance comparison (Python 3.11):

import timeit

name = "World"
# F-string: ~0.05 microseconds
t = timeit.timeit(lambda: f"Hello {name}!", number=1000000)
print("Time: {t:.6f} seconds")

# .format(): ~0.15 microseconds
t = timeit.timeit(lambda: "Hello {}!".format(name), number=1000000)
print("Time: {t:.6f} seconds")

# % formatting: ~0.12 microseconds
t = timeit.timeit(lambda: "Hello %s!" % name, number=1000000)
print("Time: {t:.6f} seconds")

How Do I Format Numbers in Python Strings?

F-strings support comprehensive number formatting using format specifications after a colon:

value = 1234.5678

# Basic decimal places
print(f"Two decimals: {value:.2f}")           # 1234.57
print(f"No decimals: {value:.0f}")            # 1235
print(f"With sign: {value:+.2f}")             # +1234.57

# Padding and alignment
print(f"Right aligned: {value:10.2f}")        #    1234.57
print(f"Left aligned: {value:<10.2f}")        # 1234.57
print(f"Center aligned: {value:^10.2f}")      #  1234.57
print(f"Zero padded: {value:010.2f}")         # 001234.57

# Thousands separator
print(f"With commas: {value:,.2f}")           # 1,234.57

# Percentage
ratio = 0.857
print(f"Percentage: {ratio:.1%}")             # 85.7%

# Scientific notation
big_number = 1500000
print(f"Scientific: {big_number:.2e}")        # 1.50e+06

# Different bases
num = 255
print(f"Hex: {num:x}")                        # ff
print(f"Binary: {num:b}")                     # 11111111
print(f"Octal: {num:o}")                      # 377

What Are the Most Common String Formatting Errors?

1. Empty f-string expression:

# Error
name = "Alice"
print(f"Hello {}")  # SyntaxError: f-string: empty expression not allowed

# Fix
print(f"Hello {name}")

2. Missing variables in format string:

# Error
print("Hello {name}".format())  # KeyError: 'name'

# Fix
print("Hello {name}".format(name="Alice"))
print(f"Hello {name}")  # f-strings catch this at runtime

3. Mismatched braces:

# Error
print(f"Value: {value")  # SyntaxError: f-string: unterminated string

# Fix
print(f"Value: {value}")

4. Quotes inside f-strings:

# Error (same quote type)
print(f"He said "Hello"")  # SyntaxError

# Fix (different quote types)
print(f'He said "Hello"')
print(f"He said 'Hello'")
print(f"He said \"Hello\"")  # escaped quotes

Number Formatting Reference

This comprehensive table shows various ways to format numbers using f-strings. All examples can be used with .format() method as well.

Usage: print(f"{NUMBER:FORMAT}") or print("{:FORMAT}".format(NUMBER))

Number Format Output Description
3.1415926 {:.2f} 3.14 Format float 2 decimal places
3.1415926 {:+.2f} +3.14 Format float 2 decimal places with sign
-1 {:+.2f} -1.00 Format float 2 decimal places with sign
2.71828 {:.0f} 3 Format float with no decimal places
5 {:0>2d} 05 Pad number with zeros (left padding, width 2)
5 {:x<4d} 5xxx Pad number with x's (right padding, width 4)
1000000 {:,} 1,000,000 Number format with comma separator
0.25 {:.2%} 25.00% Format percentage
1000000000 {:.2e} 1.00e+09 Exponent notation
13 {:10d}         13 Right aligned (default, width 10)
13 {:<10d} 13 Left aligned (width 10)
13 {:^10d}     13 Center aligned (width 10)

Format specification syntax: {value:[[fill]align][sign][#][0][width][,][.precision][type]}

  • fill: Character to pad with (default: space)
  • align: < (left), > (right), ^ (center), = (after sign)
  • sign: + (always), - (negative only), (space for positive)
  • width: Minimum field width
  • ,: Use comma as thousands separator
  • precision: Digits after decimal point
  • type: f (float), d (decimal), e (scientific), % (percentage)

How Do I Use F-string Expressions and Methods?

F-strings support more than just variable substitution - you can include expressions, method calls, and even complex calculations directly inside the braces.

Basic String Substitution

adj = "tasty"
noun = "burger"
s = f"hmmm, this is a {adj} {noun}"
print(s)
# Output: hmmm, this is a tasty burger

Mathematical Expressions

x, y = 15, 25
print(f"Sum: {x + y}")           # Sum: 40
print(f"Product: {x * y}")       # Product: 375
print(f"Average: {(x + y) / 2}") # Average: 20.0
print(f"Power: {x ** 2}")        # Power: 225

Method Calls and String Operations

name = "alice"
message = "hello world"

print(f"Title case: {name.title()}")           # Title case: Alice
print(f"Uppercase: {message.upper()}")         # Uppercase: HELLO WORLD
print(f"Word count: {len(message.split())}")   # Word count: 2
print(f"First letter: {name[0].upper()}")      # First letter: A

Advanced Expressions with Built-in Functions

numbers = [1, 2, 3, 4, 5]
text = "Python Programming"

print(f"Max: {max(numbers)}")              # Max: 5
print(f"Length: {len(text)}")              # Length: 18
print(f"Reversed: {text[::-1]}")           # Reversed: gnimmargorP nohtyP
print(f"Sum of squares: {sum(x**2 for x in numbers)}")  # Sum of squares: 55

F-string Debugging (Python 3.8+)

Use the = specifier to show both variable names and values - great for debugging:

user_id = 12345
username = "alice"
score = 98.5

print(f"{user_id=}")           # user_id=12345
print(f"{username=}")          # username='alice'
print(f"{score=:.1f}")         # score=98.5
print(f"{len(username)=}")     # len(username)=5

How Do I Handle Quotes and Special Characters?

F-strings work with all Python quote types. Choose the appropriate quote style to avoid escaping:

name = "Alice"
# All produce the same output
print(f'{name}')           # Single quotes
print(f"{name}")           # Double quotes
print(f"""{name}""")       # Triple quotes (multiline)

# Smart quote usage to avoid escaping
message = "don't"
print(f"She said: {message}")              # She said: don't
print(f'He replied: "OK"')                 # He replied: "OK"
print(f"""Multi-line message: {name}""")   # Multi-line message: Alice

Note: You can use uppercase F but lowercase f is the standard convention.

How Do I Escape Braces in F-strings?

When you need literal braces in your output, double them:

value = 42
print(f"The variable {{value}} equals {value}")
# Output: The variable {value} equals 42

print(f"CSS: {{color: red; margin: {10}px}}")
# Output: CSS: {color: red; margin: 10px}

# For JSON-like strings
data = "example"
print(f'{{"key": "{data}", "status": "active"}}')
# Output: {"key": "example", "status": "active"}

How Do I Use Dynamic Formatting?

Use variables to control formatting dynamically:

value = 123.456789
width = 10
precision = 2

print(f"{value:{width}.{precision}f}")      # "    123.46"
print(f"{value:0{width}.{precision}f}")     # "0000123.46"
print(f"{value:<{width}.{precision}f}")     # "123.46    "

# Dynamic alignment
align = "^"  # center
print(f"{value:{align}{width}.{precision}f}")  # "  123.46  "

How Do I Convert Numbers to Different Bases?

F-strings make base conversion simple using format specifiers:

num = 255

print(f"Decimal: {num:d}")       # Decimal: 255
print(f"Hexadecimal: {num:x}")   # Hexadecimal: ff
print(f"Hex (upper): {num:X}")   # Hex (upper): FF
print(f"Octal: {num:o}")         # Octal: 377
print(f"Binary: {num:b}")        # Binary: 11111111

# With prefixes for clarity
print(f"Hex with prefix: {num:#x}")     # Hex with prefix: 0xff
print(f"Binary with prefix: {num:#b}")  # Binary with prefix: 0b11111111
print(f"Octal with prefix: {num:#o}")   # Octal with prefix: 0o377

# All bases in one line
print(f"{num:d} = {num:x} = {num:o} = {num:b}")
# Output: 255 = ff = 377 = 11111111

When Should I Use the .format() Method?

While f-strings are recommended for modern Python, the .format() method still has specific use cases and offers some unique capabilities.

How Do I Use Positional Arguments with .format()?

The .format() method allows flexible argument positioning that f-strings don't support:

# Positional arguments
template = "{0} is better than {1}"
s1 = template.format("Python", "Java")    # Python is better than Java
s2 = template.format("Java", "Python")    # Java is better than Python

# Reorder without changing variables
template = "{1} is better than {0}"
s3 = template.format("Python", "Java")    # Java is better than Python

# Named arguments
template = "{language} has {features} features"
result = template.format(language="Python", features="many")
print(result)  # Python has many features

How Do I Reuse Variables with .format()?

Reuse the same argument multiple times by referencing its position:

# Positional reuse
msg = "Oh {0}, {0}! wherefore art thou {0}?".format("Romeo")
print(msg)  # Oh Romeo, Romeo! wherefore art thou Romeo?

# Named reuse
template = "Hello {name}! Welcome {name} to our {name}'s profile page."
result = template.format(name="Alice")
print(result)  # Hello Alice! Welcome Alice to our Alice's profile page.

# Mixed usage
template = "{greeting} {name}! Your score is {score}. Congratulations {name}!"
result = template.format(greeting="Hi", name="Bob", score=95)
print(result)  # Hi Bob! Your score is 95. Congratulations Bob!

How Do I Create Reusable Format Templates?

Use .format as a bound method to create reusable formatting functions:

# Define format templates
email_template = "Your email address is {email}".format
log_template = "[{level}] {timestamp}: {message}".format
currency_template = "Price: ${amount:.2f} ({currency})".format

# Use templates multiple times
print(email_template(email="alice@example.com"))
print(email_template(email="bob@example.com"))
# Output: Your email address is alice@example.com
# Output: Your email address is bob@example.com

print(log_template(level="INFO", timestamp="2025-09-19", message="Server started"))
# Output: [INFO] 2025-09-19: Server started

# User preference formats
user_prefers_commas = "{:,}".format
user_prefers_scientific = "{:.2e}".format

big_number = 1500000
print(user_prefers_commas(big_number))      # 1,500,000
print(user_prefers_scientific(big_number))  # 1.50e+06

How Do I Handle Locale-Specific Formatting?

Use the n format type for locale-aware number formatting:

import locale

# Set locale (system dependent)
try:
    locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')  # US format
    print("US format: {:n}".format(1234567.89))     # 1,234,567.89
except locale.Error:
    print("US locale not available")

try:
    locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')  # German format
    print("German format: {:n}".format(1234567.89))  # 1.234.567,89
except locale.Error:
    print("German locale not available")

# Reset to default
locale.setlocale(locale.LC_ALL, 'C')

How Do I Format Tables and Structured Data?

Both .format() and f-strings work well for creating aligned tabular data:

Using .format() method:

# Sample data
players = [
    ['Andre Iguodala', 4, 3, 7],
    ['Klay Thompson', 5, 0, 21],
    ['Stephen Curry', 5, 8, 36],
    ['Draymond Green', 9, 4, 11],
    ['Andrew Bogut', 3, 0, 2],
]

# Create reusable row formatter
row_format = "| {player:<16} | {reb:3d} | {ast:3d} | {pts:3d} |".format
header_format = "| {player:<16} | {reb:>3} | {ast:>3} | {pts:>3} |".format

# Print table
print(header_format(player="Player", reb="REB", ast="AST", pts="PTS"))
print("|" + "-" * 18 + "|" + "-" * 5 + "|" + "-" * 5 + "|" + "-" * 5 + "|")

for player_data in players:
    print(row_format(
        player=player_data[0],
        reb=player_data[1],
        ast=player_data[2],
        pts=player_data[3]
    ))

Using f-strings (more modern approach):

# Same data
players = [
    ['Andre Iguodala', 4, 3, 7],
    ['Klay Thompson', 5, 0, 21],
    ['Stephen Curry', 5, 8, 36],
    ['Draymond Green', 9, 4, 11],
    ['Andrew Bogut', 3, 0, 2],
]

# Header
print(f"| {'Player':<16} | {'REB':>3} | {'AST':>3} | {'PTS':>3} |")
print(f"|{'-'*18}|{'-'*5}|{'-'*5}|{'-'*5}|")

# Data rows
for name, reb, ast, pts in players:
    print(f"| {name:<16} | {reb:3d} | {ast:3d} | {pts:3d} |")

Output:

| Player           | REB | AST | PTS |
|------------------|-----|-----|-----|
| Andre Iguodala   |   4 |   3 |   7 |
| Klay Thompson    |   5 |   0 |  21 |
| Stephen Curry    |   5 |   8 |  36 |
| Draymond Green   |   9 |   4 |  11 |
| Andrew Bogut     |   3 |   0 |   2 |

Why Should I Avoid % String Formatting?

The % formatting method is Python's oldest string formatting approach, but it's error-prone and less readable than modern alternatives.

Problems with % Formatting

Common errors and their solutions:

# ERROR: Type mismatch
name = "Alice"
age = 25
try:
    result = "Name: %d, Age: %s" % (name, age)  # Wrong types!
except TypeError as e:
    print(f"Error: {e}")
    # TypeError: %d format: a number is required, not str

# FIX: Use correct format specifiers
result = "Name: %s, Age: %d" % (name, age)
print(result)  # Name: Alice, Age: 25

# ERROR: Wrong number of arguments
try:
    result = "%s and %s living together" % ("cats",)  # Missing argument
except TypeError as e:
    print(f"Error: {e}")
    # TypeError: not enough arguments for format string

# FIX: Provide all required arguments
result = "%s and %s living together" % ("cats", "dogs")
print(result)  # cats and dogs living together

Comparison: Why Modern Methods Are Better

name = "Alice"
age = 25
score = 95.7

# % formatting (old, error-prone)
old_way = "Name: %s, Age: %d, Score: %.1f%%" % (name, age, score)

# .format() method (better)
better_way = "Name: {}, Age: {}, Score: {:.1f}%".format(name, age, score)

# f-strings (best, Python 3.6+)
best_way = f"Name: {name}, Age: {age}, Score: {score:.1f}%"

# All produce: Name: Alice, Age: 25, Score: 95.7%

Advanced String Formatting Techniques

How Do I Format Dates and Times?

See Working with Dates for more.

from datetime import datetime, date

now = datetime.now()
today = date.today()

# Basic date/time formatting
print(f"Current time: {now}")
print(f"Today: {today}")

# Custom date formatting
print(f"Formatted: {now:%Y-%m-%d %H:%M:%S}")      # 2025-09-19 14:30:45
print(f"US format: {now:%m/%d/%Y}")               # 09/19/2025
print(f"European: {now:%d/%m/%Y}")                # 19/09/2025
print(f"Readable: {now:%B %d, %Y}")               # September 19, 2025
print(f"Time only: {now:%I:%M %p}")               # 02:30 PM

# ISO format
print(f"ISO format: {now:%Y-%m-%dT%H:%M:%S}")     # 2025-09-19T14:30:45

How Do I Use Custom Format Methods?

Create classes that support custom formatting:

class Person:
    def __init__(self, first, last, age):
        self.first = first
        self.last = last
        self.age = age

    def __format__(self, format_spec):
        if format_spec == 'full':
            return f"{self.first} {self.last}"
        elif format_spec == 'last_first':
            return f"{self.last}, {self.first}"
        elif format_spec == 'age':
            return f"{self.first} ({self.age})"
        else:
            return f"{self.first} {self.last}"

person = Person("Alice", "Johnson", 30)

print(f"Default: {person}")           # Default: Alice Johnson
print(f"Full name: {person:full}")    # Full name: Alice Johnson
print(f"Formal: {person:last_first}") # Formal: Johnson, Alice
print(f"With age: {person:age}")      # With age: Alice (30)

How Do I Format Large Numbers Readably?

# Large number formatting
big_number = 1234567890

print(f"Default: {big_number}")           # 1234567890
print(f"Commas: {big_number:,}")          # 1,234,567,890
print(f"Underscores: {big_number:_}")     # 1_234_567_890
print(f"Scientific: {big_number:.2e}")    # 1.23e+09
print(f"Engineering: {big_number:.3g}")   # 1.23e+09

# Memory sizes
bytes_value = 1073741824
print(f"Bytes: {bytes_value}")            # 1073741824
print(f"MB: {bytes_value / 1024**2:.1f}") # 1024.0
print(f"GB: {bytes_value / 1024**3:.1f}") # 1.0

Troubleshooting String Formatting

What if My Format String is Dynamic?

# Dynamic format strings (use with caution - security risk!)
def safe_format(template, **kwargs):
    """Safely format a string template with given arguments."""
    try:
        return template.format(**kwargs)
    except KeyError as e:
        return f"Missing key: {e}"
    except ValueError as e:
        return f"Format error: {e}"

# Example usage
template = "Hello {name}, you have {count:d} messages"
result = safe_format(template, name="Alice", count=5)
print(result)  # Hello Alice, you have 5 messages

# Missing key example
result = safe_format(template, name="Bob")  # missing 'count'
print(result)  # Missing key: 'count'

Summary

Key Takeaways:

  • Use f-strings for all new Python 3.6+ code - they're fastest, most readable, and support expressions
  • Use .format() when you need template reuse, positional flexibility, or Python < 3.6 compatibility
  • Avoid % formatting except for legacy code or specific library requirements
  • Use debugging format {variable=} to quickly inspect values (Python 3.8+)