F-strings or how to make the code a little faster and more readable





Python has 3 ways to format strings, and one of them is better than the others. But let's not get ahead of ourselves - what kind of formatting are we talking about? Every time we want to greet a user by name, we need to insert a string with a name in the template string. Most useful log entries also contain variable values. And here is an 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}')
      
      





The first way, formatting with the% operator, came in Python from C - it imitates the printf function. This method was the first in python, and remains the only one (discussed in the article) in Python version 2.5 and below.



The second way is the str.format method, which belongs to the built-in string class. It came with Python 3.0, and has been ported to version 2.6. This method has been recommended as having a richer syntax.



The third method, f-string, appeared in Python version 3.6. As explained in PEP-0498 , the creation of a new way to format strings was motivated by the shortcomings of existing methods, which the authors characterize as error prone, not flexible enough, and not elegant:

This PEP is driven by the desire to have a simpler way to format strings in Python. The existing ways of formatting are either error prone, inflexible, or cumbersome.
So, we have three ways to solve one problem. But maybe this is a matter of personal taste and preference? Perhaps, but the style of your code (especially the code in a project with a large number of participants) will definitely benefit from uniformity. In the best case, you should use one method of formatting strings, then it will become easier to read the code. But which method to choose? And is there a difference in code performance?



Let's try to answer the question about performance experimentally:



 import timeit setup = """ integer = 42 string = 'FORTY_TWO' """.strip() percent_stmt ="'Number %s or simply %d' % (string, integer)" call_stmt = "'Number {} or simply {}'.format(string, integer)" fstr_stmt = """f'Number {string} or simply {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)}")
      
      





Results on MacBook with Python 3.7:



 Timing percent formating: | 2.025 Timing call formating: | 2.943 Timing f-string formating: | 1.348
      
      





The difference is significant. So now, run regex search on ".format" and rewrite hundreds of expressions? In principle, the task is simple but time-consuming. Plus the chance to make a mistake and put a bug in the previously working code! There seems to be room for automation. Indeed, there are libraries that can convert most expressions to f-strings: flynt , pyupgrade .



Flynt is easy to use. For example, run the conversion on the flask source code:



 38f9d3a65222:~ ikkamens$ git clone https://github.com/pallets/flask.git Cloning into 'flask'... ... Resolving deltas: 100% (12203/12203), done. 38f9d3a65222:~ ikkamens$ flynt flask Flynt run has finished. Stats: Execution time: 0.623s Files modified: 18 Expressions transformed: 43 Character count reduction: 241 (0.04%) _-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_. Please run your tests before commiting. Report bugs as github issues at: https://github.com/ikamensh/flynt Thank you for using flynt! Fstringify more projects and recommend it to your colleagues! _-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_. 38f9d3a65222:~ ikkamens$
      
      





It is also worth noting the possibility of converting expressions that occupy several lines, and collecting statistics about the changes made. The flag --line_length XX defines the line length limit after conversion. Flynt allows you to call pyupgrade with the --upgrade flag.



Pyupgrade includes more functionality, and can clean your code from many Python 2 artifacts - such as inheriting from object, specifying class names in super, and much more . Pyupgrade is designed for use with pre-commit , a utility for automatically modifying code before commits.



It is better to convert the source in a gita or other version control. Itโ€™s worth running the tests and looking at the changes yourself (using git diff or PyCharm environments). As long as we live among those who care, that the code has become a couple of characters shorter, proactive conversion will also save their time. After all, sooner or later someone will start to do with their hands what can be made a utility. F-strings only work on Python 3.6+, but soon this will not be a problem since other versions will become obsolete .



It is worth noting that completely abandoning the classic .format method will fail. In the case when you use the same template to create messages with different variables in different places in the code, you should save this template in a variable and use it - the principle โ€œDon't repeat yourselfโ€ is much more important than winning nanoseconds from formatting a string.



Findings:



We looked at the three string formatting methods available in Python 3.6+, their brief history, and compared their performance. We also examined the existing open-source utilities for automatically converting code to a new method for formatting strings, and their additional functions. Don't forget the simple things in your code, and good luck!



All Articles