Python f-strings Are More Powerful Than You Might Think
Tags: python, tutorial, programming, beginners
Published: 2022-04-04 19:15:47

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?

```python
import datetime
today = datetime.datetime.today()
print(f"{today:%Y-%m-%d}")
# 2022-03-11
print(f"{today:%Y}")
# 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](https://manpages.debian.org/bullseye/manpages-dev/strftime.3.en.html) 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:

```python
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:

```python

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")
print(f"{user}")
# John Doe
print(f"{user!r}")
# 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
# 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](https://docs.python.org/3/library/string.html#formatspec), so you can embed a lot of formatting operations into their modifiers:

```python
text = "hello world"

# Center text:
print(f"{text:^15}")
# '  hello world  '

number = 1234567890
# Set separator
print(f"{number:,}")
# 1,234,567,890

number = 123
# Add leading zeros
print(f"{number:08}")
# 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:

```python
number = 254.3463
print(f"{f'${number:.3f}':>10s}")
# '  $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:

```python
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:

```python
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:

```python
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_.