Python f-Strings: Formatting Done Right
A practical tour of what goes after the colon in a Python f-string: alignment and width, thousands separators, decimal places and percentages, signs and padding, dates and times, repr and the = debugging specifier, nested specs for dynamic widths, and building aligned text tables.
GloryTecks Team
Training & Placement Faculty
Last updated: 24 September 2026
Most people use f-strings for one thing: dropping a variable into text, as in f"Hello, {name}". The part after the colon inside the braces is where f-strings become genuinely useful. It is a small formatting language that controls width, alignment, separators, decimal places, percentages and dates, and it is the same language format() and str.format() use (Python docs: format specification mini-language). Learn it once and you stop writing round() and string padding by hand.
Anatomy of a replacement field
A replacement field is {expression!conversion:format_spec}. Only the expression is required. The conversion (!r, !s or !a) chooses repr(), str() or ascii() before formatting, and the format spec after the colon controls the layout (Python reference: f-strings). The expression can be any Python expression: an arithmetic result, a method call, an indexed value.
Numbers: separators, decimals, percentages
revenue = 12345678.456
growth = 0.0734
units = 42
print(f"{revenue:,.2f}") # thousands separator, 2 decimals
print(f"{revenue:_.0f}") # underscore separator, no decimals
print(f"{growth:.1%}") # multiply by 100 and add %
print(f"{units:05d}") # zero-pad to width 5
print(f"{revenue:.3e}") # scientific notation
print(f"{-7.5:+.1f} {7.5:+.1f}") # always show the sign12,345,678.46
12_345_678
7.3%
00042
1.235e+07
-7.5 +7.5.2f rounds for display only; the variable keeps its full value, which is exactly what you want in a report. The % type multiplies by 100 for you, so store rates as fractions (0.0734) and format them as percentages at the edge.
A caution for Indian number formatting: the , option always groups in thousands (12,345,678), not in lakhs and crores (1,23,45,678). For lakh grouping, use a locale-aware library or a small helper function; the format language has no option for it.
Width and alignment
for name, amount in [("Notebook", 120), ("Stapler", 1450.5), ("Pen", 25)]:
print(f"|{name:<10}|{amount:>10,.2f}|{name:^10}|")
print(f"{'total':*^20}")|Notebook | 120.00| Notebook |
|Stapler | 1,450.50| Stapler |
|Pen | 25.00| Pen |
*******total********<, > and ^ align left, right and centre within the width; a character before the alignment symbol, like the * in the last line, fills the space. Right-aligning numbers with a fixed number of decimals lines up the decimal points, which is what makes a column of figures readable. Text longer than the width is not truncated; add a precision to a string ({name:.8}) to cut it.
Dates and times
date and datetime objects accept strftime codes directly after the colon (Python docs: format codes):
from datetime import datetime
paid_at = datetime(2026, 9, 25, 14, 5)
print(f"{paid_at:%d %b %Y, %H:%M}")
print(f"{paid_at:%Y-%m-%d} week {paid_at:%V} {paid_at:%A}")25 Sep 2026, 14:05
2026-09-25 week 39 FridayFor machine-readable output, such as logs, files and APIs, prefer paid_at.isoformat(), which includes the offset for time-zone-aware values.
repr and the = specifier for debugging
city = "Hyderabad "
print(f"{city}|") # the trailing space is invisible
print(f"{city!r}|") # repr shows it
print(f"{city=}") # expression text, then its repr
print(f"{units * 3 = }") # spaces around = are keptHyderabad |
'Hyderabad '|
city='Hyderabad '
units * 3 = 126!r uses repr(), which shows quotes and invisible characters: invaluable when a value "looks right" but does not match. The = specifier (Python 3.8+) prints the expression itself followed by its value, so a debugging line no longer needs the variable name typed twice. By default it uses repr() too.
Dynamic widths with nested fields
The format spec can itself contain replacement fields, so widths and precision can come from variables:
rows = [("Hyderabad", 1523.456), ("Visakhapatnam", 98.1)]
width = max(len(city) for city, _ in rows) + 2
decimals = 1
for city, value in rows:
print(f"{city:<{width}}{value:>10.{decimals}f}")Hyderabad 1523.5
Visakhapatnam 98.1Computing the column width from the data means the table fits whatever names appear, with no magic numbers.
Literal braces and quotes
Double a brace to print it literally: f"{{placeholder}}" prints {placeholder}. Before Python 3.12, the expression inside an f-string could not reuse the string's own quote character or contain a backslash; from 3.12 those restrictions are lifted (PEP 701). If your code must run on older versions, use the other quote type inside, as in f"{row['name']}".
Choosing precision honestly
Formatting decides what readers believe about your numbers, so a few habits matter more than any particular spec:
- Show only the precision the data supports. A survey average printed as 3.4172 suggests an accuracy the data does not have; one decimal place is more honest.
- Keep a column consistent. Every value in a column should have the same number of decimals, so that 120.00 and 1,450.50 line up and compare at a glance.
- Round for display, not for storage. Keep full precision in variables and files, and round only in the f-string at the moment of output. Rounding early and then summing produces totals that disagree with the detail rows.
- Expect rounded percentages not to add up to 100. Three shares of 33.3% sum to 99.9%. Either say so in a note, or adjust the largest share so the displayed values reach 100.
When not to use an f-string
- Logging calls.
logger.info("loaded %s rows", n)defers formatting until the message is actually emitted, and log tools can group messages by their template. An f-string formats every time, even when the level is switched off. - SQL queries. Never build SQL by formatting values into the string; that is how injection happens. Pass values as query parameters.
- User-facing templates that translators or non-developers edit. Use a template system, where the text does not contain code.
Quick reference
| Goal | Spec | Example output |
|---|---|---|
| Two decimals, grouped | :,.2f | 12,345,678.46 |
| Percentage, one decimal | :.1% | 7.3% |
| Zero-pad integer to 5 | :05d | 00042 |
| Right-align in 10 | :>10 | right-aligned |
| Centre with fill | :*^20 | centred, starred |
| Scientific | :.3e | 1.235e+07 |
| Hex, binary | :x, :b | 2a, 101010 |
| Date | :%d %b %Y | 25 Sep 2026 |
| Show repr | !r | quotes visible |
| Debug | = | expression and value |
Are f-strings faster than format() or %-formatting?
How do I format currency with the rupee symbol?
Can I reuse a format spec in several places?
Should I still use str.format()?
Why does round() give different results from the f-string?
Formatting is one of many string operations; Python string methods every developer should know covers splitting, cleaning and searching text. The Python best practices article shows f-strings among other everyday idioms. String operations, methods and string formatting are part of the strings and regular expressions module of the Python Programming course.
Sources
- f-strings (lexical analysis) — Python documentation
- Format Specification Mini-Language — Python documentation
- strftime() and strptime() format codes — Python documentation
- PEP 701 – Syntactic formalization of f-strings — Python Enhancement Proposals
GloryTecks Team
Training & Placement Faculty
The GloryTecks faculty team mentors learners across Data Science, AI and Data Engineering, with a focus on real-time projects and placement readiness in Hyderabad.
Want to master Python?
Join GloryTecks’ expert-led Python Programming training in Hyderabad with real-time projects and 100% placement support.
Related articles
Learn Python from Scratch: A Complete 2026 Roadmap
How to learn Python from zero: install a current version (including the new Windows install manager), then work through seven stages, from values and loops to files, virtual environments, classes and tests, each ending with a small program you should be able to write on your own.
Python Interview Questions: Top 50 with Answers
Fifty conceptual Python interview questions with short, precise answers, grouped by topic: basics, types and mutability, data structures, functions and scope, OOP, iterators and decorators, errors and files, and internals, with runnable examples for the ones people most often get wrong.
Python for Data Science: The Essential Libraries
The Python data science stack explained by the job each library does: NumPy for arrays, pandas for tables, Matplotlib and seaborn for charts, SciPy and statsmodels for statistics, scikit-learn for models and Jupyter for exploration, with one runnable example that passes a real dataset through them.