PEP 498 – Literal String Interpolation | peps.python.org (2024)

Author:
Eric V. Smith <eric at trueblade.com>
Status:
Final
Type:
Standards Track
Created:
01-Aug-2015
Python-Version:
3.6
Post-History:
07-Aug-2015, 30-Aug-2015, 04-Sep-2015, 19-Sep-2015, 06-Nov-2016
Resolution:
Python-Dev message
Table of Contents
  • Abstract
  • Rationale
    • No use of globals() or locals()
  • Specification
    • Escape sequences
    • Code equivalence
    • Expression evaluation
    • Format specifiers
    • Concatenating strings
    • Error handling
    • Leading and trailing whitespace in expressions is ignored
    • Evaluation order of expressions
  • Discussion
    • python-ideas discussion
      • How to denote f-strings
      • How to specify the location of expressions in f-strings
      • Supporting full Python expressions
    • Similar support in other languages
    • Differences between f-string and str.format expressions
    • Triple-quoted f-strings
    • Raw f-strings
    • No binary f-strings
    • !s, !r, and !a are redundant
    • Lambdas inside expressions
    • Can’t combine with ‘u’
  • Examples from Python’s source code
  • References
  • Copyright

Abstract

Python supports multiple ways to format text strings. These include%-formatting [1], str.format() [2], and string.Template[3]. Each of these methods have their advantages, but in additionhave disadvantages that make them cumbersome to use in practice. ThisPEP proposed to add a new string formatting mechanism: Literal StringInterpolation. In this PEP, such strings will be referred to as“f-strings”, taken from the leading character used to denote suchstrings, and standing for “formatted strings”.

This PEP does not propose to remove or deprecate any of the existingstring formatting mechanisms.

F-strings provide a way to embed expressions inside string literals,using a minimal syntax. It should be noted that an f-string is reallyan expression evaluated at run time, not a constant value. In Pythonsource code, an f-string is a literal string, prefixed with ‘f’, whichcontains expressions inside braces. The expressions are replaced withtheir values. Some examples are:

>>> import datetime>>> name = 'Fred'>>> age = 50>>> anniversary = datetime.date(1991, 10, 12)>>> f'My name is {name}, my age next year is {age+1}, my anniversary is {anniversary:%A, %B %d, %Y}.''My name is Fred, my age next year is 51, my anniversary is Saturday, October 12, 1991.'>>> f'He said his name is {name!r}.'"He said his name is 'Fred'."

A similar feature was proposed in PEP 215. PEP 215 proposed to supporta subset of Python expressions, and did not support the type-specificstring formatting (the __format__() method) which was introducedwith PEP 3101.

Rationale

This PEP is driven by the desire to have a simpler way to formatstrings in Python. The existing ways of formatting are either errorprone, inflexible, or cumbersome.

%-formatting is limited as to the types it supports. Only ints, strs,and doubles can be formatted. All other types are either notsupported, or converted to one of these types before formatting. Inaddition, there’s a well-known trap where a single value is passed:

>>> msg = 'disk failure'>>> 'error: %s' % msg'error: disk failure'

But if msg were ever to be a tuple, the same code would fail:

>>> msg = ('disk failure', 32)>>> 'error: %s' % msgTraceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: not all arguments converted during string formatting

To be defensive, the following code should be used:

>>> 'error: %s' % (msg,)"error: ('disk failure', 32)"

str.format() was added to address some of these problems with%-formatting. In particular, it uses normal function call syntax (andtherefore supports multiple parameters) and it is extensible throughthe __format__() method on the object being converted to astring. See PEP 3101 for a detailed rationale. This PEP reuses much ofthe str.format() syntax and machinery, in order to providecontinuity with an existing Python string formatting mechanism.

However, str.format() is not without its issues. Chief among themis its verbosity. For example, the text value is repeated here:

>>> value = 4 * 20>>> 'The value is {value}.'.format(value=value)'The value is 80.'

Even in its simplest form there is a bit of boilerplate, and the valuethat’s inserted into the placeholder is sometimes far removed fromwhere the placeholder is situated:

>>> 'The value is {}.'.format(value)'The value is 80.'

With an f-string, this becomes:

>>> f'The value is {value}.''The value is 80.'

F-strings provide a concise, readable way to include the value ofPython expressions inside strings.

In this sense, string.Template and %-formatting have similarshortcomings to str.format(), but also support fewer formattingoptions. In particular, they do not support the __format__protocol, so that there is no way to control how a specific object isconverted to a string, nor can it be extended to additional types thatwant to control how they are converted to strings (such as Decimaland datetime). This example is not possible withstring.Template:

>>> value = 1234>>> f'input={value:#06x}''input=0x04d2'

And neither %-formatting nor string.Template can controlformatting such as:

>>> date = datetime.date(1991, 10, 12)>>> f'{date} was on a {date:%A}''1991-10-12 was on a Saturday'

No use of globals() or locals()

In the discussions on python-dev [4], a number of solutions wherepresented that used locals() and globals() or their equivalents. Allof these have various problems. Among these are referencing variablesthat are not otherwise used in a closure. Consider:

>>> def outer(x):...  def inner():...  return 'x={x}'.format_map(locals())...  return inner...>>> outer(42)()Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in innerKeyError: 'x'

This returns an error because the compiler has not added a referenceto x inside the closure. You need to manually add a reference to x inorder for this to work:

>>> def outer(x):...  def inner():...  x...  return 'x={x}'.format_map(locals())...  return inner...>>> outer(42)()'x=42'

In addition, using locals() or globals() introduces an informationleak. A called routine that has access to the callers locals() orglobals() has access to far more information than needed to do thestring interpolation.

Guido stated [5] that any solution to better string interpolationwould not use locals() or globals() in its implementation. (This doesnot forbid users from passing locals() or globals() in, it justdoesn’t require it, nor does it allow using these functions under thehood.)

Specification

In source code, f-strings are string literals that are prefixed by theletter ‘f’ or ‘F’. Everywhere this PEP uses ‘f’, ‘F’ may also beused. ‘f’ may be combined with ‘r’ or ‘R’, in either order, to produceraw f-string literals. ‘f’ may not be combined with ‘b’: this PEP doesnot propose to add binary f-strings. ‘f’ may not be combined with ‘u’.

When tokenizing source files, f-strings use the same rules as normalstrings, raw strings, binary strings, and triple quoted strings. Thatis, the string must end with the same character that it started with:if it starts with a single quote it must end with a single quote, etc.This implies that any code that currently scans Python code lookingfor strings should be trivially modifiable to recognize f-strings(parsing within an f-string is another matter, of course).

Once tokenized, f-strings are parsed in to literal strings andexpressions. Expressions appear within curly braces '{' and'}'. While scanning the string for expressions, any doubledbraces '{{' or '}}' inside literal portions of an f-string arereplaced by the corresponding single brace. Doubled literal openingbraces do not signify the start of an expression. A single closingcurly brace '}' in the literal portion of a string is an error:literal closing curly braces must be doubled '}}' in order torepresent a single closing brace.

The parts of the f-string outside of braces are literalstrings. These literal portions are then decoded. For non-rawf-strings, this includes converting backslash escapes such as'\n', '\"', "\'", '\xhh', '\uxxxx','\Uxxxxxxxx', and named unicode characters '\N{name}' intotheir associated Unicode characters [6].

Backslashes may not appear anywhere within expressions. Comments,using the '#' character, are not allowed inside an expression.

Following each expression, an optional type conversion may bespecified. The allowed conversions are '!s', '!r', or'!a'. These are treated the same as in str.format(): '!s'calls str() on the expression, '!r' calls repr() on theexpression, and '!a' calls ascii() on the expression. Theseconversions are applied before the call to format(). The onlyreason to use '!s' is if you want to specify a format specifierthat applies to str, not to the type of the expression.

F-strings use the same format specifier mini-language as str.format.Similar to str.format(), optional format specifiers maybe beincluded inside the f-string, separated from the expression (or thetype conversion, if specified) by a colon. If a format specifier isnot provided, an empty string is used.

So, an f-string looks like:

f ' <text> { <expression> <optional !s, !r, or !a> <optional : format specifier> } <text> ... '

The expression is then formatted using the __format__ protocol,using the format specifier as an argument. The resulting value isused when building the value of the f-string.

Note that __format__() is not called directly on each value. Theactual code uses the equivalent of type(value).__format__(value,format_spec), or format(value, format_spec). See thedocumentation of the builtin format() function for more details.

Expressions cannot contain ':' or '!' outside of strings orparentheses, brackets, or braces. The exception is that the '!='operator is allowed as a special case.

Escape sequences

Backslashes may not appear inside the expression portions off-strings, so you cannot use them, for example, to escape quotesinside f-strings:

>>> f'{\'quoted string\'}' File "<stdin>", line 1SyntaxError: f-string expression part cannot include a backslash

You can use a different type of quote inside the expression:

>>> f'{"quoted string"}''quoted string'

Backslash escapes may appear inside the string portions of anf-string.

Note that the correct way to have a literal brace appear in theresulting string value is to double the brace:

>>> f'{{ {4*10} }}''{ 40 }'>>> f'{{{4*10}}}''{40}'

Like all raw strings in Python, no escape processing is done for rawf-strings:

>>> fr'x={4*10}\n''x=40\\n'

Due to Python’s string tokenizing rules, the f-stringf'abc {a['x']} def' is invalid. The tokenizer parses this as 3tokens: f'abc {a[', x, and ']} def'. Just like regularstrings, this cannot be fixed by using raw strings. There are a numberof correct ways to write this f-string: with a different quotecharacter:

f"abc {a['x']} def"

Or with triple quotes:

f'''abc {a['x']} def'''

Code equivalence

The exact code used to implement f-strings is not specified. However,it is guaranteed that any embedded value that is converted to a stringwill use that value’s __format__ method. This is the samemechanism that str.format() uses to convert values to strings.

For example, this code:

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

Might be evaluated as:

'abc' + format(expr1, spec1) + format(repr(expr2), spec2) + 'def' + format(expr3) + 'ghi'

Expression evaluation

The expressions that are extracted from the string are evaluated inthe context where the f-string appeared. This means the expression hasfull access to local and global variables. Any valid Python expressioncan be used, including function and method calls.

Because the f-strings are evaluated where the string appears in thesource code, there is no additional expressiveness available withf-strings. There are also no additional security concerns: you couldhave also just written the same expression, not inside of anf-string:

>>> def foo():...  return 20...>>> f'result={foo()}''result=20'

Is equivalent to:

>>> 'result=' + str(foo())'result=20'

Expressions are parsed with the equivalent of ast.parse('(' +expression + ')', '<fstring>', 'eval') [7].

Note that since the expression is enclosed by implicit parenthesesbefore evaluation, expressions can contain newlines. For example:

>>> x = 0>>> f'''{x... +1}''''1'>>> d = {0: 'zero'}>>> f'''{d[0... ]}''''zero'

Format specifiers

Format specifiers may also contain evaluated expressions. This allowscode such as:

>>> width = 10>>> precision = 4>>> value = decimal.Decimal('12.34567')>>> f'result: {value:{width}.{precision}}''result: 12.35'

Once expressions in a format specifier are evaluated (if necessary),format specifiers are not interpreted by the f-string evaluator. Justas in str.format(), they are merely passed in to the__format__() method of the object being formatted.

Concatenating strings

Adjacent f-strings and regular strings are concatenated. Regularstrings are concatenated at compile time, and f-strings areconcatenated at run time. For example, the expression:

>>> x = 10>>> y = 'hi'>>> 'a' 'b' f'{x}' '{c}' f'str<{y:^4}>' 'd' 'e'

yields the value:

'ab10{c}str< hi >de'

While the exact method of this run time concatenation is unspecified,the above code might evaluate to:

'ab' + format(x) + '{c}' + 'str<' + format(y, '^4') + '>de'

Each f-string is entirely evaluated before being concatenated toadjacent f-strings. That means that this:

>>> f'{x' f'}'

Is a syntax error, because the first f-string does not contain aclosing brace.

Error handling

Either compile time or run time errors can occur when processingf-strings. Compile time errors are limited to those errors that can bedetected when scanning an f-string. These errors all raiseSyntaxError.

Unmatched braces:

>>> f'x={x' File "<stdin>", line 1SyntaxError: f-string: expecting '}'

Invalid expressions:

>>> f'x={!x}' File "<stdin>", line 1SyntaxError: f-string: empty expression not allowed

Run time errors occur when evaluating the expressions inside anf-string. Note that an f-string can be evaluated multiple times, andwork sometimes and raise an error at other times:

>>> d = {0:10, 1:20}>>> for i in range(3):...  print(f'{i}:{d[i]}')...0:101:20Traceback (most recent call last): File "<stdin>", line 2, in <module>KeyError: 2

or:

>>> for x in (32, 100, 'fifty'):...  print(f'x = {x:+3}')...'x = +32''x = +100'Traceback (most recent call last): File "<stdin>", line 2, in <module>ValueError: Sign not allowed in string format specifier

Leading and trailing whitespace in expressions is ignored

For ease of readability, leading and trailing whitespace inexpressions is ignored. This is a by-product of enclosing theexpression in parentheses before evaluation.

Evaluation order of expressions

The expressions in an f-string are evaluated in left-to-rightorder. This is detectable only if the expressions have side effects:

>>> def fn(l, incr):...  result = l[0]...  l[0] += incr...  return result...>>> lst = [0]>>> f'{fn(lst,2)} {fn(lst,3)}''0 2'>>> f'{fn(lst,2)} {fn(lst,3)}''5 7'>>> lst[10]

Discussion

python-ideas discussion

Most of the discussions on python-ideas [8] focused on three issues:

  • How to denote f-strings,
  • How to specify the location of expressions in f-strings, and
  • Whether to allow full Python expressions.

How to denote f-strings

Because the compiler must be involved in evaluating the expressionscontained in the interpolated strings, there must be some way todenote to the compiler which strings should be evaluated. This PEPchose a leading 'f' character preceding the string literal. Thisis similar to how 'b' and 'r' prefixes change the meaning ofthe string itself, at compile time. Other prefixes were suggested,such as 'i'. No option seemed better than the other, so 'f'was chosen.

Another option was to support special functions, known to thecompiler, such as Format(). This seems like too much magic forPython: not only is there a chance for collision with existingidentifiers, the PEP author feels that it’s better to signify themagic with a string prefix character.

How to specify the location of expressions in f-strings

This PEP supports the same syntax as str.format() fordistinguishing replacement text inside strings: expressions arecontained inside braces. There were other options suggested, such asstring.Template’s $identifier or ${expression}.

While $identifier is no doubt more familiar to shell scripters andusers of some other languages, in Python str.format() is heavilyused. A quick search of Python’s standard library shows only a handfulof uses of string.Template, but hundreds of uses ofstr.format().

Another proposed alternative was to have the substituted text between\{ and } or between \{ and \}. While this syntax wouldprobably be desirable if all string literals were to supportinterpolation, this PEP only supports strings that are already markedwith the leading 'f'. As such, the PEP is using unadorned bracesto denoted substituted text, in order to leverage end user familiaritywith str.format().

Supporting full Python expressions

Many people on the python-ideas discussion wanted support for eitheronly single identifiers, or a limited subset of Python expressions(such as the subset supported by str.format()). This PEP supportsfull Python expressions inside the braces. Without full expressions,some desirable usage would be cumbersome. For example:

>>> f'Column={col_idx+1}'>>> f'number of items: {len(items)}'

would become:

>>> col_number = col_idx+1>>> f'Column={col_number}'>>> n_items = len(items)>>> f'number of items: {n_items}'

While it’s true that very ugly expressions could be included in thef-strings, this PEP takes the position that such uses should beaddressed in a linter or code review:

>>> f'mapping is { {a:b for (a, b) in ((1, 2), (3, 4))} }''mapping is {1: 2, 3: 4}'

Similar support in other languages

Wikipedia has a good discussion of string interpolation in otherprogramming languages [9]. This feature is implemented in manylanguages, with a variety of syntaxes and restrictions.

Differences between f-string and str.format expressions

There is one small difference between the limited expressions allowedin str.format() and the full expressions allowed insidef-strings. The difference is in how index lookups are performed. Instr.format(), index values that do not look like numbers areconverted to strings:

>>> d = {'a': 10, 'b': 20}>>> 'a={d[a]}'.format(d=d)'a=10'

Notice that the index value is converted to the string 'a' when itis looked up in the dict.

However, in f-strings, you would need to use a literal for the valueof 'a':

>>> f'a={d["a"]}''a=10'

This difference is required because otherwise you would not be able touse variables as index values:

>>> a = 'b'>>> f'a={d[a]}''a=20'

See [10] for a further discussion. It was this observation that led tofull Python expressions being supported in f-strings.

Furthermore, the limited expressions that str.format() understandsneed not be valid Python expressions. For example:

>>> '{i[";]}'.format(i={'";':4})'4'

For this reason, the str.format() “expression parser” is not suitablefor use when implementing f-strings.

Triple-quoted f-strings

Triple quoted f-strings are allowed. These strings are parsed just asnormal triple-quoted strings are. After parsing and decoding, thenormal f-string logic is applied, and __format__() is called oneach value.

Raw f-strings

Raw and f-strings may be combined. For example, they could be used tobuild up regular expressions:

>>> header = 'Subject'>>> fr'{header}:\s+''Subject:\\s+'

In addition, raw f-strings may be combined with triple-quoted strings.

No binary f-strings

For the same reason that we don’t support bytes.format(), you maynot combine 'f' with 'b' string literals. The primary problemis that an object’s __format__() method may return Unicode data thatis not compatible with a bytes string.

Binary f-strings would first require a solution forbytes.format(). This idea has been proposed in the past, mostrecently in PEP 461. The discussions of such a feature usuallysuggest either

  • adding a method such as __bformat__() so an object can controlhow it is converted to bytes, or
  • having bytes.format() not be as general purpose or extensibleas str.format().

Both of these remain as options in the future, if such functionalityis desired.

!s, !r, and !a are redundant

The !s, !r, and !a conversions are not strictlyrequired. Because arbitrary expressions are allowed inside thef-strings, this code:

>>> a = 'some string'>>> f'{a!r}'"'some string'"

Is identical to:

>>> f'{repr(a)}'"'some string'"

Similarly, !s can be replaced by calls to str() and !a bycalls to ascii().

However, !s, !r, and !a are supported by this PEP in orderto minimize the differences with str.format(). !s, !r, and!a are required in str.format() because it does not allow theexecution of arbitrary expressions.

Lambdas inside expressions

Because lambdas use the ':' character, they cannot appear outsideof parentheses in an expression. The colon is interpreted as the startof the format specifier, which means the start of the lambdaexpression is seen and is syntactically invalid. As there’s nopractical use for a plain lambda in an f-string expression, this isnot seen as much of a limitation.

If you feel you must use lambdas, they may be used inside of parentheses:

>>> f'{(lambda x: x*2)(3)}''6'

Can’t combine with ‘u’

The ‘u’ prefix was added to Python 3.3 in PEP 414 as a means to easesource compatibility with Python 2.7. Because Python 2.7 will neversupport f-strings, there is nothing to be gained by being able tocombine the ‘f’ prefix with ‘u’.

Examples from Python’s source code

Here are some examples from Python source code that currently usestr.format(), and how they would look with f-strings. This PEPdoes not recommend wholesale converting to f-strings, these are justexamples of real-world usages of str.format() and how they’d lookif written from scratch using f-strings.

Lib/asyncio/locks.py:

extra = '{},waiters:{}'.format(extra, len(self._waiters))extra = f'{extra},waiters:{len(self._waiters)}'

Lib/configparser.py:

message.append(" [line {0:2d}]".format(lineno))message.append(f" [line {lineno:2d}]")

Tools/clinic/clinic.py:

methoddef_name = "{}_METHODDEF".format(c_basename.upper())methoddef_name = f"{c_basename.upper()}_METHODDEF"

python-config.py:

print("Usage: {0} [{1}]".format(sys.argv[0], '|'.join('--'+opt for opt in valid_opts)), file=sys.stderr)print(f"Usage: {sys.argv[0]} [{'|'.join('--'+opt for opt in valid_opts)}]", file=sys.stderr)

References

Copyright

This document has been placed in the public domain.

PEP 498 – Literal String Interpolation | peps.python.org (2024)
Top Articles
Latest Posts
Article information

Author: Margart Wisoky

Last Updated:

Views: 6546

Rating: 4.8 / 5 (58 voted)

Reviews: 89% of readers found this page helpful

Author information

Name: Margart Wisoky

Birthday: 1993-05-13

Address: 2113 Abernathy Knoll, New Tamerafurt, CT 66893-2169

Phone: +25815234346805

Job: Central Developer

Hobby: Machining, Pottery, Rafting, Cosplaying, Jogging, Taekwondo, Scouting

Introduction: My name is Margart Wisoky, I am a gorgeous, shiny, successful, beautiful, adventurous, excited, pleasant person who loves writing and wants to share my knowledge and understanding with you.