Python f-strings Are More Powerful Than You Might Think

Formatted string literals - also called f-strings - have been around since Python 3.6, so we all know what they are and how to use them. There are however some facts and handy features of f-string that you might not know about. So, let's take a tour of some awesome f-string features that you'll want to use in your everyday coding.

Date and Time Formatting

Applying number formatting with f-strings is pretty common, but did you know that you can also format dates and timestamp strings?

import datetime
today =
# 2022-03-11
# 2022

f-strings can format date and time as if you used datetime.strftime method. This is extra nice, when you realize that there are more formats than just the few mentioned in the docs. Python's strftime supports also all the formats supported by the underlying C implementation, which might vary by platform and that's why it's not mentioned in docs. With that said you can take advantage of these formats anyway and use for example %F, which is an equivalent of %Y-%m-%d or %T which is an equivalent of %H:%M:%S, also worth mentioning are %x and %X which are locales preferred date and time formats respectively. Usage of these formats is obviously not limited to f-strings. Refer to the Linux manpages for full list of formats.

Variable Names and Debugging

One of the more recent additions to f-string features (starting with Python 3.8) is ability to print variable names along with the value:

x = 10
y = 25
print(f"x = {x}, y = {y}")
# x = 10, y = 25
print(f"{x = }, {y = }")  # Better! (3.8+)
# x = 10, y = 25

print(f"{x = :.3f}")
# x = 10.000

This feature is called "debugging" and can be applied in combination with other modifiers. It also preserves whitespaces, so f"{x = }" and f"{x=}" will produce different strings.

__repr__ and __str__

When printing class instances, __str__ method of the class is used by default for string representation. If we however want to force usage of __repr__, we can use the !r conversion flag:

class User:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

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

    def __repr__(self):
        return f"User's name is: {self.first_name} {self.last_name}"

user = User("John", "Doe")
# John Doe
# User's name is: John Doe

We could also just call repr(some_var) inside the f-string, but using the conversion flag is a nice native and concise solution.

Superior Performance

Powerful features and syntax sugar oftentimes comes with performance penalty, that's however not the case when it comes to f-strings:

# python -m timeit -s 'x, y = "Hello", "World"' 'f"{x} {y}"'
from string import Template

x, y = "Hello", "World"

print(f"{x} {y}")  # 39.6 nsec per loop - Fast!
print(x + " " + y)  # 43.5 nsec per loop
print(" ".join((x, y)))  # 58.1 nsec per loop
print("%s %s" % (x, y))  # 103 nsec per loop
print("{} {}".format(x, y))  # 141 nsec per loop
print(Template("$x $y").substitute(x=x, y=y))  # 1.24 usec per loop - Slow!

The above samples were tested with timeit module like so: python -m timeit -s 'x, y = "Hello", "World"' 'f"{x} {y}"' and as you can see f-strings are actually the fastest of all formatting options Python provides. So, even if you prefer using some of the older formatting options, you might consider switching to f-strings just for the performance boost.

Full Power of Formatting Spec

F-strings support Python's Format Specification Mini-Language, so you can embed a lot of formatting operations into their modifiers:

text = "hello world"

# Center text:
# '  hello world  '

number = 1234567890
# Set separator
# 1,234,567,890

number = 123
# Add leading zeros
# 00000123

Python's formatting mini-language includes much more than just the options to format numbers and dates. It allows us to align or center text, add leading zeros/spaces, set thousands separator and more. All this is obviously available not just for f-strings, but for all the other formatting options too.

Nested F-Strings

If basic f-strings aren't good enough for your formatting needs you can even nest them into each other:

number = 254.3463
# '  $254.346'

You can embed f-strings inside f-strings for tricky formatting problems like adding a dollar sign to a right aligned float, as shown above.

Nested f-strings can also be used in case you need to use variables in the format specifier part. This can also make the f-string more readable:

import decimal
width = 8
precision = 3
value = decimal.Decimal("42.12345")
print(f"output: {value:{width}.{precision}}")
# 'output:     42.1'

Conditionals Formatting

Building on top of the above example with nested f-strings, we can go a bit farther and use ternary conditional operators inside the inner f-string:

import decimal
value = decimal.Decimal("42.12345")
print(f'Result: {value:{"4.3" if value < 100 else "8.3"}}')
# Result: 42.1
value = decimal.Decimal("142.12345")
print(f'Result: {value:{"4.2" if value < 100 else "8.3"}}')
# Result:      142

This can become very unreadable very quickly, so you might want to break it into multiple lines instead.

Lambda Expressions

If you want to push limits of f-strings and also make whoever reads your code angry, then - with a little bit of effort - you can also use lambdas:

print(f"{(lambda x: x**2)(3)}")
# 9

Parenthesis around the lambda expression are in this case mandatory, because of the :, which would be otherwise interpreted by f-string.

Closing Thoughts

As we've seen here, f-strings really are quite powerful and have many more features than most people think. Most of these "unknown" features are however mentioned in Python docs, so I do recommend reading through docs pages of not just f-strings, but any other module/feature of Python you might be using. Diving into the docs will oftentimes help you uncover some very useful features that you won't find even when digging through StackOverflow.