Methods for formatting strings in Python have evolved over time, giving you multiple ways to embed variables, expressions, and computed values into readable, type-safe output. In 2026, the three main approaches are the legacy %-style (still found in older code), str.format() (flexible for dynamic templates), and f-strings (formatted string literals, introduced in Python 3.6) — now the dominant, preferred method for its unmatched simplicity, speed, clarity, and ability to embed arbitrary expressions directly. Each method has its strengths: % for quick legacy compatibility, .format() for reusable/dynamic format strings, and f-strings for most modern code. Choosing the right one improves readability, performance, and maintainability — especially in logging, reporting, templating, user messages, API payloads, and data presentation.
Here’s a complete, practical guide to string formatting methods in Python: legacy %-style, str.format() (positional/named/indexed), f-strings, format specifiers for alignment/precision/type, real-world patterns, comparison of methods, and modern best practices with type hints, safety, performance, and pandas/Polars integration.
Legacy %-style formatting uses placeholders like %s (string), %d (integer), %f (float) — values passed as tuple or single value. It’s simple but limited and considered outdated.
name = "Alice"
age = 25
occupation = "programmer"
# Tuple-based
print("My name is %s, I am %d years old, and I work as a %s." % (name, age, occupation))
# My name is Alice, I am 25 years old, and I work as a programmer.
# Single value with precision
print("Temperature: %.1f°C" % 23.456) # Temperature: 23.5°C
str.format() uses curly brace placeholders {} — positional (default), indexed {0}, named {name}, or keyword-based with format_map(). It’s more flexible than % and supports reuse, reordering, and advanced specifiers.
# Positional
print("My name is {}, I am {} years old, and I work as a {}.".format(name, age, occupation))
# My name is Alice, I am 25 years old, and I work as a programmer.
# Indexed + reuse
print("My name is {0}, I am {1} years old. Hi again, {0}!".format(name, age))
# My name is Alice, I am 25 years old. Hi again, Alice!
# Named placeholders
print("My name is {name}, I am {age} years old, and I work as a {occupation}.".format(name=name, age=age, occupation=occupation))
# My name is Alice, I am 25 years old, and I work as a programmer.
f-strings (formatted string literals) embed expressions directly inside f"..." — fastest, clearest, and most readable; support full expressions and format specifiers.
print(f"My name is {name}, I am {age} years old, and I work as a {occupation}.")
# My name is Alice, I am 25 years old, and I work as a programmer.
# Expressions and specifiers inside f-string
print(f"Temperature: {23.456:.1f}°C") # Temperature: 23.5°C
print(f"Date: {date.today():%Y-%m-%d}") # Date: 2026-02-10
print(f"Total: {price * quantity:.2f}") # e.g., Total: 49.95
Format specifiers control alignment, padding, precision, and type — used in both .format() and f-strings.
price = 19.99
print(f"Price: ${price:>10.2f}") # Price: $ 19.99 (right-aligned, width 10)
print(f"Tax rate: {0.0875:.1%}") # Tax rate: 8.8%
print(f"ID: {42:05d}") # ID: 00042 (zero-padded to 5 digits)
print(f"Name: {name:<10} Age: {age:>3}") # Name: Alice Age: 25
Real-world pattern: dynamic logging, reports, or templating — f-strings for static formats, .format() for templates from config/files/databases.
# Log template from config
log_template = "[{time:%Y-%m-%d %H:%M:%S}] {level}: {message}"
log = log_template.format(time=datetime.now(), level="INFO", message="User login successful")
print(log)
# [2026-02-10 14:30:45] INFO: User login successful
Best practices make string formatting safe, readable, and performant. Prefer f-strings for most cases — fastest, clearest, support expressions, and less error-prone. Use .format() when format string is dynamic (from file, config, user input, or database) — template.format_map(data_dict) with named args. Modern tip: use Polars for large string formatting — pl.col("name").str.format("Hello {}") is fast and vectorized. Add type hints — str — improves static analysis. Use named placeholders for clarity — "{name} is {age}".format(name=name, age=age). Handle missing keys — format_map() raises KeyError; use dict.get() or defaults. For internationalization, use str.format_map() with gettext — supports positional and named args. Avoid % operator — deprecated in favor of .format() and f-strings. Combine with str.join() — ", ".join(["{:.2f}".format(x) for x in values]) formats lists cleanly.
String formatting with f-strings, .format(), and legacy % embeds values into strings cleanly and flexibly. In 2026, prefer f-strings for static formats, .format() for dynamic templates, vectorize in pandas/Polars, and add type hints for safety. Master formatting, and you’ll create dynamic messages, logs, reports, and templates accurately and efficiently.
Next time you need to insert variables into a string — reach for f-strings or .format(). It’s Python’s cleanest way to say: “Put these values here, formatted right.”