PEP-498 updates: specify str.interpolate() and beef up some of the discussion text. Still needs work on the XXX marked sections.

This commit is contained in:
Eric V. Smith 2015-08-20 20:02:45 -04:00
parent eaf9ea6841
commit 2b710a1d86
1 changed files with 95 additions and 25 deletions

View File

@ -41,6 +41,9 @@ values. Some examples are::
>>> f'He said his name is {name!r}.'
"He said his name is 'Fred'."
This PEP proposes a new method on the str type: str.interpolate. This
method will be used to implement f-strings.
A similar feature was proposed in PEP 215 [#]_. PEP 215 proposed to
support a subset of Python expressions, and did not support the
type-specific string formatting (the __format__ method) which was
@ -139,6 +142,11 @@ order for this to work::
>>> outer(42)()
'x=42'
In addition, using locals() or globals() introduces an information
leak. A called routine that has access to the callers locals() or
globals() has access to far more information than needed to do the
string interpolation.
Guido stated [#]_ that any solution to better string interpolation
would not use locals() or globals().
@ -185,21 +193,34 @@ Expressions cannot contain ':' or '!' outside of strings or parens,
brackets, or braces. The exception is that the '!=' operator is
special cased.
str.interpolate()
-----------------
str.interpolate will be a new method. It takes one argument: a mapping
of field names to values. This method is the same as str.format_map()
[#]_, with one difference: it does not interpret the field_name [#]_
in any way. The field name is only used to look up the replacement
value in the supplied mapping object. Like str.format() and
str.format_map(), it does interpret and apply the optional conversion
character and format specifier. Thus, a field name may not contain the
characters ':' or '}', nor the strings '!s' and '!r'.
Code equivalence
----------------
The exact code that is executed when converting expressions to strings
is unspecified by this PEP. However, it is specified that once the
expression is evaluated, the result's __format__() method will be
called with the given format specifier.
An f-string is evaluated at run time as a call to str.interpolate().
For example, this code::
f'abc{expr1:spec1}{expr2!r:spec2}def{expr3:!s}ghi'
May be evaluated as::
Will be be evaluated as::
'abc{expr1:spec1}{expr2!r:spec2}def{expr3:!s}ghi'.interpolate({'expr1': expr1, 'expr2': expr2, 'expr3': expr3})
Note that the string on which interpolate() is being called is
identical to the value of the f-string.
''.join(['abc', expr1.__format__('spec1'), repr(expr2).__format__('spec2'), 'def', str(expr3).__format__(''), 'ghi'])
Expression evaluation
---------------------
@ -258,10 +279,11 @@ yields the value::
'ab10cstr< hi >de'
While the exact code that is executed when evaluating this f-string is
not specified, one possible strategy is to evaluate::
While the exact method of this runtime concatenation is unspecified,
the above code might evaluate to::
''.join(['ab', x.__format__(''), 'c', 'str<', y.__format__('^4'), '>', 'de'])
''.join(['ab', '{x}'.interpolate({'x': x}), 'c', 'str<', 'str<{y:^4}>'.interpolate({'y': y}), 'de'])
Error handling
--------------
@ -310,24 +332,20 @@ or::
File "<stdin>", line 2, in <module>
ValueError: Sign not allowed in string format specifier
Leading whitespace in expressions is skipped
--------------------------------------------
Leading and trailing whitespace in expressions is skipped
---------------------------------------------------------
Because expressions may begin with a left brace ('{'), there is a
problem when parsing such expressions. For example::
For ease of readability, leading and trailing whitespace in
expressions is ignored. However, this does not affect the string or
keys passed to str.interpolate().
>>> f'{{k:v for k, v in [(1, 2), (3, 4)]}}'
'{k:v for k, v in [(1, 2), (3, 4)]}'
>>> x = 100
>>> f'x = { x }'
'x = 100'
In this case, the doubled left braces and doubled right braces are
interpreted as single braces, and the string becomes just a normal
string literal. There is no expression evaluation being performed.
This would be evaluated as::
To allow for expressions to begin with a left brace, whitespace
characters at the beginning of an expression are skipped::
>>> f'{ {k:v for k, v in [(1, 2), (3, 4)]}}'
'{1: 2, 3: 4}'
'x = { x }'.interpolate({' x ': 100})
Discussion
==========
@ -337,14 +355,15 @@ Most of the discussions on python-ideas [#]_ focused on a few issues:
- Whether to allow full Python expressions.
- How to designate f-strings, and how to specify the location of
expressions in them.
- How to concatenate adjacent strings and f-strings.
XXX: more on the above issues.
Similar support in other languages
----------------------------------
XXX: Placeholder.
Wikipedia has a good discussion of string interpolation in other
programming languages [#]_. This feature is implemented in many
languages, with a variety of syntaxes and restrictions.
Differences between f-string and str.format expressions
-------------------------------------------------------
@ -377,6 +396,25 @@ use variables as index values::
See [#]_ for a further discussion. It was this observation that led to
full Python expressions being supported in f-strings.
Triple-quoted f-strings
-----------------------
Triple quoted f-strings are allowed. These strings are parsed just as
normal triple-quoted strings are. After parsing, the normal f-string
logic is applied, and str.interpolate() is called.
Raw f-strings
-------------
Raw and f-strings may be combined. For example they could be used to
build 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
-------------------
@ -385,6 +423,8 @@ combine 'f' with 'b' string literals. The primary problem is that an
object's __format__() method may return Unicode data that is not
compatible with a bytes string.
#XXX: maybe allow this, but encode the output as ascii?
!s and !r are redundant
-----------------------
@ -421,6 +461,27 @@ Lambdas may be used inside of parens::
>>> f'{(lambda x: x*2)(3)}'
'6'
Future extensions:
==================
XXX: By using another leading character (say, 'i'), we could extend
this proposal to cover internationalization and localization. The idea
is that the string would be passed to some lookup function before
.interpolate() is called on it:
>>> name = 'Eric'
>>> fi'Name: {name}'
Could be translated as::
gettext.gettext('Name: {name}').interpolate({'name': name})
If gettext.gettext() returned '{name} es mi nombre', then the
resulting string would be 'Eric es mi nombre'.
Any such internationalization work will be specified in an additional
PEP.
References
==========
@ -445,6 +506,15 @@ References
.. [#] Avoid locals() and globals()
(https://mail.python.org/pipermail/python-ideas/2015-July/034701.html)
.. [#] str.format_map() documentation
(https://docs.python.org/3/library/stdtypes.html#str.format_map)
.. [#] Format string syntax
(https://docs.python.org/3/library/string.html#format-string-syntax)
.. [#] Wikipedia article on string interpolation
(https://en.wikipedia.org/wiki/String_interpolation)
.. [#] Start of python-ideas discussion
(https://mail.python.org/pipermail/python-ideas/2015-July/034657.html)