String Formatting with Python 3's f-Strings

Introduction

Python 3.6 introduced a new way to format strings: f-Strings. It is faster than other string formatting methods in Python, and they allow us to evaluate Python expressions inside a string.

In this post, we'll look at the various ways we can format strings in Python. Then we'll have a deeper look at f-Strings, looking at how we can use it when displaying different data.

Traditional String Formatting in Python

Before we get into f-Strings, let's have a look at the more "traditional" string formatting options available in Python. If you just want to skip to learning about f-Strings, check out the section String Formatting with f-Strings in this article.

String Concatenation

String concatenation means that we are combining two strings to make a new one. In Python, we typically concatenate strings with the + operator.

In your Python interpreter, let's use concatenation to include a variable in a string:

name = "Python"
print("I like " + name + " very much!")

You would see the following output:

I like Python very much!

String concatenation works only on strings. If you want non-string data to be included, you have to convert it manually.

Let's see by concatenating a number with a string:

age = (10 + 5) * 2
print("You wouldn't believe me, but I am only " + age + " years old")

Running the above you'll see this error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

Now, let's convert age to a string:

age = (10 + 5) * 2
print("You wouldn't believe me, but I am only " + str(age) + " years old")

The interpreter would correctly show:

You wouldn't believe me, but I am only 30 years old

This method is not always the quickest for Python to evaluate, nor is it easy for humans to manage with many variables. It's better to use Python's dedicated string formatting methods.

C-Style String Formatting

C-style string formatting, also referred to as the printf style, uses strings as templates that are marked with % so that variables can be substituted.

For example, %d tells Python that we are substituting a number, whereas %s tells Python that we are substituting a string.

There are a few controlling mechanisms available too, for example %02d ensures that the number is at least 2 digits, and if it is not, the rest will be filled by zeros. We can use a marker like %.4f to substitute a decimal with exactly 4 floating points in the string.

In your Python interpreter, type the following code so we can see C-style string formatting in action:

print('Here is an integer %02d' % 4)
print('And a float: %.2f' % 3.141592)
print("And a %s with %d replacements" % ("mess", 2))

Your output should be:

Here is an integer 04
And a float: 3.14
And a mess with 2 replacements

As you could see, with more and more arguments it gets pretty convoluted and hard to follow. So Python takes this one step further and allows you to use dictionaries in % formatting.

Although it isn't much of an improvement in its overall aesthetics, it could help a little bit on the readability front.

Try formatting a C-style string with a dictionary:

print('Using %(dictionary)s format in C-style string formatting, here is %(num)d number to make it more interesting!' % {'dictionary': "named", "num": 1})

As you can see, we can specify the name within parenthesis between the % and the rest of the identifier.

Your interpreter should display:

Using named format in C-style string formatting, here is 1 number to make it more interesting!

If you haven't spent the last 42 years developing in C, the above syntax might not be to your liking. As such, Python gives us another option using the format() method on strings.

Python format() Function

The format() function behaves similarly to the C-style formatting, but it is much more readable. It works by invoking the format() method on your string while providing the replacements in the template within curly brackets - {}.

Using your Python interpreter, we'll use the format() method to format a string. Let's start by substituting one string:

print("This is a first {}".format("attempt!"))

The output will be:

This is a first attempt!

We can use curly brackets multiple times, substituting the variables in order:

print("And {} can add {} string too.".format("we", "more"))

Output:

And we can add more string too.

We can also name the variables we are formatting:

print("String format also supports {dictionary} arguments.".format(dictionary="named"))

The above code should print:

String format also supports named arguments.

While this method is very readable, Python is more efficient with f-Strings, while also being readable. Let's have a look at f-Strings more closely.

String Formatting with f-Strings

F-Strings, or formatted string literals, are prefixed with an f and contain the replacement fields with curly braces. They are evaluated at run-time, which makes them the fastest string formatting option in Python (at least in the standard CPython implementation).

First, verify that you have a Python version that's 3.6 or higher:

$ python3 --version
Python 3.6.0

If your Python version is less than 3.6, you need to upgrade to use this feature.

F-String literals start with an f, followed by any type of strings (i.e. single quotation, double quotation, triple quotation), then you can include your Python expression inside the string, in between curly brackets.

Let's string to use an f-String in your Python shell:

name = "f-String"
print(f"hello {name}!")

And now run it to see the magic:

hello f-String!

It's very similar to using the format() function. However, it comes with a lot of features that are not available with other formatting methods.

Let's have a look at those features, starting with multi-line strings.

Multi-line f-Strings

F-Strings allows for multi-line strings, all you have to do is wrap them in parentheses:

name = "World"
message = (
  f"Hello {name}. "
  "This is from a multi-lined f-string. "
  f"Have a good day, {name}"
)
print(message)

You will see this on printed out:

Hello World. This is from a multi-lined f-string. Have a good day, World

Don't forget to put the f literal in front of lines that have replacements. The substitutions won't work otherwise.

Evaluating Python Expressions in f-Strings

Python's f-Strings allow you to do expressions right in the string itself. The following string is valid:

number = 10
print(f"{number} + 1 = {number+1}")

And outputs:

10 + 1 = 11

You can include any valid expression you want, as long as it doesn't have special characters. For example, we can have function calls:

def func():
  return 42

print(f"func() = {func()}")

This returns:

func() = 42

Formatting Classes with f-Strings

You can include objects created from classes in an f-String. Every class has two different methods that allow an object to be converted to a string:

  • The __str__() method of an object should return a string that is both displayable to the user, and understandable by them. That means it should not have any secret information that should be kept hidden from the user, and it should not have any debug messages.
  • The __repr__() method of an object should return a more detailed string, possibly containing debug information.

f-Strings automatically call the __str__() for the object you provide them.

Let's try our f-Strings with a class to see for ourselves. We will create a User class that stores our name and associated database ID that our imaginary system uses.

Create a new file called fstring_classes.py and add the following:

# fstring_classes.py

class User:
    def __init__(self, name, db_id):
        self.name = name
        self.db_id = db_id

    def __str__(self):
        return f"User with name: {self.name}"

my_user = User("Sajjad", 12)
print(f"{my_user}")

In your Terminal, run the file with Python:

$ python fstring_classes.py

This program will show:

User with name: Sajjad

If we wanted to use an f-String with the __repr_() method of an object, you can put an !r in front of your object being substituted.

Edit the fstring_classes.py file and change the last line:

print(f"{my_user}")

Into the following, which includes the !r suffix:

print(f"{my_user!r}")

Now we can run this program again:

$ python fstring_classes.py

And our output will be:

User with name: Sajjad with id 12

Handling Special Characters in f-Strings

F-Strings, like any other Python string, support the use of backslashes to escape characters. All the following are valid Python f-Strings:

print(f'escaping with \\ is also possible, like: \'')

Running this will print:

escaping with \ is also possible, like: '

However, there is one limitation, we cannot use backslashes within the curly braces (the expression part of the f-String). If you try the following in your Python interpreter:

print(f'{ord("\n")}')

You'll get the following error:

  File "<stdin>", line 1
SyntaxError: f-string expression part cannot include a backslash

The same limitation is true for same-type quotation marks. The code below:

a = {"key": "value"}
print(f"the value is {a["key"]}")

Gives us this syntax error:

  File "<stdin>", line 1
    print(f"the value is {a["key"]}")
                               ^
SyntaxError: invalid syntax

To avoid these problems, we can either calculate them outside of the f-String:

ord_newline = ord("\n")
print(f'{ord_newline}')

Which prints:

10

Or we can use different quotation types:

a = {"key": "val"}
print(f"The value is {a['key']}")

Which displays:

The value is val

Conclusion

In this post, we looked at different methods of string formatting in Python, with a focus on the f-String method. In Python, we are spoiled for options as we can concatenate strings, use C-Style formatting with %, or the format() method on strings.

However, with f-Strings, we get a readable solution that Python has optimized for. Aside from its speed benefits, we've seen how well f-Strings work with evaluating expressions and objects from classes.