Formatting Text in Python

Formatting text is the bridge between raw values and output someone will actually read. Python offers three styles, and in modern code you will almost always reach for the youngest one: the f-string. Introduced in Python 3.6 and improved every release since, it embeds expressions directly inside a string literal and lets a compact format specifier control width, alignment, precision, grouping, sign and base.

A format specifier sits after a colon inside the braces: f"{value:<10.2f}" means "format value left-aligned in a 10-character field, as a float with two decimals". The language of the specifier is shared with str.format() and documented under "format specification mini-language". The pieces you will use weekly are: </>/^ for alignment, 0 prefix for zero padding, , or _ for thousands grouping, .<n> for precision, and the type codes d, f, e, x, b, %.

The second-newest style, str.format(), uses the same mini-language but keeps the data outside the string: "{name} scored {score:.1f}".format(name=n, score=s). It is handy when a template is generated dynamically (from a config file, for example). The oldest style, %-formatting, is still common inside the standard library and in logging calls; learn to read it so existing code makes sense, but prefer f-strings for anything new.

Two lesser-known f-string tricks pay for themselves quickly. The debug flag (f"{name=}") prints both the variable name and its value — a life-saver while tracing a bug. The conversion flags !r (repr), !s (str) and !a (ascii) force a specific string form of a value before formatting, useful when logging objects that have rich __str__/__repr__ implementations.

For localised output (currency, dates, plural forms) Python ships the locale module, and for templated email/HTML prefer a dedicated templating engine such as Jinja2. For tabular text output there are str.ljust, str.rjust and str.center, but a one-line f-string is usually enough. Knowing when to stop formatting by hand — and reach for JSON, CSV or a templating library — is part of choosing the right tool for the job.

The format-spec mini-language

Read a spec left to right: optional fill character, alignment, sign indicator, hash/#, zero padding, width, thousands separator, precision, and type. For {value:+,.3f}: show the sign (+), group thousands with commas, use three decimals, format as float. For {value:#010x}: prefix with 0x, pad to ten characters with zeros, format as hexadecimal.

Alignment, padding, and unicode widths

Alignment shines when printing tables: f"{name:<12}{age:>4}" produces a nicely lined-up two-column row. Unicode fullwidth characters (Chinese, Japanese) count as one character in Python but render two columns wide, which can throw alignment off; libraries such as wcwidth solve it if you need precise column math.

These are the building blocks of readable formatted output in Python.

ToolPurpose
f"..."
syntax
Embeds expressions and format specs inside a string literal.
str.format()
method
Template-based formatting with positional or named fields.
format spec
mini-language
Width, alignment, sign, precision and type codes.
str.ljust() / rjust() / center()
methods
Pad a string to a given width with a chosen character.
str.zfill()
method
Zero-pad a numeric string; good for postal codes or times.
string.Template
class
Dollar-sign substitution with no format spec; safer for user input.
locale.format_string()
function
Locale-aware number formatting for reports.
f"{x=}"
f-string flag
Prints the variable name and its repr; Python 3.8+.

Formatting Text in Python code example

The example prints a small formatted report using each feature in turn: alignment, precision, grouping, percentages and the debug flag.

# Lesson: Formatting Text in Python
# Goal: build one polished table of numbers using only f-strings.
from datetime import date


report = [
    ("apples",       1230, 0.879),
    ("bananas",      9812, 0.42),
    ("dragonfruit",     7, 3.5),
]

header = f"| {'product':<12} | {'units':>8} | {'price':>7} | {'total':>10} |"
print(header)
print("|" + "-" * (len(header) - 2) + "|")

grand_total = 0.0
for name, units, price in report:
    total = units * price
    grand_total += total
    print(f"| {name:<12} | {units:>8,d} | {price:>7.2f} | {total:>10,.2f} |")

print("|" + "-" * (len(header) - 2) + "|")
print(f"| {'grand total':<12} | {'':>8} | {'':>7} | {grand_total:>10,.2f} |")

# percentage and sign
delta = (grand_total - 9000) / 9000
print(f"variance vs budget: {delta:+.1%}")

# debug flag + date formatting
today = date.today()
print(f"{today=:%Y-%m-%d}")

Notice how each spec contributes to the final row:

1) `<12` and `>8` keep the columns aligned left and right.
2) `{units:>8,d}` adds thousands separators to integers.
3) `{price:>7.2f}` limits floats to two decimals in a 7-wide field.
4) The debug flag (`{today=:%Y-%m-%d}`) also respects date format codes.

Two compact practice snippets focused on numeric formatting.

# Example A: currency-like output without the locale module
amount = 12_345.678
print(f"{amount:,.2f}")       # -> 12,345.68
print(f"${amount:,.2f}")      # -> $12,345.68

# Example B: binary and hex with prefixes
n = 255
print(f"dec={n} bin={n:#010b} hex={n:#06x}")
# -> dec=255 bin=0b11111111 hex=0x00ff

Specs are easier to remember once pinned down by assertions.

assert f"{3.14159:.3f}" == "3.142"
assert f"{42:06d}" == "000042"
assert f"{0.125:+.1%}" == "+12.5%"
assert f"{'hi':^8}" == "   hi   "
assert f"{1234567:,d}" == "1,234,567"

Running the script prints a tidy report:

| product      |    units |   price |      total |
|------------------------------------------------|
| apples       |    1,230 |    0.88 |   1,081.17 |
| bananas      |    9,812 |    0.42 |   4,121.04 |
| dragonfruit  |        7 |    3.50 |      24.50 |
|------------------------------------------------|
| grand total  |          |         |   5,226.71 |
variance vs budget: -41.9%
today=datetime.date(2026, 4, 21)