F-strings — make your python code more readable today

Ilya Kamen
4 min readJun 29, 2019

--

TL,DR: Use flynt to convert the bulk of string formatting to use new, faster f-string formatting!

Most applications moving beyond prototype phase take on two tasks: interacting with users, and logging. Some applications also construct shell commands, or craft html code on the fly. What do these usecases have in common? They all come down to formatting strings.

We format strings when we want to greet user personally by their name. We format strings when we save current time in logs. It happens fairly often — yet how does your application do it? Does it use the % notation, that takes its inspiration from printf from C? Do you use .format() calls, and count in your head at which position will each argument land? Did you know: f-strings, first introduced in Python3.6 are a faster and more readable alternative. Learn more at realpython’s f-strings article.

Lets look at this example:

integer = 42
string = 'FORTY_TWO'

print('string number %s, or simply %d' % (string, integer))
print('string number {}, or simply {}'.format(string, integer))
print(f'string number {string}, or simply {integer}')

It demonstrates three ways to derive the same string using the different string formatting notations.

Readability

Comparing percent and .format call formatting vs f-string:

  • with f-string, you don’t have to order multiple inputs in your head to see what output will look like
  • Extra parenthesis, and binary operation or function call can become last drop to make a complicated piece just too much to parse. Not that f-string did any magic, but it lacks an extra set of parenthesis.
  • It also uses 6 chars less than percent formatting and 10 chars less than the format call (Hello 79-char line limit).

Of course readability is also a question of what you are used to read. A programmer who has looked at percent formatting for last 15 years of their career is likely not to feel the improvement at first.

Performance

What about performance? lets use ‘timeit’ module to measure that!

import timeitsetup = """
integer = 42
string = 'FORTY_TWO'
"""
.strip()

percent_stmt = "'Number %s aka %d' % (string, integer)"
call_stmt = "'Number {} aka {}'.format(string, integer)"
fstr_stmt = """f'Number {string} aka {integer}'"""

def
time(stmt):
return f"{timeit.timeit(stmt, setup, number=int(1e7)):.3f}"

print(f"Timing percent formating: | {time(percent_stmt)}" )
print(f"Timing call formating: | {time(call_stmt)}")
print(f"Timing f-string formating: | {time(fstr_stmt)}")

Output:

Timing percent formating:    |  2.171 s
Timing call formating: | 3.163 s
Timing f-string formating: | 1.391 s

Note: we run 10 million calls for each statement, so it is not that embarrassing to take a second or two for this.

How to get there

For a programmer it is usually trivial to translate one statement into another. A bit of thinking, 3 minutes of careful copy-pasting arguments and removing the extras and voila! But what happens when your program has hundreds of format statements? Humans make mistakes, and most cases are trivial, so why not automate it?

I was there and tried to convert 10 KLOC application to use the f-strings. What started as “cool, I found an easy way to make our code a bit better” soon turned to be mundane work that was also hard on others to review. It takes concentration and precision to keep doing conversions right. As I always wanted to learn how python AST module works, it was a good opportunity to dive into it and build something.

Result: flynt package for python, to be found at https://github.com/ikamensh/flynt

To use it, you need a 3.6 python interpreter. it is as easy as

pip install flynt
flynt /my/path/to/src

Attention: Flynt will modify files in-place. This is intended for easy workflow with git or other version control tool, as you will be able to see the diff right away. Run your tests, and verify that you are OK with the changes.

You will see output like:

fstringifying /Users/ikkamens/Myproj/src/integration_tests/tests.py…yes

Flynt run has finished. Stats:

Execution time: 0.452s
Files modified: 10
Expressions transformed: 54
Character count reduction: 563 (0.14%)

Limitations

Flynt only converts explicit set of easier cases. It will not touch multi-line statements, nor situations where a string is embedded in a string. It was tested on flask source code, and few projects in my company — yet this is far from extensive testing. Report issues and feature requests on github: https://github.com/ikamensh/flynt

F-strings only work on python 3.6+. Python 2 deprecation is coming, (https://pythonclock.org/) and soon this will be the case for all python projects, but we are not there yet.

Summary

If you have a bigger project that uses mix of formatting styles, and your interpreter is 3.6+, then you can easily convert the bulk of those to new f-string notation using flynt.

--

--

Ilya Kamen

Machine Learning Engineer @ Amazon, I don’t represent my employer on medium and opinions are my own.