The f-strings in Python3.6
Updates:
- 2024-04-19: This post is completely rewritten for better reference.
Intro
String formatting can be regarded as one of the most common activities in daily programming. We often need to output various strings and precisely control their format.
You may still see the usage of %
to format strings in some outdated Python tutorials. However, the f-strings have become the optimal choice for formatting strings since Python3.6. The main advantages in my opinion are:
- Expressions can be embedded in the string literals conveniently
- More human-readable
The following simple comparison shows the advantages of f-strings in readability:
name = "Martin"
f"My name is {name}"
'My name is Martin'
name = "Martin"
"My name is %s" % name
'My name is Martin'
We implemented the same string output in two different ways.
Without any effort, we can find that the f-strings here are to output the name (this requires you to use meaningful variable names, such as name
here). On the contrary, it is slightly unnatural to look at the %
format string. We first saw it use %s
as a placeholder, then we need to go to the right to find out which variable it refers to.
The example here is too short to show the real power of f-strings. Just trying to imagine that we are reading a long format string written in %
syntax, we will need to look it back and forth to figure out what it means. Not just about reading the code, if you write a long format string by yourself, you have to check whether the order of the placeholders and variables is consistent, which is undoubtedly a burden. 😫
Syntax rules
To create a f-string, we first add the leading f
before "..."
or '...'
.
f"..."
# or f'...'
Then, we can use {expression}
inside the quotes. Python will evaluate this expression
for us and print its corresponding value.
If we want to get more control over the output, we can use the format_specifier
, which contains various optional parts for different purposes.
From the perspective of grammar, the syntax rule of f-strings is
f"...{expression:format_specifier}..."
Now let’s figure out how to use f-strings in various scenarios
Usage
Basic usage
left, right = 3, 5
f"{left} + {right} = {left + right}"
'3 + 5 = 8'
{}
as a placeholder naturally leads to a question: what if we want to display {}
in the string? The answer is very simple, we just need to add another {}
, i.e. {{}}
f"{{}}"
'{}'
It is also worth mentioning that :
and !
and \
are not allowed in expression
These characters rarely appear in expression
though. One usage scenario of \
is to escape quotes. However, we could just use a different type of quote inside the expression
. As for :
, a use case would be defining a lambda function in expression
1. In this situation, we only need to enclose the lambda expression within ()
:
# Note that we need to add () around the lambda expression
f"{(lambda x: x + 1)(3)}"
'4'
Different string representation
In Python, objects generally have two types of string representations, corresponding to the __str__
and __repr__
methods, where the former is typically user-facing and more readable, while the latter is typically developer-facing and aids in debugging programs. The f-strings allowes us to specify which one to use.
{foo!s} # it's equal to call str(foo) first
{foo!r} # similarily, call repr(foo) first
{foo!a} # similarily,call ascii(foo) first
Padding & Alignment
The padding and alignment involve the following issues:
- Which character should we use? Here, we use
fill
to represent that. - To what length should it be padded? Here, we use
width
to represent that. - How should it be aligned? Here, we use
align
to represent that.
The general format_specifier for padding and alignment is [[fill]align][width]
.
[]
indicates that this part is optional.The various ways of alignment options:
< # left-aligned
> # right-aligned
^ # centered
= # only valid for numeric types, pad between sign and digits
For example
f"{-1:*^9}" # set `fill` to *, and set width to 9
'***-1****'
f"{-1:*>9}" # set `fill` to *, and set width to 9
'*******-1'
f"{-1:*<9}" # set `fill` to *, and set width to 9
'-1*******'
f"{-1:*=9}" # set `fill` to *, and set width to 9
'-*******1'
Sign
It decides how the sign should be represented and is only valid for numerical values.
+ # both positive and negative
- # only negative (default)
space # a leading space for positive and minus sign for negative
f"{1:+}"
'+1'
f"{-1:+}"
'-1'
assert f"{1}" == f"{1:-}" # because it's the default behavior
The precision of float-point numbers
We can use .nf
to keep n
decimal places for float-point numbers, which is somewhat similar to C/C++’s printf
. For example
x = 1.23456
f"{x:.1f}"
'1.2'
x = 1.23456
f"{x:.3f}"
'1.235'
The -0.0
issue
We can use z
to handle negative zero (i.e. -0.
) since Python3.11. According to the PEP2, programmers usually will suppress negative zero.
x = -0.0001
f"{x:.1f}" # set the precision to 1, so it will round to -0.0
'-0.0'
x = -0.0001
f"{x:z.1f}" # with z, we will get 0.0 rather than -0.0
'0.0'
Other number representation
In Python, we can use the built-in functions to get the representation in other bases including bin
and oct
. You may attempt to use {bin(a)}
to get the binary representation output of a
. However, the f-strings offers a more elegant way to handle this
b # base 2
o # base 8
d # base 10
x # base 16, low-case letters
X # base 16, upper-case letters
#
character will add the corresponding prefix (e.g. 0b
)f"{15:b}" # represent 15 in base 2
'1111'
f"{15:#b}" # represent 15 in base 2, use # to add prefix 0b
'0b1111'
f"{15:#X}" # represent 15 in base 16, use # to add prefix 0X
'0XF'
Thousands separator
Specify the thousands separators. Two options available34:
_
,
Both separators will make the long numbers more human-readable.
f"{123456789:,}"
'123,456,789'
f"{1234.56789:,}"
'1,234.56789'
f"{123456789:_}"
'123_456_789'
f"{1234.56789:_}"
'1_234.56789'
Trick
We can use {expression=}
for self-documenting expression since Python3.85.
How could it be helpful? As a print debugger, I usually print values of variables to debug a Python program. It’s kind of annoying that every time I need to write the variable name twice.
f"left={left}, right={right}"
'left=3, right=5'
Now we can write
f"{left=}, {right=}"
'left=3, right=5'
Wrap-up
When I started to learn Python a long time ago, most of the tutorials said we should use %
to format strings. After str.format
appeared, it became a better choice and outdated the %
syntax. F-strings, released with Python3.6, has become the best practice now.🚀 It meets the zen of Python - There should be one– and preferably only one –obvious way to do it