Very interesting proposal!
When reading this PEP, I also went back to skim PEP 501 and realised I had never actually merged @nhumrich’s updates from last year. I have now fixed that oversight, so the fully rendered version of the “template literal strings” proposal should appear on peps.python.org in the not too distant future. (Edit: the rendered version is live)
Functionally, I’m obviously in favour of the general idea, and have been for a long time. The first thread referenced in PEP 501 is actually the PEP 498 thread, since it started out as a PEP 498 competitor (and only later morphed into an idea that built on f-strings instead of competing with them).
Syntactically and semantically, I’m not sure replacing a first class object created via a dedicated string prefix (types.TemplateLiteral
and t
respectively in the 2023 update to PEP 501) with a particular call signature accessed via novel syntax would end up being a net win.
Using the introductory example from PEP 750, the PEP 501 equivalent would be:
name = "World"
greeting = greet(t"hello {name}")
assert greeting == "Hello WORLD!"
def greet(template:TemplateLiteral):
def render_template(parts):
salutation, recipient, *_ = parts
return f"{salutation.title().strip()} {recipient.upper()}!"
return template.render(render_template=render_template)
Edit: fixed the example greet
implementation to actually match the PEP 750 example
Since template renderers are just callables that take a template literal as an argument (it doesn’t even have to be their first argument), there are no lexer-based restrictions on how we would refer to them. Dotted names et al would all just work, since it is only the t
prefix that would need special handling when lexing.
As first class objects, they’re also able to natively support template concatenation and repetition.
Syntax highlighters would only need the minimal update to recognise t
as a valid string prefix, while type checkers would only need to know that a t-string defines a TemplateLiteral
object instead of a regular string.
Template literal support can also be added to existing methods that accept strings (which is particularly important for potential use cases like logging) rather than needing to define new APIs that fit the tagged string function signature.
Several of the other differences folks in this thread have been requesting (like eagerly evaluating the interpolated expressions by default) are also part of PEP 501, but I see those compile time details of how the template is decomposed from source code to runtime objects as less fundamental than the core structural difference between “t-strings always emit a first-class TemplateLiteral object, which may then be passed to a rendering function as a regular parameter” and “tagged strings are an alternate call syntax that pass the component parts of the template literal to the callable named by the string prefix”.
I do agree that PEP 750 is a generalisation of PEP 501, since given PEP 750 you could write a t
tagged string function that emitted a TemplateLiteral
object. I’m just not sure it’s a generalisation that increases the expressiveness of the syntax over passing TemplateLiteral
objects to regular functions.
One PEP 501 idea that PEP 750 does give me is that TemplateLiteral.render
should probably accept a render_text
callback (in addition to the already defined render_field
and render_template
callbacks), similar to the way PEP 750 makes it straightforward to customise the rendering of both the text portions and the interpolated fields based on the parameter types passed to the callable.
(Edit 2) As a general usage note: template literals are definitely syntactically noisier than tagged strings (e.g. html(t"<h1 {attributes}>Hello!</h1>")
vs html"<h1 {attributes}>Hello!</h1>"
). However, they’re also more explicit about what is actually happening at runtime (a function call to produce a particular kind of object from the given template string).