Lord of Strings (Part 2)

Lord of Strings (Part 2)

In our previous post, we wrote a program to check sensor data for a home IoT project.  It works, but has a few problems.

In order to fix those problems, we decided we needed a:

  1. Flexible
  2. and Concise
  3. way to substitute parts in a “template string”, While
  4. formatting
  5. and converting those parts
  6. all at the same time as the substitution

If there was such a string formatting solution in Python, we could avoid the “stack and pack” approach seen in our original program.

Before Python 3.6’s introduction of f-strings, the language already had three existing ways of formatting strings.  Yet none of those completely satisfied all 6 criteria above.

%-Formatting

%-formatting is based on C’s  printf() function. While this interface can substitute, convert (between hex, floating point, signed, unsigned, etc . . .) and format (padding, precision) at the same time, only int’s, strings and doubles are supported.  It’s limited, but can work in a pinch.  Take, for example, the math operation in our program which caused all those trailing digits for Sensor 1’s error in the previous post:

>>>error = (5000-float(3000))/3000
>>>print("Sensor 1 reads an error of %0.4s" % error)

‘Sensor 1 reads an error of 0.66’

Since %-formatting doesn’t allow us to insert the error calculation formula directly into our printed string, we still need the error variable.

String.Template

Introduced in Python 2.4, the Template class of the string module provides a way to perform basic string substituitions.  In fact, string substituitions are the only thing this class does, which doesn’t help us much:

>>>from string import Template
>>>s = Template("Sensor 1 reads $error_placeholder")
>>>error = (5000-float(3000))/3000
>>>error_str = str(error)
>>>s.substitute(error_placeholder = error_str)

‘Sensor 1 reads 0.6666666666666666’

Given the extra steps of importing the class and calling the constructor, the fact that we still need string copies of the error values just as in the original program, all for the very limited benefit of avoiding “+” concatenation, string. Template is more trouble than its worth for our sensor error reporting program.

Str.format()

Intended to replace %-formatting, the str.format() method was introduced in Python 3.0. and supports more data types, has more formatting options and even supports a limited number of Python expressions.

Let’s apply str.format() to that same math operation:

>>>"Sensor 1 reads {:.2}".format((5000-float(3000))/3000)

‘Sensor 1 reads 0.67’

With str.format(), we’ve accomplished in one line what %-formatting required two to do.

Here’s what our entire program looks like, after re-writing it to use str.format():

>>>nominal = {0: 200, 1: 3000, 2: 50000}
>>>reported = ["100", "5000", "15"]
>>>for sensor in range(3):
print("Sensor {} reads an error of {:.2}".format(sensor,
(float(reported[sensor])-nominal[sensor])/nominal[sensor]))

Which produces this output:

‘Sensor 0 reads an error of -0.5’
‘Sensor 1 reads an error of 0.67’
‘Sensor 2 reads an error of -1.0’

Since str.format() can take functions as arguments, let’s pull out the error calculation into its own function (err_calc()) which takes as arguments the sensor number and the reported value string.  This will make that last line (which does all the work) much more readable:

print("Sensor {} reads an error of {:.2}".format(sensor,
err_calc(sensor, reported_string_value))

Wouldn’t it be nice if we could put our sensor value and the call to err_calc() into the {} placeholders themselves and therefore eliminate much of the boilerplate required by str.format()?  With f-strings, we can.

F-strings

F-strings are formatted string literals, and they “provide a concise, readable way to include the value of Python expressions inside strings” by building on the foundation that str.format() provides: curly braces ({}), syntax for specifying formatting, and support for the __format__() method of the value being converted to a string.

One key advantage of f-strings over str.format() is that they support full Python expressions in the placeholders, therefore eliminating the separation between the substitution placeholder and the values which insert into them.

As a result, code using f-strings can be more concise than code using str.format().  If however, you already have a variable containing the information you want substituted into the string, you can just drop it in.  This further improves the readability of that last line of our program:

print(f’Sensor {sensor} reads an error of {err_calc(sensor,
rep_str):.2}’)

Conclusion

Python’s f-strings offer a concise and flexible way of simultaneously substituting values into template text strings, and controlling the formatting during their conversion to strings.  Since the other three ways to format strings are still supported, developers have great flexibility when writing their code.

Python truly is the Lord of the Strings.

Copyright © Python People