Skip to main content
โšก Calmops

Python String Formatting: f-strings, format(), and % Operator

Table of Contents

Introduction

String formatting is one of the most common tasks in Python programming. Whether you’re building user interfaces, generating reports, logging information, or creating dynamic messages, you need to combine variables with text in readable, maintainable ways.

Python offers three main approaches to string formatting:

  1. f-strings (Python 3.6+) - Modern, readable, and performant
  2. format() method (Python 2.7+) - Flexible and widely compatible
  3. % operator (Python 2.x legacy) - Historical but still used in older codebases

In this guide, we’ll explore all three methods, understand their strengths and weaknesses, and learn when to use each one. By the end, you’ll be able to choose the right formatting technique for any situation and write cleaner, more maintainable code.


Why String Formatting Matters

Before diving into the techniques, let’s understand why string formatting is important:

Without proper formatting:

name = "Alice"
age = 25
city = "New York"

# Messy concatenation
message = "Hello, " + name + "! You are " + age + " years old and live in " + city + "."
print(message)

With proper formatting:

name = "Alice"
age = 25
city = "New York"

# Clean and readable
message = f"Hello, {name}! You are {age} years old and live in {city}."
print(message)

Both produce the same output, but the second is clearer, easier to maintain, and less error-prone.


Method 1: f-strings (Formatted String Literals)

What Are f-strings?

f-strings (formatted string literals) were introduced in Python 3.6 and are now the recommended way to format strings. They’re called “f-strings” because you prefix the string with the letter f.

Basic Syntax

name = "Alice"
age = 25

# Basic f-string
message = f"Hello, {name}! You are {age} years old."
print(message)  # Output: Hello, Alice! You are 25 years old.

The curly braces {} contain expressions that are evaluated and inserted into the string.

Simple Variable Insertion

# Single variables
first_name = "Alice"
last_name = "Smith"
print(f"Full name: {first_name} {last_name}")
# Output: Full name: Alice Smith

# Multiple variables
product = "laptop"
price = 999.99
quantity = 2
print(f"Item: {product}, Price: ${price}, Quantity: {quantity}")
# Output: Item: laptop, Price: $999.99, Quantity: 2

Expressions Inside f-strings

f-strings can contain any valid Python expression:

# Arithmetic
x = 10
y = 20
print(f"Sum: {x + y}")  # Output: Sum: 30
print(f"Product: {x * y}")  # Output: Product: 200

# Function calls
name = "alice"
print(f"Uppercase: {name.upper()}")  # Output: Uppercase: ALICE

# List indexing
colors = ["red", "green", "blue"]
print(f"First color: {colors[0]}")  # Output: First color: red

# Dictionary access
student = {"name": "Alice", "grade": "A"}
print(f"Student: {student['name']}, Grade: {student['grade']}")
# Output: Student: Alice, Grade: A

# Conditional expressions
age = 20
status = f"Adult" if age >= 18 else f"Minor"
print(f"Status: {status}")  # Output: Status: Adult

Number Formatting

f-strings provide powerful formatting options for numbers:

# Decimal places
price = 19.99
print(f"Price: ${price:.2f}")  # Output: Price: $19.99

# Thousands separator
population = 1000000
print(f"Population: {population:,}")  # Output: Population: 1,000,000

# Percentage
score = 0.85
print(f"Score: {score:.1%}")  # Output: Score: 85.0%

# Scientific notation
large_number = 1234567
print(f"Scientific: {large_number:.2e}")  # Output: Scientific: 1.23e+06

# Padding and alignment
name = "Alice"
print(f"Name: {name:>10}")  # Right-aligned, 10 characters
print(f"Name: {name:<10}")  # Left-aligned, 10 characters
print(f"Name: {name:^10}")  # Center-aligned, 10 characters
# Output:
# Name:      Alice
# Name: Alice     
# Name:   Alice  

# Zero-padding for numbers
number = 42
print(f"Number: {number:05d}")  # Output: Number: 00042

Format Specifiers Reference

The general format for f-string expressions is:

{expression:format_spec}

Common format specifiers:

Specifier Meaning Example
d Integer {42:d} โ†’ 42
f Float {3.14:f} โ†’ 3.140000
.2f Float with 2 decimals {3.14:.2f} โ†’ 3.14
% Percentage {0.85:.1%} โ†’ 85.0%
e Scientific notation {1234:.2e} โ†’ 1.23e+03
, Thousands separator {1000000:,} โ†’ 1,000,000
>10 Right-align in 10 chars {'hi':>10} โ†’ hi
<10 Left-align in 10 chars {'hi':<10} โ†’ hi
^10 Center in 10 chars {'hi':^10} โ†’ hi
05d Zero-pad to 5 digits {42:05d} โ†’ 00042

Real-World Examples with f-strings

Example 1: Invoice Generation

items = [
    {"name": "Laptop", "price": 999.99, "quantity": 1},
    {"name": "Mouse", "price": 29.99, "quantity": 2},
    {"name": "Keyboard", "price": 79.99, "quantity": 1}
]

print("=" * 50)
print(f"{'Item':<20} {'Price':>10} {'Qty':>5} {'Total':>10}")
print("=" * 50)

total = 0
for item in items:
    item_total = item["price"] * item["quantity"]
    total += item_total
    print(f"{item['name']:<20} ${item['price']:>9.2f} {item['quantity']:>5} ${item_total:>9.2f}")

print("=" * 50)
print(f"{'TOTAL':<20} {' ':>10} {' ':>5} ${total:>9.2f}")
print("=" * 50)

# Output:
# ==================================================
# Item                     Price  Qty      Total
# ==================================================
# Laptop                 $999.99    1  $999.99
# Mouse                   $29.99    2   $59.98
# Keyboard                $79.99    1   $79.99
# ==================================================
# TOTAL                                $1139.96
# ==================================================

Example 2: Data Logging

from datetime import datetime

user_id = 12345
action = "login"
timestamp = datetime.now()
ip_address = "192.168.1.100"

log_message = f"[{timestamp:%Y-%m-%d %H:%M:%S}] User {user_id} performed '{action}' from {ip_address}"
print(log_message)
# Output: [2025-12-15 14:30:45] User 12345 performed 'login' from 192.168.1.100

Example 3: Dynamic HTML Generation

title = "Welcome"
content = "Hello, World!"
author = "Alice"

html = f"""
<html>
    <head>
        <title>{title}</title>
    </head>
    <body>
        <h1>{title}</h1>
        <p>{content}</p>
        <footer>By {author}</footer>
    </body>
</html>
"""
print(html)

Advantages of f-strings

โœ… Readable - Variables are visible in the string
โœ… Fast - Faster than other methods (evaluated at runtime)
โœ… Flexible - Supports expressions, not just variables
โœ… Modern - Recommended for Python 3.6+
โœ… Concise - Less verbose than alternatives

Limitations of f-strings

โŒ Python 3.6+ only - Not available in older Python versions
โŒ Can’t be used with eval() - Security consideration for dynamic strings
โŒ Limited for complex logic - Keep expressions simple for readability


Method 2: The format() Method

What Is the format() Method?

The format() method is a string method that allows you to insert values into a string using placeholders. It’s been available since Python 2.7 and is more compatible with older Python versions than f-strings.

Basic Syntax

name = "Alice"
age = 25

# Basic format
message = "Hello, {}! You are {} years old.".format(name, age)
print(message)  # Output: Hello, Alice! You are 25 years old.

The curly braces {} are placeholders that are replaced with the arguments passed to format().

Positional Arguments

# Positional arguments (in order)
print("Hello, {} and {}!".format("Alice", "Bob"))
# Output: Hello, Alice and Bob!

# Explicit positions
print("Hello, {1} and {0}!".format("Alice", "Bob"))
# Output: Hello, Bob and Alice!

# Repeated positions
print("{0} likes {1}. {0} also likes {1}.".format("Alice", "pizza"))
# Output: Alice likes pizza. Alice also likes pizza.

Named Arguments

# Named arguments
message = "Hello, {name}! You are {age} years old.".format(name="Alice", age=25)
print(message)  # Output: Hello, Alice! You are 25 years old.

# Mix positional and named
print("{0} is {age} years old.".format("Alice", age=25))
# Output: Alice is 25 years old.

Dictionary Unpacking

student = {"name": "Alice", "age": 20, "grade": "A"}

# Using ** to unpack dictionary
message = "Student: {name}, Age: {age}, Grade: {grade}".format(**student)
print(message)  # Output: Student: Alice, Age: 20, Grade: A

# Accessing dictionary items
print("Name: {student[name]}".format(student=student))
# Output: Name: Alice

Number Formatting with format()

# Decimal places
price = 19.99
print("Price: ${:.2f}".format(price))  # Output: Price: $19.99

# Thousands separator
population = 1000000
print("Population: {:,}".format(population))  # Output: Population: 1,000,000

# Percentage
score = 0.85
print("Score: {:.1%}".format(score))  # Output: Score: 85.0%

# Padding and alignment
name = "Alice"
print("Name: {:>10}".format(name))  # Right-aligned
print("Name: {:<10}".format(name))  # Left-aligned
print("Name: {:^10}".format(name))  # Center-aligned
# Output:
# Name:      Alice
# Name: Alice     
# Name:   Alice  

# Zero-padding
number = 42
print("Number: {:05d}".format(number))  # Output: Number: 00042

Real-World Examples with format()

Example 1: Table Formatting

data = [
    ("Alice", 25, 95.5),
    ("Bob", 30, 87.3),
    ("Charlie", 22, 92.1)
]

print("{:<15} {:<10} {:<10}".format("Name", "Age", "Score"))
print("-" * 35)
for name, age, score in data:
    print("{:<15} {:<10} {:<10.1f}".format(name, age, score))

# Output:
# Name            Age        Score     
# -----------------------------------
# Alice           25         95.5      
# Bob             30         87.3      
# Charlie         22         92.1

Example 2: Configuration String

config = {
    "host": "localhost",
    "port": 5432,
    "database": "mydb",
    "user": "admin"
}

connection_string = "postgresql://{user}@{host}:{port}/{database}".format(**config)
print(connection_string)
# Output: postgresql://admin@localhost:5432/mydb

Advantages of format()

โœ… Compatible - Works with Python 2.7+
โœ… Flexible - Supports positional and named arguments
โœ… Powerful - Can handle complex formatting
โœ… Readable - Clear placeholder syntax

Limitations of format()

โŒ More verbose - Longer than f-strings
โŒ Slower - Slightly slower than f-strings
โŒ Less intuitive - Requires understanding format specifications


Method 3: The % Operator (Old-Style Formatting)

What Is % Formatting?

The % operator is the oldest string formatting method in Python, inherited from C’s printf function. While it’s still used in legacy code, it’s generally not recommended for new projects.

Basic Syntax

name = "Alice"
age = 25

# Basic % formatting
message = "Hello, %s! You are %d years old." % (name, age)
print(message)  # Output: Hello, Alice! You are 25 years old.

The % operator uses format codes like %s (string), %d (integer), %f (float).

Format Codes

Code Type Example
%s String "Hello, %s" % "Alice" โ†’ Hello, Alice
%d Integer "Age: %d" % 25 โ†’ Age: 25
%f Float "Price: %f" % 19.99 โ†’ Price: 19.990000
%.2f Float (2 decimals) "Price: %.2f" % 19.99 โ†’ Price: 19.99
%x Hexadecimal "Hex: %x" % 255 โ†’ Hex: ff
%o Octal "Octal: %o" % 8 โ†’ Octal: 10

Examples

# Single value
print("Hello, %s!" % "Alice")
# Output: Hello, Alice!

# Multiple values (must use tuple)
print("Name: %s, Age: %d" % ("Alice", 25))
# Output: Name: Alice, Age: 25

# Decimal places
print("Price: $%.2f" % 19.99)
# Output: Price: $19.99

# Padding
print("Number: %05d" % 42)
# Output: Number: 00042

# Dictionary
person = {"name": "Alice", "age": 25}
print("Name: %(name)s, Age: %(age)d" % person)
# Output: Name: Alice, Age: 25

Advantages of % Formatting

โœ… Historical compatibility - Works with very old Python versions
โœ… Concise - Short syntax for simple cases

Limitations of % Formatting

โŒ Outdated - Not recommended for new code
โŒ Less readable - Format codes are cryptic
โŒ Error-prone - Easy to mix up format codes
โŒ Limited flexibility - Doesn’t support expressions
โŒ Slower - Slower than f-strings and format()


Comparison: Which Method Should You Use?

Performance Comparison

import timeit

name = "Alice"
age = 25

# f-string
f_string_time = timeit.timeit(
    'f"Hello, {name}! You are {age} years old."',
    globals={"name": name, "age": age},
    number=1000000
)

# format() method
format_time = timeit.timeit(
    '"Hello, {}! You are {} years old.".format(name, age)',
    globals={"name": name, "age": age},
    number=1000000
)

# % operator
percent_time = timeit.timeit(
    '"Hello, %s! You are %d years old." % (name, age)',
    globals={"name": name, "age": age},
    number=1000000
)

print(f"f-string: {f_string_time:.4f}s")
print(f"format(): {format_time:.4f}s")
print(f"% operator: {percent_time:.4f}s")

# Typical output:
# f-string: 0.0234s
# format(): 0.0456s
# % operator: 0.0389s

Result: f-strings are the fastest, followed by the % operator, then format().

Readability Comparison

name = "Alice"
age = 25
city = "New York"

# f-string - Most readable
message1 = f"Hello, {name}! You are {age} years old and live in {city}."

# format() - Readable but more verbose
message2 = "Hello, {}! You are {} years old and live in {}.".format(name, age, city)

# % operator - Least readable
message3 = "Hello, %s! You are %d years old and live in %s." % (name, age, city)

print(message1)
print(message2)
print(message3)
# All output: Hello, Alice! You are 25 years old and live in New York.

Decision Matrix

Situation Recommended Method Reason
New Python 3.6+ code f-strings Best performance and readability
Python 2.7 compatibility format() Most compatible modern method
Legacy code maintenance % operator Already in use, don’t change
Complex formatting format() or f-strings More control over output
Simple string concatenation f-strings Clearest and fastest
Dynamic format strings format() More flexible for runtime changes

Best Practices and Recommendations

1. Use f-strings for New Code

For any new Python 3.6+ project, use f-strings:

# Good
name = "Alice"
print(f"Hello, {name}!")

# Avoid
print("Hello, {}!".format(name))
print("Hello, %s!" % name)

2. Keep Expressions Simple

While f-strings support expressions, keep them simple for readability:

# Good - simple expression
age = 25
print(f"Next year you'll be {age + 1}")

# Avoid - complex expression
print(f"Next year you'll be {age + 1 if age < 100 else 'very old'}")

# Better - use a variable
next_age = age + 1 if age < 100 else None
print(f"Next year you'll be {next_age}")

3. Use Named Arguments for Clarity

When formatting dictionaries or complex data, use named arguments:

# Good - clear what each value represents
student = {"name": "Alice", "grade": "A", "score": 95}
print(f"Student: {student['name']}, Grade: {student['grade']}, Score: {student['score']}")

# Better - use named arguments
print("Student: {name}, Grade: {grade}, Score: {score}".format(**student))

4. Format Numbers Appropriately

Always specify precision for financial and scientific data:

# Good - explicit precision
price = 19.99
print(f"Price: ${price:.2f}")

# Avoid - implicit precision
print(f"Price: ${price}")  # Could show 19.99 or 19.990000 depending on value

5. Use Alignment for Tables

When creating tabular output, use alignment specifiers:

# Good - aligned columns
print(f"{'Name':<15} {'Age':>5} {'Score':>7}")
print(f"{'Alice':<15} {25:>5} {95.5:>7.1f}")

# Avoid - unaligned
print(f"Name: Alice, Age: 25, Score: 95.5")

6. Avoid String Formatting in Loops

Pre-format strings outside loops when possible:

# Good - format once
header = f"{'Name':<15} {'Age':>5}"
print(header)
for name, age in [("Alice", 25), ("Bob", 30)]:
    print(f"{name:<15} {age:>5}")

# Avoid - format in loop
for name, age in [("Alice", 25), ("Bob", 30)]:
    print(f"{'Name':<15} {'Age':>5}")  # Repeated formatting
    print(f"{name:<15} {age:>5}")

7. Use Logging with Proper Formatting

For logging, use f-strings or format():

import logging

logger = logging.getLogger(__name__)

user_id = 12345
action = "login"

# Good - f-string
logger.info(f"User {user_id} performed action: {action}")

# Also good - format()
logger.info("User {} performed action: {}".format(user_id, action))

# Avoid - string concatenation
logger.info("User " + str(user_id) + " performed action: " + action)

Common Use Cases

Use Case 1: User-Friendly Output

# Display information to users
name = "Alice"
balance = 1234.56
transactions = 42

output = f"""
Account Summary
===============
Name: {name}
Balance: ${balance:,.2f}
Transactions: {transactions}
"""
print(output)

Use Case 2: Data Validation Messages

def validate_email(email):
    if "@" not in email:
        return f"Invalid email: '{email}' must contain '@'"
    return f"Valid email: {email}"

print(validate_email("[email protected]"))
print(validate_email("alice.example.com"))

Use Case 3: SQL Query Building

# Note: This is for demonstration. Use parameterized queries in production!
user_id = 123
username = "alice"

# Using f-strings (not recommended for SQL due to injection risks)
# query = f"SELECT * FROM users WHERE id = {user_id} AND username = '{username}'"

# Better: Use parameterized queries
query = "SELECT * FROM users WHERE id = ? AND username = ?"
params = (user_id, username)

Use Case 4: Configuration Files

# Generate configuration strings
host = "localhost"
port = 5432
database = "mydb"

config = f"""
[database]
host = {host}
port = {port}
database = {database}
"""
print(config)

Use Case 5: Error Messages

def divide(a, b):
    if b == 0:
        raise ValueError(f"Cannot divide {a} by zero")
    return a / b

try:
    result = divide(10, 0)
except ValueError as e:
    print(f"Error: {e}")

Troubleshooting Common Issues

Issue 1: Curly Braces in Output

# Problem: Want to print literal curly braces
# Wrong
print(f"Set: {1, 2, 3}")  # Error: invalid syntax

# Solution: Double the braces
print(f"Set: {{1, 2, 3}}")  # Output: Set: {1, 2, 3}

# Or use format()
print("Set: {}".format("{1, 2, 3}"))  # Output: Set: {1, 2, 3}

Issue 2: Quotes Inside f-strings

# Problem: Mixing quote types
# Wrong
name = "Alice"
print(f"She said "Hello"")  # Error: unterminated string

# Solution: Use different quote types
print(f"She said 'Hello'")  # Output: She said 'Hello'
print(f'She said "Hello"')  # Output: She said "Hello"

# Or escape quotes
print(f"She said \"Hello\"")  # Output: She said "Hello"

Issue 3: Format Specification Errors

# Problem: Wrong format specifier
# Wrong
value = "text"
print(f"{value:d}")  # Error: unknown format code 'd' for object of type 'str'

# Solution: Use correct specifier
print(f"{value:s}")  # Output: text

# Or use default
print(f"{value}")  # Output: text

Issue 4: Missing Arguments in format()

# Problem: Not enough arguments
# Wrong
print("Hello, {} and {}".format("Alice"))  # Error: not enough arguments

# Solution: Provide all arguments
print("Hello, {} and {}".format("Alice", "Bob"))  # Output: Hello, Alice and Bob

Conclusion

String formatting is a fundamental skill in Python programming. Here’s what you’ve learned:

Key takeaways:

  1. Use f-strings for new code - They’re the modern standard (Python 3.6+)
  2. f-strings are fast and readable - Best performance and clarity
  3. format() is compatible - Use when supporting older Python versions
  4. % operator is legacy - Avoid in new code, but understand it for maintenance
  5. Choose the right method - Consider your Python version and use case
  6. Format numbers appropriately - Always specify precision for financial data
  7. Keep expressions simple - f-strings support expressions, but use them judiciously
  8. Use alignment for tables - Make output readable with proper formatting

By mastering these three string formatting methods, you’ll write cleaner, more maintainable Python code. Start with f-strings for new projects, and you’ll rarely need the other methods.

Happy formatting! ๐Ÿ


Quick Reference

f-strings (Python 3.6+)

name = "Alice"
age = 25
price = 19.99

f"Hello, {name}!"                    # Basic
f"Age: {age + 1}"                    # Expressions
f"Price: ${price:.2f}"               # Formatting
f"{name:>10}"                        # Alignment

format() Method (Python 2.7+)

"Hello, {}!".format(name)            # Positional
"Hello, {0}!".format(name)           # Indexed
"Hello, {name}!".format(name=name)   # Named
"Price: ${:.2f}".format(price)       # Formatting

% Operator (Legacy)

"Hello, %s!" % name                  # String
"Age: %d" % age                      # Integer
"Price: %.2f" % price                # Float
"Hello, %s! Age: %d" % (name, age)   # Multiple

Comments