Why this matters
Output is how your program communicates: reports, receipts, log lines, tables, error messages. Gluing strings with + and str() quickly becomes unreadable, and print with commas gives you no control over spacing or decimal places. Python's f-strings let you write the output's shape directly, with values dropped into place and formatted precisely: prices with two decimals, columns that line up, big numbers with separators. They are also one of the best debugging tools in the language.
The basic idea
Put an f before the opening quote, and any expression inside { } is evaluated and inserted:
name = "Ada"
age = 36
print(f"{name} is {age} years old.")
print(f"Next year {name} will be {age + 1}.")
print(f"Shouting: {name.upper()}!")
Ada is 36 years old.
Next year Ada will be 37.
Shouting: ADA!
Compare the alternatives:
print(name + " is " + str(age) + " years old.")
print(name, "is", age, "years old.")
The f-string is easier to read, converts values to text automatically, and gives you full control over spaces.
What happens underneath
An f-string is not a string with a special runtime meaning; it is compiled into code. For f"{name} is {age}", the bytecode compiler emits instructions that evaluate name, convert it with format(), evaluate age, convert it, and concatenate the pieces with the literal text in one efficient step (BUILD_STRING). You can check with dis.dis('f"{name} is {age}"'). Two consequences:
- The expressions are evaluated at the moment the f-string runs, using the current values of the names.
- Errors inside the braces are ordinary errors:
f"{undefined_name}"raisesNameError.
To include a literal brace, double it: f"{{x}} is {x}" gives {x} is 5 when x is 5.
Format specifications
After the expression, a colon introduces a format specification (format spec) that controls how the value is rendered:
{value:spec}
|
[[fill]align][sign][0][width][,][.precision][type]
You rarely need all parts at once. Here are the ones you will use constantly.
Precision and type for numbers
pi = 3.14159265
print(f"{pi:.2f}")
print(f"{pi:.0f}")
print(f"{2.5:.3f}")
print(f"{1234.5:e}")
print(f"{0.256:.1%}")
3.14
3
2.500
1.234500e+03
25.6%
fmeans fixed-point;.2fgives exactly 2 digits after the decimal point, rounding as needed.eis scientific notation;%multiplies by 100 and adds a percent sign.dis a decimal integer (the default for ints),xhexadecimal,bbinary:f"{255:x}"isff,f"{5:b}"is101.
Note: Rounding uses the exact binary value of the float, so results may surprise you at the halfway point:
f"{2.675:.2f}"gives2.67, because 2.675 is actually stored as 2.67499999... (recall the float lesson). For money that must round exactly, use thedecimalmodule.
Width and alignment
A width sets the minimum number of characters; shorter values are padded. Alignment chooses where the padding goes: < left, > right, ^ center. By default strings align left and numbers align right.
print(f"[{'cat':10}]")
print(f"[{'cat':>10}]")
print(f"[{'cat':^10}]")
print(f"[{42:10}]")
print(f"[{42:<10}]")
[cat ]
[ cat]
[ cat ]
[ 42]
[42 ]
A fill character goes before the alignment symbol:
print(f"{'title':*^15}")
print(f"{7:0>3}")
print(f"{7:03}")
*****title*****
007
007
A leading 0 before the width is a shortcut for zero padding numbers (it also keeps the sign in front: f"{-7:04}" is -007).
Width is a minimum: a value longer than the width is never cut off, it just pushes the columns out of line.
Thousands separators and signs
n = 1234567.891
print(f"{n:,.2f}")
print(f"{n:_.0f}")
print(f"{5:+}")
print(f"{-5:+}")
1,234,567.89
1_234_568
+5
-5
, or _ groups digits by thousands; + always shows the sign.
Specs can use expressions too
The width itself can come from a variable, using nested braces:
width = 8
print(f"[{'hi':>{width}}]")
[ hi]
Building aligned tables
Combining widths and alignment produces tidy columns:
print(f"{'item':<10}{'qty':>5}{'price':>9}")
print(f"{'apple':<10}{3:>5}{0.5:>9.2f}")
print(f"{'watermelon':<10}{1:>5}{4.25:>9.2f}")
item qty price
apple 3 0.50
watermelon 1 4.25
The header uses the same widths as the rows, which is what makes it line up.
Conversions: !r and !s
Before formatting, you can convert the value: !s uses str() (the default), !r uses repr(). repr is the programmer's view: strings get quotes and invisible characters become escapes.
line = "ada \n"
print(f"value: {line}|")
print(f"value: {line!r}|")
value: ada
|
value: 'ada \n'|
The second line reveals the trailing space and newline at once.
The = debugging form
Adding = after an expression prints the expression's text, an equals sign, and its repr. It is the fastest way to inspect values while debugging:
x = 7
name = "ada"
print(f"{x=}")
print(f"{x * 2 = }")
print(f"{name=}")
print(f"{x=:05}")
x=7
x * 2 = 14
name='ada'
x=00007
Spaces around = are kept in the output. Strings show their quotes because = uses repr unless you give a format spec.
Older styles you will meet
Older code uses two other formatting styles. You should recognize them:
print("{} is {} years old".format("Ada", 36))
print("%s is %d years old" % ("Ada", 36))
str.format uses the same format-spec mini-language as f-strings ("{:.2f}".format(pi)), and is still useful when the template is stored separately from the values. The % style comes from C's printf and survives in some libraries, notably logging. For new code, prefer f-strings.
Common mistakes
- Forgetting the
f."{name} is here"prints the braces literally:{name} is here. - Quotes inside braces. Before Python 3.12, you could not reuse the outer quote character inside the braces. In 3.12 and later,
f"{d["key"]}"works, but using the other quote kind (f"{d['key']}") remains clearer and works everywhere. - Wrong spec for the type.
f"{'abc':.2f}"raisesValueError: Unknown format code 'f' for object of type 'str'. Convert input to a number first:float(text). - Expecting truncation.
f"{'watermelon':5}"still prints all ten characters; width never cuts. - Rounding surprises.
f"{0.125:.2f}"gives0.12. Python rounds halfway cases to the even digit when the binary value is exactly halfway, and otherwise follows the true stored value.
Recap
f"...{expr}..."evaluates each expression when the line runs and inserts its text.- After a colon, the format spec controls fill, alignment (
<,>,^), width, grouping (,_), precision (.2f) and type (d,f,e,%,x,b). !rshows the repr, revealing quotes and hidden whitespace;{expr=}prints the expression and its value for debugging.- Double braces
{{}}produce literal braces.
Going further: read "Format Specification Mini-Language" in the Python library docs (under string). Then try formatting the current date: import datetime; print(f"{datetime.date.today():%A %d %B %Y}"). Objects can define their own format spec language, and dates do.