[Python String] f-Strings: A New and Improved Way to String Interpolation in Python

f-Strings: A New and Improved Way to String Interpolation in Python

Also called “formatted string literals,” f-strings are string literals that have an f at the beginning and curly braces containing expressions that will be replaced with their values. The expressions are evaluated at runtime and then formatted using the __format__ protocol. As always, the Python docs are your friend when you want to learn more.

Here are some of the ways f-strings can make your life easier.

Simple Syntax

The syntax is similar to the one you used with str.format() but less verbose. Look at how easily readable this is:

1
2
3
4
>>> name = "Eric"
>>> age = 74
>>> f"Hello, {name}. You are {age}."
'Hello, Eric. You are 74.'

It would also be valid to use a capital letter F:

1
2
>>> F"Hello, {name}. You are {age}."
'Hello, Eric. You are 74.'

Do you love f-strings yet? I hope that, by the end of this article, you’ll answer >>> F"Yes!".

Arbitrary Expressions

Because f-strings are evaluated at runtime, you can put any and all valid Python expressions in them. This allows you to do some nifty things.

You could do something pretty straightforward, like this:

1
2
>>> f"{2 * 37}"
'74'

But you could also call functions. Here’s an example:

1
2
3
4
5
6
>>> def to_lowercase(input):
... return input.lower()

>>> name = "Eric Idle"
>>> f"{to_lowercase(name)} is funny."
'eric idle is funny.'

You also have the option of calling a method directly:

1
2
>>> f"{name.lower()} is funny."
'eric idle is funny.'

You could even use objects created from classes with f-strings. Imagine you had the following class:

1
2
3
4
5
6
7
8
9
10
11
class Comedian:
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age

def __str__(self):
return f"{self.first_name} {self.last_name} is {self.age}."

def __repr__(self):
return f"{self.first_name} {self.last_name} is {self.age}. Surprise!"

You’d be able to do this:

1
2
3
>>> new_comedian = Comedian("Eric", "Idle", "74")
>>> f"{new_comedian}"
'Eric Idle is 74.'

The __str__() and __repr__() methods deal with how objects are presented as strings, so you’ll need to make sure you include at least one of those methods in your class definition. If you have to pick one, go with __repr__() because it can be used in place of __str__().

The string returned by __str__() is the informal string representation of an object and should be readable. The string returned by __repr__() is the official representation and should be unambiguous. Calling str() and repr() is preferable to using __str__() and __repr__() directly.

By default, f-strings will use __str__(), but you can make sure they use __repr__() if you include the conversion flag !r:

1
2
3
4
>>> f"{new_comedian}"
'Eric Idle is 74.'
>>> f"{new_comedian!r}"
'Eric Idle is 74. Surprise!'

If you’d like to read some of the conversation that resulted in f-strings supporting full Python expressions, you can do so here.

Multiline f-Strings

You can have multiline strings:

1
2
3
4
5
6
7
8
9
10
>>> name = "Eric"
>>> profession = "comedian"
>>> affiliation = "Monty Python"
>>> message = (
... f"Hi {name}. "
... f"You are a {profession}. "
... f"You were in {affiliation}."
... )
>>> message
'Hi Eric. You are a comedian. You were in Monty Python.'

But remember that you need to place an f in front of each line of a multiline string. The following code won’t work:

1
2
3
4
5
6
7
8
message = (
>>> message = (
... f"Hi {name}. "
... "You are a {profession}. "
... "You were in {affiliation}."
... )
>>> message
'Hi Eric. You are a {profession}. You were in {affiliation}.'

If you don’t put an f in front of each individual line, then you’ll just have regular, old, garden-variety strings and not shiny, new, fancy f-strings.

If you want to spread strings over multiple lines, you also have the option of escaping a return with a :

1
2
3
4
5
6
7
message = (
>>> message = f"Hi {name}. " \
... f"You are a {profession}. " \
... f"You were in {affiliation}."
...
>>> message
'Hi Eric. You are a comedian. You were in Monty Python.'

But this is what will happen if you use “”":

1
2
3
4
5
6
7
8
9
message = (
>>> message = f"""
... Hi {name}.
... You are a {profession}.
... You were in {affiliation}.
... """
...
>>> message
'\n Hi Eric.\n You are a comedian.\n You were in Monty Python.\n'

Read up on indentation guidelines in PEP 8.

Speed

The f in f-strings may as well stand for “fast.”

f-strings are faster than both %-formatting and str.format(). As you already saw, f-strings are expressions evaluated at runtime rather than constant values. Here’s an excerpt from the docs:

“F-strings provide a way to embed expressions inside string literals, using a minimal syntax. It should be noted that an f-string is really an expression evaluated at run time, not a constant value. In Python source code, an f-string is a literal string, prefixed with f, which contains expressions inside braces. The expressions are replaced with their values.” (Source)

At runtime, the expression inside the curly braces is evaluated in its own scope and then put together with the string literal part of the f-string. The resulting string is then returned. That’s all it takes.

Here’s a speed comparison:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> import timeit
>>> timeit.timeit("""name = "Eric"
... age = 74
... '%s is %s.' % (name, age)""", number = 10000)
0.003324444866599663

>>> timeit.timeit("""name = "Eric"
... age = 74
... '{} is {}.'.format(name, age)""", number = 10000)
0.004242089427570761

>>> timeit.timeit("""name = "Eric"
... age = 74
... f'{name} is {age}.'""", number = 10000)
0.0024820892040722242

As you can see, f-strings come out on top.

However, that wasn’t always the case. When they were first implemented, they had some speed issues and needed to be made faster than str.format(). A special BUILD_STRING opcode was introduced.

The Pesky Details

Now that you’ve learned all about why f-strings are great, I’m sure you want to get out there and start using them. Here are a few details to keep in mind as you venture off into this brave new world.

Quotation Marks

You can use various types of quotation marks inside the expressions. Just make sure you are not using the same type of quotation mark on the outside of the f-string as you are using in the expression.

This code will work:

1
2
>>> f"{'Eric Idle'}"
'Eric Idle'

This code will also work:

1
2
>>> f'{"Eric Idle"}'
'Eric Idle'

You can also use triple quotes:

1
2
3
4
5
>>> f"""Eric Idle"""
'Eric Idle'

>>> f'''Eric Idle'''
'Eric Idle'

If you find you need to use the same type of quotation mark on both the inside and the outside of the string, then you can escape with \:

1
2
>>> f"The \"comedian\" is {name}, aged {age}."
'The "comedian" is Eric Idle, aged 74.'

Dictionaries

Speaking of quotation marks, watch out when you are working with dictionaries. If you are going to use single quotation marks for the keys of the dictionary, then remember to make sure you’re using double quotation marks for the f-strings containing the keys.

This will work:

1
2
3
>>> comedian = {'name': 'Eric Idle', 'age': 74}
>>> f"The comedian is {comedian['name']}, aged {comedian['age']}."
The comedian is Eric Idle, aged 74.

But this will be a hot mess with a syntax error:

1
2
3
4
5
6
>>> comedian = {'name': 'Eric Idle', 'age': 74}
>>> f'The comedian is {comedian['name']}, aged {comedian['age']}.'
File "<stdin>", line 1
f'The comedian is {comedian['name']}, aged {comedian['age']}.'
^
SyntaxError: invalid syntax

If you use the same type of quotation mark around the dictionary keys as you do on the outside of the f-string, then the quotation mark at the beginning of the first dictionary key will be interpreted as the end of the string.

Braces

In order to make a brace appear in your string, you must use double braces:

1
2
>>> f"{{70 + 4}}"
'{70 + 4}'

Note that using triple braces will result in there being only single braces in your string:

1
2
>>> f"{{{70 + 4}}}"
'{74}'

However, you can get more braces to show if you use more than triple braces:

1
2
>>> f"{{{{70 + 4}}}}"
'{{70 + 4}}'

Backslashes

As you saw earlier, it is possible for you to use backslash escapes in the string portion of an f-string. However, you can’t use backslashes to escape in the expression part of an f-string:

1
2
3
4
5
>>> f"{\"Eric Idle\"}"
File "<stdin>", line 1
f"{\"Eric Idle\"}"
^
SyntaxError: f-string expression part cannot include a backslash

You can work around this by evaluating the expression beforehand and using the result in the f-string:

1
2
3
>>> name = "Eric Idle"
>>> f"{name}"
'Eric Idle'

Inline Comments

Expressions should not include comments using the # symbol. You’ll get a syntax error:

1
2
3
4
5
>>> f"Eric is {2 * 37 #Oh my!}."
File "<stdin>", line 1
f"Eric is {2 * 37 #Oh my!}."
^
SyntaxError: f-string expression part cannot include '#'

Format String

The following example formats datetime, float, width and justify.

Format datetime

The example displays a formatted current datetime. The datetime format specifiers follow the : character.

1
2
3
4
>>> import datetime
>>> now = datetime.datetime.now()
>>> print(f'{now:%Y-%m-%d %H:%M}')
2019-05-11 22:39

Format floats

Floating point values have the f suffix. We can also specify the precision: the number of decimal places. The precision is a value that goes right after the dot character.

The example prints a formatted floating point value. The output shows the number having two and five decimal places.

1
2
3
4
5
>>> val = 12.3
>>> print(f'{val:.2f}')
12.30
>>> print(f'{val:.5f}')
12.30000

Format width

The width specifier sets the width of the value. The value may be filled with spaces or other characters if the value is shorter than the specified width.

The example prints three columns. Each of the columns has a predefined width. The first column uses 0 to fill shorter values. This is the output.

1
2
3
4
5
6
7
8
9
10
11
12
>>> for x in range(1, 11):
print(f'{x:02} {x*x:3} {x*x*x:4}')
01 1 1
02 4 8
03 9 27
04 16 64
05 25 125
06 36 216
07 49 343
08 64 512
09 81 729
10 100 1000

Justify string

By default, the strings are justified to the left. We can use the > character to justify the strings to the right. The > character follows the colon character.

We have four strings of different length. We set the width of the output to ten characters. The values are justified to the right.

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> s1 = 'a'
>>> s2 = 'ab'
>>> s3 = 'abc'
>>> s4 = 'abcd'

>>> print(f'{s1:>10}')
>>> print(f'{s2:>10}')
>>> print(f'{s3:>10}')
>>> print(f'{s4:>10}')
a
ab
abc
abcd

Numeric notations

Numbers can have various numeric notations, such as decadic or hexadecimal.

The example prints a value in three different notations.

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> a = 300

>>> # hexadecimal
>>> print(f"{a:x}")

>>> # octal
print(f"{a:o}")

>>> # scientific
>>> print(f"{a:e}")
12c
454
3.000000e+02

References

[1] PEP 498 – Literal String Interpolation | Python.org - https://www.python.org/dev/peps/pep-0498/

[2] Python String Formatting Best Practices – Real Python - https://realpython.com/python-string-formatting/

[3] Python 3’s f-Strings: An Improved String Formatting Syntax (Guide) – Real Python - https://realpython.com/python-f-strings/

[4] Python String Interpolation - https://www.programiz.com/python-programming/string-interpolation

[5] Python String Interpolation. Learn the basics of string… | by Iffat Malik Gore | Towards Data Science - https://towardsdatascience.com/python-string-interpolation-829e14e1fc75

[6] Python f-string - formatting strings in Python with f-string - https://zetcode.com/python/fstring/