Printing the right answer is only half the job. Compare Total: 1234.5 with Total: £1,234.50, or a list of products whose prices wobble across the screen with one where every price lines up neatly in a column. Good formatting makes output easier to read and makes your programs feel finished.
You've already met f-strings, which put values into text with curly braces: f"Hello, {name}". They can do much more. After the value, you can add a colon and a format specifier that controls decimal places, thousands separators, percentages, width and alignment, all in a few characters.
In this lesson you'll learn the format specifiers you'll use most, build a neatly aligned receipt, use a handy shortcut for debugging, and see the older format() method you'll still find in existing code.
Decimal places and separators
Put a colon after the value inside the braces, then the format:
price = 1234.5
print(f"{price:.2f}") # 1234.50
print(f"{price:,.2f}") # 1,234.50
print(f"{0.257:.1%}") # 25.7%.2fmeans "a fixed-point number with 2 decimal places". It rounds as well as pads.,adds a comma between every three digits;_adds underscores instead.%multiplies by 100 and adds a percent sign, so.1%shows one decimal place.:dis for whole numbers, and:eshows scientific notation, such as1.23e+06.
Width and alignment
A number before the type sets the minimum width. Add an alignment character before it:
| Format | Meaning | Result for "tea" |
|---|---|---|
{x:<8} | Left align in 8 characters | tea |
{x:>8} | Right align | tea |
{x:^8} | Centre | tea |
{x:*^8} | Centre, filled with * | **tea*** |
Text is left-aligned by default and numbers right-aligned. Combine width with other options: {price:>10.2f} right-aligns a price in 10 characters with 2 decimals. {n:03d} pads a number with zeros, turning 7 into 007.
Signs, dynamic widths and repr
A few more options cover situations that come up in real reports:
change = 4.2
print(f"{change:+.1f}") # +4.2 (always show the sign)
print(f"{-4.2:+.1f}") # -4.2
width = 12
print(f"[{'Total':>{width}}]") # [ Total]
name = "Ada"
print(f"{name!r}") # 'Ada'+forces a plus or minus sign, which is useful for changes and differences.- A width can come from a variable by nesting braces:
{value:>{width}}. This lets you size columns to fit the longest item. !rshows therepr()of a value. For strings this adds quotes, so you can spot hidden spaces:'Ada 'is obviously different from'Ada'.
Expressions and debugging
The braces can hold any expression, not just a variable: f"{qty * price:.2f}" or f"{name.upper()}". Keep them short, though; complex logic is easier to read on its own line.
Add = after an expression and Python prints the expression as well as its value, which is perfect for quick debugging:
total = 42
print(f"{total=}") # total=42
print(f"{total * 2 = }") # total * 2 = 84To show a literal curly brace, double it: f"{{not a value}}" prints {not a value}.
The format() method and dates
Before f-strings arrived in Python 3.6, code used the format() method. The format specifiers are exactly the same:
"{} owes {:.2f}".format("Sam", 7.5) # Sam owes 7.50You may also see the very old % style, "%s is %d" % ("Sam", 30), in older tutorials. For new code, prefer f-strings.
Dates have their own specifiers. With a datetime value, f"{today:%d %B %Y}" gives 05 March 2025. You'll learn more in the dates and times lesson.
f-string rules that changed in Python 3.12
F-strings were added in Python 3.6, so they don't work on older versions. Python 3.12 relaxed several rules inside the braces, and it helps to know the difference because you will meet both kinds of code.
Before 3.12 you couldn't reuse the same quote character inside the braces, or put a backslash there:
user = {"name": "Ada"}
print(f"Hi {user['name']}") # works on every version
print(f"Hi {user["name"]}") # only Python 3.12 and laterIf your code must run on older versions, use the other quote type inside the braces, and calculate anything that needs a backslash, such as "\n".join(items), in a variable before the f-string.
Python String Formatting Syntax
Format specifier patterns
f"{value:.2f}" # 2 decimal places
f"{value:,}" # 1,000,000
f"{value:.1%}" # 12.5%
f"{text:<10}" # left align, width 10
f"{num:>8.2f}" # right align, width 8, 2 decimals
f"{num:05d}" # 00042
f"{value=}" # value=...
"{:.2f}".format(value)Python String Formatting Summary
- Add a colon and a format specifier inside f-string braces:
{value:spec}. .2fgives two decimal places,,adds thousands separators and%shows a percentage.- A width with
<,>or^aligns values into columns;0pads numbers with zeros. {expression=}prints the expression and its value for debugging.format()uses the same specifiers and appears in older code.
Python String Formatting Examples
Each example shows the code, the output it produces and what each part does.
A neat receipt
items = [("Coffee", 2, 2.80), ("Bagel", 1, 3.25), ("Juice", 3, 1.90)]
print(f"{'Item':<10}{'Qty':>5}{'Total':>10}")
total = 0
for name, qty, price in items:
line = qty * price
total += line
print(f"{name:<10}{qty:>5}{line:>10.2f}")
print(f"{'TOTAL':<15}{total:>10.2f}")Item Qty Total
Coffee 2 5.60
Bagel 1 3.25
Juice 3 5.70
TOTAL 14.55Line-by-line explanation
- Line 3Headings use the same widths as the data, so the columns line up.
- Line 8Name left-aligned in 10, quantity right-aligned in 5, line total in 10 with 2 decimals.
- Line 9TOTAL takes 15 characters, the width of the first two columns together.
Big numbers and percentages
visitors = 1284503
signups = 9876
rate = signups / visitors
print(f"Visitors: {visitors:,}")
print(f"Sign-ups: {signups:,}")
print(f"Conversion rate: {rate:.2%}")Visitors: 1,284,503
Sign-ups: 9,876
Conversion rate: 0.77%Line-by-line explanation
- Lines 5 to 6The comma option makes large numbers easy to read.
- Line 7.2% multiplies by 100, rounds to 2 places and adds the percent sign.
Order numbers and debugging
for n in [7, 42, 315]:
print(f"Order #{n:05d}")
width, height = 4, 2.5
print(f"{width * height = }")Order #00007
Order #00042
Order #00315
width * height = 10.0Line-by-line explanation
- Line 205d pads each number with zeros to 5 digits.
- Line 5The = shortcut shows both the expression and its result.
Common Python String Formatting Mistakes
Rounding with round() and losing zeros
print(round(4.5, 2)) # 4.5, not 4.50print(f"{4.5:.2f}") # 4.50round() returns a number, which doesn't keep trailing zeros. Formatting does.
Forgetting the f
name = "Ada"
print("Hello, {name}") # Hello, {name}print(f"Hello, {name}") # Hello, AdaFormatting too early
price = f"{19.99:.2f}"
total = price * 2 # '19.9919.99'price = 19.99
total = price * 2
print(f"{total:.2f}") # 39.98Python String Formatting in Real Projects
Formatting in practice
Command-line tools align columns so output is easy to scan, reports show currency with separators and two decimals, and log messages often use {variable=} while a developer is tracking down a bug. Data libraries such as pandas use the same format specifiers, so what you learn here carries straight into data analysis work.
Python String Formatting FAQ
Should I use f-strings, format() or % formatting?
Use f-strings for new code: they are the most readable and the fastest in typical use. str.format() is still handy when the template is stored separately from the values, for example in a configuration file. The % style is the oldest and appears mainly in legacy code and the logging module.
How do I show a currency symbol with two decimal places?
Put the symbol in the text and the format in the braces: f"£{price:,.2f}" gives £1,234.50. For money in different countries, where separators and symbol positions vary, use the locale module or a library such as Babel instead of hard-coding the format.
Does :.2f round or cut off the extra decimals?
It rounds. f"{2.346:.2f}" gives 2.35. Because floats are stored in binary, a value such as 2.675 may round down to 2.67. If you need exact decimal rounding, for example for invoices, use the decimal module.
Can I use an f-string with a dictionary or a method call?
Yes. Any expression is allowed inside the braces, so f"{user['name']}", f"{name.title()}" and f"{len(items)} items" all work. Keep the expressions short; if the logic needs more than one step, calculate it on the line before.
How do I print a table where the columns fit the data?
Work out the widest value first, for example width = max(len(name) for name in names), then use a nested width in the format: f"{name:<{width}}". Every row then lines up regardless of the longest name.
Python String Formatting Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the f-string so the price shows exactly two decimal places.
price = 3.5
print(f"£{price:}")
What does this print?
n = 1500000
print(f"{n:,}")
What does this print?
print(f"[{'ok':>5}]")
Which f-string shows 0.4 as 40.0%?
What does this print?
x = 3
print(f"{x**2=}")
Python String Formatting Quiz
5 questions to check what you learned.
What does
f"{7:03d}"produce?- 7
- 700
- 007
- 3d7
Which alignment character centres text?
- <
- >
- =
- ^
What does
"{:>6.1f}".format(3.14159)produce?- "3.1 "
- " 3.1" (right-aligned in 6 characters)
- "3.14159"
- " 3.14"
How do you print a literal
{inside an f-string?- {{
- \{
- '{'
- It isn't possible
Why is it better to format values at the moment you print them?
- Formatting is slow
- f-strings can only be used in print()
- Formatted values are strings, so calculating with them gives wrong results or errors
- Python forgets formatting after one line