f-strings Containing Python Code

Learn how to use Python f-strings and explore format specifiers for versatile string formatting.

Overview

We aren’t restricted to interpolating the values of simple string variables into an f-string template. Any primitives, such as integers or floats, can be formatted. More interestingly, complex objects, including lists, tuples, dictionaries, and arbitrary objects, can be used, and we can access indexes and variables or call functions on those objects from within the format string.

Example

For example, if our email message had grouped the From and To email addresses into a tuple and placed the subject and message in a dictionary, for some reason (perhaps because that’s the input required for an existing send_mail function we want to use), we can format it like this:

Press + to interact
emails = ("steve@example.com", "dusty@example.com")
message = {
"subject": "Next Chapter",
"message": "Here's the next chapter to review!",
}
formatted = f"""
From: <{emails[0]}>
To: <{emails[1]}>
Subject: {message['subject']} ...
{message['message']}
"""

The variables inside the braces in the template string look a little weird, so let’s look at what they’re doing. The two email addresses are looked up by the expression emails[x], where x is either 0 or 1. This is an ordinary tuple indexing operation, so emails[0] refers to the first item in the emails tuple. Similarly, the expression message['subject'] gets an item from a dictionary.

This works out particularly well when we have a more complex object to display. We can extract object attributes and properties and even call methods inside the f-string. Let’s change our email message data once again, this time to a class:

Press + to interact
class Notification:
def __init__(
self,
from_addr: str,
to_addr: str,
subject: str,
message: str
) -> None:
self.from_addr = from_addr
self.to_addr = to_addr
self.subject = subject
self._message = message
def message(self):
return self._message

Here’s an instance of the Notification class:

Press + to interact
email = Notification( "dusty@example.com",
"steve@example.com",
"Comments on the Chapter",
"Can we emphasize Python 3.9 type hints?", )

We can use this email instance to fill in an f-string as follows:

Press + to interact
formatted = f"""
From: <{email.from_addr}>
To: <{email.to_addr}>
Subject: {email.subject}
{email.message()}
"""

Pretty much any Python code that we would expect to return a string (or a value that can convert to a string with the str() function) can be executed inside an f-string. As an example of how powerful it can get, we can even use a list comprehension or ternary operator in a format string parameter:

Press + to interact
f"{[2*a+1 for a in range(5)]}"
'[1, 3, 5, 7, 9]'
for n in range(1, 5):
print(f"{'fizz' if n % 3 == 0 else n}")

In some cases, we’ll want to include a label on the value. This is great for debugging; we can add an = suffix to the expression. It looks like this:

Press + to interact
a = 5
b = 7
print(f"{a=}, {b=}, {31*a//42*b + b=}")

This technique creates a label and a value for us. It can be very helpful. Of course, there are a number of more sophisticated formatting options available to us.

Making it look right

It’s nice to be able to include variables in template strings, but sometimes the variables need a bit of coercion to make them look the way we want them to in the output. We’re planning a sailing trip around the Chesapeake Bay. Starting from Annapolis, we want to visit Saint Michaels, Oxford, and Cambridge. To do this, we’ll need to know the distances among these sailing ports. Here’s a useful distance computation for relatively short distances. First, the formal math, because that can help explain the code:

This follows the same pattern as the hypotenuse of a triangle computation.

There are some differences, which are important:

  • We wrote ΔΦ for the differences in the north-south latitudes, converted
...