When Python old style string formatting is best practice

String formatting in Python 2 utilized the % modulo operator. Python 3 was released in 2008 which included an alternatives to formatting strings: str.format()
. Fast forward to 2016, Python 3.6 formatted string literals (or f-strings) were released.
It may seem like iconoclasm to say str.format
and f strings are not always preferable to old style but Python documentation itself say that old style can be better in cases like logging. This blog post will explain why you shouldn’t use new string formatting when logging.
When to use new string formatting over the old formatting
F-strings are considered by most to be more convenient than the % operator and arguably easier to read. They are also more flexible and can be used to interpolate Python expressions into a string.
As the zen of python states: Readability counts. Python’s new string formatting tool is a great way to improve the readability. The syntax is very similar to the existing string formatting syntax, but there are some essential differences:
# old style. like printf in C
print("Hello, %s!" % "world")
print("The answer is %d" % 42)
print("%f + %" = %" % (1, 2, 3))
You can also use the format() method to format strings. This provides a more flexible syntax and allows using formatting specifications:
print("Hello, {0}!".format("world"))
print("The answer is {0}".format(42))
print("{0} + {1} = {2}".format(1, 2, 3)
Template strings allow you to embed python code directly in your strings, which can be very useful for complex formatting tasks:
import datetime
t = datetime.date(2016, 1, 1)
print("Today is {t.day}/{t.month}/{t.year}".format(t=t))
The new string formatting approach has several advantages:
- It is more flexible and easier to use.
- It supports named arguments, which can make code easier to read.
- It supports complex objects (such as lists and dictionaries), which the old % operator approach does not.
Old % operator approach may be preferable when working with legacy code because consistently bad is preferable to inconsistently good, or (shudder to think) a library that still supports Python 2.7. But in general, it is best to use the new string formatting approach whenever possible.
In defense of old string formatting
Old style of formatting strings can be preferable according to Python logging documentation.
Logging in Python 3
Python’s logging module provides a powerful and flexible platform for emitting log messages from Python programs. The key thing for this string optimization is: not every logger call is emitted to the log depending on logging configuration.
The simplest way to configure logging is to call the basicConfig()
. This will configure the logging module with a default set of parameters. Once the logging module is configured, you can use the various log functions to emit log messages. The most commonly used log functions are debug
, info
, warning
, error
, and exception
. Each function takes a string argument that might be emitted to the log (again, depending on configuration).
The debug
function is used for debugging messages that help the developer when developing the code, while the info
function is for messages that help the developer understand how the code is behaving in production. The warning
, error
, and exception
functions are used for logging problems of increasing levels of severity.
All of these functions take an optional second argument, a dictionary of additional information that will be logged along with the message.
Old vs new when logging
The logging module docs states that f-strings are less optimal because, as the logging documentation states:
Formatting of message arguments is deferred until it cannot be avoided
So for optimization the module prefers to perform evaluation of logged strings as late as possible. Take the examples:
logger.debug("encountered %s", foo) # old style
vs
logger.debug(f"encountered {foo}") # f string
Assume logging configuration is higher than debug (so debug
calls are not emitted to the log). In the first version, the final string will not be computed (because the logging handler will ignore debug logging calls), while in the second version the string is evaluated immediately. As the docs say:
You may want to avoid doing it if the logger will just throw away your event
The reason the logging module suggests this is there may be cases where evaluating the strings are resource intensive: perhaps foo
represents an ORM lazily evaluated queryset and evaluating the string would force the database read. It would be a shame if observing the code negatively affected the system. If logging reduced the performance of the application the developers would use it less, and that would reduce their performance as developers.
Improve your code
Code Review Doctor is a code scanning tool that suggests Python and Django fixes right inside your pull request:

Check your GitHub or Bitbucket Pull Requests, scan your entire codebase for free online, or follow us on Twitter.