-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: C.A.M. Gerlach <[email protected]> Co-authored-by: Nick Coghlan <[email protected]>
- Loading branch information
1 parent
586afe7
commit f23e5d4
Showing
1 changed file
with
68 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,26 +3,29 @@ Title: General purpose string template literals | |
Version: $Revision$ | ||
Last-Modified: $Date$ | ||
Author: Nick Coghlan <[email protected]>, Nick Humrich <[email protected]> | ||
Discussions-To: https://discuss.python.org/t/pep-501-reopen-general-purpose-string-template-literals/24625 | ||
Status: Draft | ||
Type: Standards Track | ||
Content-Type: text/x-rst | ||
Requires: 498, 675, 701 | ||
Requires: 701 | ||
Created: 08-Aug-2015 | ||
Python-Version: 3.12 | ||
Post-History: 08-Aug-2015, 23-Aug-2015, 30-Aug-2015 | ||
Post-History: `08-Aug-2015 <https://mail.python.org/archives/list/[email protected]/thread/EAZ3P2M3CDDIQFR764NF6FXQHWXYMKJF/#ECGMIMPGUWWRQNRBW7CIP5SPP4LTZWTW>`__, | ||
`05-Sep-2015 <https://mail.python.org/archives/list/[email protected]/thread/ILVRPS6DTFZ7IHL5HONDBB6INVXTFOZ2/>`__, | ||
`09-Mar-2023 <https://discuss.python.org/t/pep-501-reopen-general-purpose-string-template-literals/24625>`__, | ||
|
||
Abstract | ||
======== | ||
|
||
:pep:`498` added new syntactic support for string interpolation that is | ||
transparent to the compiler, allow name references from the interpolation | ||
transparent to the compiler, allowing name references from the interpolation | ||
operation full access to containing namespaces (as with any other expression), | ||
rather than being limited to explicit name references. These are referred | ||
to in the PEP as "f-strings" (a mnemonic for "formatted strings"). | ||
|
||
Since acceptance of the :pep:`498`, f-strings have become well-established and very popular. | ||
Since acceptance of :pep:`498`, f-strings have become well-established and very popular. | ||
F-strings are becoming even more useful with the addition of :pep:`701`. | ||
While f-string are great, eager rending has its limitations. For example, the eagerness of f-strings | ||
While f-strings are great, eager rendering has its limitations. For example, the eagerness of f-strings | ||
has made code like the following likely:: | ||
|
||
os.system(f"echo {message_from_user}") | ||
|
@@ -59,28 +62,27 @@ and :pep:`701` has been introduced. This PEP has been updated to reflect current | |
and improvements from 701. It is designed to be built on top of the :pep:`701` implementation. | ||
|
||
|
||
Summary of differences from PEP 498 | ||
=================================== | ||
Summary of differences from f-strings | ||
===================================== | ||
|
||
The key additions this proposal makes relative to :pep:`498`: | ||
The key differences between f-strings and t-strings are: | ||
|
||
* the "t" (template literal) prefix indicates delayed rendering, but | ||
* the ``t`` (template literal) prefix indicates delayed rendering, but | ||
otherwise uses the same syntax and semantics as formatted strings | ||
* template literals are available at runtime as a new kind of object | ||
(``types.TemplateLiteral``) | ||
* the default rendering used by formatted strings is invoked on a | ||
template literal object by calling ``format(template)`` rather than | ||
implicitly | ||
* while f-string ``f"Message {here}"`` would be *semantically* equivalent to | ||
``format(t"Message {here}")``, f-strings currently avoid the runtime overhead of using | ||
``format(t"Message {here}")``, f-strings will continue to avoid the runtime overhead of using | ||
the delayed rendering machinery that is needed for t-strings | ||
|
||
NOTE: This proposal spells out a draft API for ``types.TemplateLiteral`` | ||
|
||
Proposal | ||
======== | ||
|
||
This PEP proposes the introduction of a new string prefix that declares the | ||
This PEP proposes a new string prefix that declares the | ||
string to be a template literal rather than an ordinary string:: | ||
|
||
template = t"Substitute {names:>10} and {expressions()} at runtime" | ||
|
@@ -94,22 +96,20 @@ This would be effectively interpreted as:: | |
(" at runtime", None), | ||
) | ||
_field_values = (names, expressions()) | ||
_format_specifiers = (">10", "") | ||
template = types.TemplateLiteral(_raw_template, | ||
_parsed_template, | ||
_field_values, | ||
_format_specifiers) | ||
_format_specifiers = (f">10", f"") | ||
template = types.TemplateLiteral( | ||
_raw_template, _parsed_template, _field_values, _format_specifiers) | ||
|
||
The ``__format__`` method on ``types.TemplateLiteral`` would then | ||
implement the following ``str.format`` inspired semantics:: | ||
implement the following :meth:`str.format` inspired semantics:: | ||
|
||
>>> import datetime | ||
>>> name = 'Jane' | ||
>>> age = 50 | ||
>>> anniversary = datetime.date(1991, 10, 12) | ||
>>> format(t'My name is {name}, my age next year is {age+1}, my anniversary is {anniversary:%A, %B %d, %Y}.') | ||
'My name is Jane, my age next year is 51, my anniversary is Saturday, October 12, 1991.' | ||
>>> format(t'She said her name is {repr(name)}.') | ||
>>> format(t'She said her name is {name!r}.') | ||
"She said her name is 'Jane'." | ||
|
||
The implementation of template literals would be based on :pep:`701`, and use the same syntax. | ||
|
@@ -122,7 +122,7 @@ strings that are not present directly in the source code of the application. | |
Rationale | ||
========= | ||
|
||
:pep:`498` made interpolating values into strings with full access to Python's | ||
F-strings (:pep:`498`) made interpolating values into strings with full access to Python's | ||
lexical namespace semantics simpler, but it does so at the cost of creating a | ||
situation where interpolating values into sensitive targets like SQL queries, | ||
shell commands and HTML templates will enjoy a much cleaner syntax when handled | ||
|
@@ -136,13 +136,13 @@ While very different in the technical details, the | |
``types.TemplateLiteral`` interface proposed in this PEP is | ||
conceptually quite similar to the ``FormattableString`` type underlying the | ||
`native interpolation <https://msdn.microsoft.com/en-us/library/dn961160.aspx>`__ support introduced in C# 6.0, | ||
as well as `template literals in Javascript <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals>`__ introduced in es6. | ||
as well as `template literals in Javascript <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals>`__ introduced in ES6. | ||
|
||
|
||
Specification | ||
============= | ||
|
||
This PEP proposes the introduction of ``t`` as a new string prefix that | ||
This PEP proposes a new ``t`` string prefix that | ||
results in the creation of an instance of a new type, | ||
``types.TemplateLiteral``. | ||
|
||
|
@@ -151,7 +151,7 @@ permitted), and string literal concatenation operates as normal, with the | |
entire combined literal forming the template literal. | ||
|
||
The template string is parsed into literals, expressions and format specifiers | ||
as described for f-strings in :pep:`498`. Conversion specifiers are handled | ||
as described for f-strings in :pep:`498` and :pep:`701`. Conversion specifiers are handled | ||
by the compiler, and appear as part of the field text in interpolation | ||
templates. | ||
|
||
|
@@ -207,41 +207,41 @@ following semantics:: | |
self.format_specifiers) | ||
) | ||
else: | ||
raise TypeError(f"unsupported operand type(s) for +: 'TemplateLiteral' and '{type(other)}'") | ||
raise TypeError(f"unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'") | ||
|
||
def __radd__(self, other): | ||
if isinstance(other, str): | ||
if self.parsed_template: | ||
new_parsed_template = ((other + self.parsed_template[0][0], self.parsed_template[0][1]),) | ||
+ self.parsed_template[1:] | ||
new_parsed_template = ( | ||
((other + self.parsed_template[0][0], self.parsed_template[0][1]),) + self.parsed_template[1:]) | ||
else: | ||
new_parsed_template = ((other, None),) | ||
|
||
return TemplateLiteral( | ||
other + self.raw_template, | ||
new_parsed_template, | ||
self.field_values, | ||
self.format_specifiers) | ||
other + self.raw_template, | ||
new_parsed_template, | ||
self.field_values, | ||
self.format_specifiers) | ||
) | ||
else: | ||
raise TypeError(f"unsupported operand type(s) for +: '{type(other)}' and 'TemplateLiteral'") | ||
raise TypeError(f"unsupported operand type(s) for +: '{type(other)}' and '{type(self)}'") | ||
|
||
def __mul__(self, other): | ||
if isinstance(other, int): | ||
if other < 1: | ||
return TemplateLiteral("", (), (), ()) | ||
final = self | ||
for _ in range(1, int): | ||
for _ in range(1, other): | ||
final = final + self | ||
return final | ||
else: | ||
raise TypeError(f"unsupported operand type(s) for *: 'TemplateLiteral' and '{type(other)}'") | ||
raise TypeError(f"unsupported operand type(s) for *: '{type(self)}' and '{type(other)}'") | ||
|
||
def __rmul__(self, other): | ||
if isinstance(other, int): | ||
return self * other | ||
else: | ||
raise TypeError(f"unsupported operand type(s) for *: '{type(other)}' and 'TemplateLiteral'") | ||
raise TypeError(f"unsupported operand type(s) for *: '{type(other)}' and '{type(self)}'") | ||
|
||
def __repr__(self): | ||
return (f"<{type(self).__qualname__} {repr(self._raw_template)} " | ||
|
@@ -256,7 +256,7 @@ following semantics:: | |
# See definition of the template rendering semantics below | ||
|
||
The result of a template literal expression is an instance of this | ||
type, rather than an already rendered string - rendering only takes | ||
type, rather than an already rendered string — rendering only takes | ||
place when the instance's ``render`` method is called (either directly, or | ||
indirectly via ``__format__``). | ||
|
||
|
@@ -430,10 +430,10 @@ immediately (though one for shell escaping is proposed), but rather proposes to | |
third party libraries, and potentially incorporated into the standard library | ||
at a later date. | ||
|
||
It is proposed that a renderer is included in the `shlex` module, aimed to offer a POSIX shell style experience for | ||
It is proposed that a renderer is included in the :mod:`shlex` module, aimed to offer a POSIX shell style experience for | ||
accessing external programs, without the significant risks posed by running | ||
``os.system`` or enabling the system shell when using the ``subprocess`` module | ||
APIs, which will provide an interface for running external programs similar to that | ||
APIs, which will provide an interface for running external programs inspired by that | ||
offered by the | ||
`Julia programming language <http://julia.readthedocs.org/en/latest/manual/running-external-programs/>`__, | ||
only with the backtick based ``\`cat $filename\``` syntax replaced by | ||
|
@@ -459,6 +459,8 @@ Unmatched braces:: | |
|
||
>>> t'x={x' | ||
File "<stdin>", line 1 | ||
t'x={x' | ||
^ | ||
SyntaxError: missing '}' in template literal expression | ||
|
||
Invalid expressions:: | ||
|
@@ -480,9 +482,12 @@ details, which will be reported as runtime exceptions. | |
|
||
Renderer for shell escaping added to shlex | ||
========================================== | ||
As a reference implementation, a renderer for safe POSIX shell escaping can be added to the ``shlex`` | ||
module. This renderer would be called ``sh`` and would be equivalent of calling ``shlex.quote`` on | ||
each field value in the template literal.: | ||
|
||
As a reference implementation, a renderer for safe POSIX shell escaping can be added to the :mod:`shlex` | ||
module. This renderer would be called ``sh`` and would be equivalent to calling ``shlex.quote`` on | ||
each field value in the template literal. | ||
|
||
Thus:: | ||
|
||
os.system(shlex.sh(t'cat {myfile}')) | ||
|
||
|
@@ -500,24 +505,24 @@ Changes to subprocess module | |
============================ | ||
|
||
With the additional renderer in the shlex module, and the addition of template literals, | ||
the subprocess module can be changed to handle accepting template literal | ||
the :mod:`subprocess` module can be changed to handle accepting template literals | ||
as an additional input type to ``Popen``, as it already accepts a sequence, or a string, | ||
with different behavior for each. | ||
With the addition of template literals, ``subprocess.Popen`` (and in return, all its higher level functions such as ``run``) | ||
could accept a string in a more safe way. | ||
With the addition of template literals, :func:`subprocess.Popen` (and in return, all its higher level functions such as :func:`~subprocess.run`) | ||
could accept strings in a safe way. | ||
For example:: | ||
|
||
subprocess.run(t'cat {myfile}', shell=True) | ||
|
||
would automatically use the ``shlex.sh`` renderer provided in this PEP. Therefore, using shlex | ||
inside a t-string like so:: | ||
inside a ``subprocess.run`` call like so:: | ||
|
||
subprocess.run(shlex.sh(t'cat {myfile}')) | ||
subprocess.run(shlex.sh(t'cat {myfile}'), shell=True) | ||
|
||
would be irrelevant, as ``run`` would automatically render any template literals through ``shlex.sh`` | ||
would be redundant, as ``run`` would automatically render any template literals through ``shlex.sh`` | ||
|
||
|
||
Alternatively, when ``subprocess.Popen`` is ran without ``shell=True``, it could still provide | ||
Alternatively, when ``subprocess.Popen`` is run without ``shell=True``, it could still provide | ||
subprocess with a more ergonomic syntax. For example:: | ||
|
||
subprocess.run(t'cat {myfile} --flag {value}') | ||
|
@@ -526,7 +531,7 @@ would be equivalent to:: | |
|
||
subprocess.run(['cat', shlex.quote(myfile), '--flag', shlex.quote(value)]) | ||
|
||
It would do this by first using the ``shlex.sh`` renderer, as above, then using ``shlex.split`` afterwords. | ||
It would do this by first using the ``shlex.sh`` renderer, as above, then using ``shlex.split`` on the result. | ||
|
||
The implementation inside ``subprocess.Popen._execute_child`` would look like:: | ||
|
||
|
@@ -567,7 +572,7 @@ keyword arguments would also remain available:: | |
As part of any such integration, a recommended approach would need to be | ||
defined for "lazy evaluation" of interpolated fields, as the ``logging`` | ||
module's existing delayed interpolation support provides access to | ||
`various attributes <https://docs.python.org/3/library/logging.html#logrecord-attributes>`__ of the event ``LogRecord`` instance. | ||
:ref:`various attributes <logrecord-attributes>` of the event ``LogRecord`` instance. | ||
|
||
For example, since template literal expressions are arbitrary Python expressions, | ||
string literals could be used to indicate cases where evaluation itself is | ||
|
@@ -589,30 +594,32 @@ that happen to be passed in as data values in a normal field. | |
|
||
Comparison to PEP 675 | ||
===================== | ||
This PEP is similar in its goals as :pep:`675`. | ||
|
||
This PEP has similar goals to :pep:`675`. | ||
While both are attempting to provide a way to have safer code, they are doing so in different ways. | ||
:pep:`675` provides a way to find potential security issues as a static analysis. | ||
:pep:`675` provides a way to find potential security issues via static analysis. | ||
It does so by providing a way for the type checker to flag sections of code that are using | ||
dynamic strings incorrectly. This requires a user to actually run a static analysis type checker such as mypy. | ||
|
||
If :pep:`675` tells you that you are violating a type check, it is up to the programmer to know how to handle the dynamic-ness of the string. | ||
This PEP provides a safe alternative to f-strings at runtime. | ||
This PEP provides a safer alternative to f-strings at runtime. | ||
If a user recieves a type-error, changing an existing f-string into a t-string could be an easy way to solve the problem. | ||
|
||
t-strings create safer code by correctly escaping the dynamic sections of strings, while maintaining the static portions. | ||
t-strings enable safer code by correctly escaping the dynamic sections of strings, while maintaining the static portions. | ||
|
||
This PEP also allows a way for a library/codebase to be safe, but it does so at runtime rather than | ||
only during static analysis. For example, if a library wanted to ensure "only safe strings", it | ||
could check that the type of object passed in at runtime is a template literal.:: | ||
could check that the type of object passed in at runtime is a template literal:: | ||
|
||
def my_safe_function(string_like_object): | ||
if not isinstance(string_like_object, types.TemplateLiteral): | ||
raise TypeError("Argument 'string_like_object' must be a t-string") | ||
|
||
The two PEPs could also be used together by typing your function as accepting either a string literal or a template literal. | ||
This way, your function can provide the same api for both static strings, and dynamic strings. | ||
This way, your function can provide the same API for both static and dynamic strings:: | ||
|
||
def my_safe_function(string_like_object: LiteralString | TemplateLiteral): | ||
... | ||
|
||
Discussion | ||
========== | ||
|
@@ -623,7 +630,7 @@ also apply to this PEP. | |
Support for binary interpolation | ||
-------------------------------- | ||
|
||
As :pep:`498` does not handle byte strings, neither will this one. | ||
As f-strings don't handle byte strings, neither will t-strings. | ||
|
||
Interoperability with str-only interfaces | ||
----------------------------------------- | ||
|
@@ -653,12 +660,13 @@ a creating a new kind of object for later consumption by interpolation | |
functions. Creating a rich descriptive object with a useful default renderer | ||
made it much easier to support customisation of the semantics of interpolation. | ||
|
||
Building atop PEP 701 rather than competing with PEP 498 | ||
-------------------------------------------------------- | ||
Building atop f-strings rather than replacing them | ||
-------------------------------------------------- | ||
|
||
Earlier versions of this PEP attempted to serve as a complete substitute for | ||
:pep:`498` . With the introduction of 701, this pep can now build a more flexible delayed | ||
rendering capability on top of :pep:`498` and :pep:`701`'s eager rendering. | ||
:pep:`498` (f-strings) . With the acceptance of that PEP and the more recent :pep:`701`, | ||
this PEP can now build a more flexible delayed rendering capability | ||
on top of the existing f-string eager rendering. | ||
|
||
Assuming the presence of f-strings as a supporting capability simplified a | ||
number of aspects of the proposal in this PEP (such as how to handle substitution | ||
|