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}"
orf"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+)
Related Topics
- Working with Strings - Basic string operations and methods
- Working with Dates - Date and time formatting
- File I/O - Writing formatted strings to files
- Regular Expressions - Pattern matching and text processing