Algorithms

As discussed in PEP 498, formatted string literals (f-string) is a way to interpolate evaluated expression values into regular string literals, using the syntax f ' <text> { <expression> <optional !s, !r, or !a> <optional : format specifier> } <text> ... '. It is roughly equivalent to first evaluate the value of expression then interpolate its value into the string literal with provided format specifiers and convension.

Basic Concepts

To convert, f2format will first extract all expressions from the f-strings, then rewrite the literal with a str.format function using anonymous positional expression sequence from the extracted expressions.

For example, with the samples from PEP 498:

f'abc{expr1:spec1}{expr2!r:spec2}def{expr3}ghi'

it should be converted to

'abc{:spec1}{:spec2}def{}ghi'.format(expr1, expr2, expr3)

Concatenable Strings

As mentioned in the Python documentation, multiple adjacent string or bytes literals (delimited by whitespace), possibly using different quoting conventions, are allowed, and their meaning is the same as their concatenation.

In cases where a f-string can be found in such sequence of concatenable strings, directly converting the f-string to str.format syntax may cause the concatenation to be broken. Therefore, f2format will rather insert the converted .format call at the end of the string sequence.

For example, a sequence of concatenable strings may be as follows:

('/usr/local/opt/python/bin/python3.7 -c "'
 'import re, sys\n'
 'for line in sys.stdin:\n'
 "    data = line.rstrip().replace('^D\x08\x08', '')\n"
 "    temp = re.sub(r'\x1b\\[[0-9][0-9;]*m', r'', data, flags=re.IGNORECASE)\n"
 f"    text = temp.replace('Password:', 'Password:\\r\\n'){_replace(password)}\n"
 '    if text:\n'
 "        print(text, end='\\r\\n')\n"
 '"')

then f2format will convert the code above as

('/usr/local/opt/python/bin/python3.7 -c "'
 'import re, sys\n'
 'for line in sys.stdin:\n'
 "    data = line.rstrip().replace('^D\x08\x08', '')\n"
 "    temp = re.sub(r'\x1b\\[[0-9][0-9;]*m', r'', data, flags=re.IGNORECASE)\n"
 "    text = temp.replace('Password:', 'Password:\\r\\n'){}\n"
 '    if text:\n'
 "        print(text, end='\\r\\n')\n"
 '"'.format(_replace(password)))

Debug F-Strings

Since Python 3.8, = was introduced to f-strings in addition to the acceptance of bpo-36817. As discussed in the Python documentation, when the equal sign '=' is provided, the output will have the expression text, the '=' and the evaluated value, therefore f2format tend to keep an original copy of the expressions in the converted strings then append str.format with corresponding expressions.

For a f-string as below:

>>> foo = "bar"
>>> f"{ foo = }" # preserves whitespace
" foo = 'bar'"

f2format will convert it as

" foo = {!r}".format(foo)