> In addition, I hope that the tooling ecosystem will adapt to support t-strings. For instance, I’d love to see black and ruff format t-string contents, and vscode color those contents, if they’re a common type like HTML or SQL.
This is such a strange take on t-strings. The only way for anything to infer that the template string is supposed to turn into valid HTML or SQL is to base it of the apparent syntax in the string, which can only be done in an ad-hoc fashion and has nothing to do with the template string feature.
The way the feature has been designed there is no indication in the string itself what type of content it is or what it will eventually be converted to. It’s all handled by the converting function.
As others have added, something like sql”select * from {table}” would have been able to do this, but there’s not even any guarantees that something that is in a template that will be converted into valid sql by a converting function should be any type of valid sql prior to that conversion. For all you know t“give me {table} but only {columns}” might be a converted into valid sql after the template is processed.
Will this allow neat SQL syntax like the following?
city = 'London'
min_age = 21
# Find all users in London who are 21 or older:
users = db.get(t'
SELECT * FROM users
WHERE city={city} AND age>{min_age}
')
If the db.get() function accepts a template, it should, right?
This would be the nicest way to use SQL I have seen yet.
Personally, this feels like a feature that is too focused on one problem to be a general feature. Python is getting huge. When people ask me if Python is easy and simple to learn I have to say "the basics, yes, but to to learn the whole language... not so much".
I feel like in this sense Go really is interesting by rejecting almost every single feature. Honestly not sure generics were worth it as they add a lot of complexity, and while they are nice, I don't need them very much. The general idea to keep the language at its original focus is the right idea IMO. C++ would be the most extreme case where the language itself barely resembles what it started out as.
What I really don't get is how it's any different than applying whatever function you would apply to the template, on the f-string variables. So instead of:
Why does this need to be a language feature. This could just be a separate library, we could use brackets instead of a letter before a string. I fear, Python is going down the path of C++
I'm a little late to the conversation (and a bit surprised to see this trending on HN) but am happy to answer any questions; I'll try to pop in throughout the day.
Landing in 3.14? Nice, but also oof, that's probably not getting to my employer's codebase for a year or two. And it sounds like it could really solve some problems for us, too.
Paging asottile - any plans to make a `future-tstrings`? :)
How do these interact with i18n? Can I load a translated t-string with `_(t"")` from a .po file? Can it include variable names and arbitrary code inside lambdas?
> t-strings evaluate to a new type, `string.templatelib.Template`
> To support processing, `Template`s give developers access to the string and its interpolated values before* they are combined into a final string.*
Are there any use-cases where processing a Template involves something other than (i) process each value, then (ii) recombine the results and the string parts, in their original order, to produce a new string? In other words, is the `process_template` function ever going to be substantially different from this (based on `pig_latin` from the article)?
def process_template(template: Template) -> str:
result = []
for item in template:
if isinstance(item, str):
result.append(item)
else:
result.append(process_value(item.value))
return "".join(result)
I haven't seen any examples where the function would be different. But if there aren't any, it's strange that the design requires every Template processing function to include this boilerplate, instead of making, say, a `Template.process` method that accepts a `process_value` function.
I wish they added the same thing JS has, where this "string literal prefix thingy" can be user-defined.
html`<p>${value}</p>` will actually run the function html(template). This means you can use this to "mark" a function in a way that can be detected by static analysis. Many editors will, for example, syntax highlight and lint any HTML marked this way, same with SQL, GraphQL and probably some others too.
> If you’ve worked with JavaScript, t-strings may feel familiar. They are the pythonic parallel to JavaScript’s tagged templates.
The syntax is template literals, not just "tagged templates". Which is a huge difference: template literals still act as real strings. They don't need a tag prefix to work, you have the option to tag them if and when needed.
As far as I understand it, t-strings can't do that. They're not strings, and you can't even coerce them into strings, you have to run them through a processor before they become a string. So they're nothing like JS's template literals, they're syntactic sugar for forming "an instance of an object that needs to be passed into a function that returns a string".
So I don't look forward to folks preferring f-strings over t-strings even when they really shouldn't, simply because "having to constantly convert them from not-a-string to a string is a hassle". If only they'd worked like JS template literals.. that would have been fantastic.
Debate around the usefulness aside, are there any linter rules for warning about f-strings in light of this? I can easily see where mistaking one for the other would cause problems. For context, I'm thinking specifically about tools like Black and MyPy.
TL;DR: like f-strings, all {foo} expressions in the t-string are evaluated immediately, but instead of immediately concatenating everything into a single result string, the t-string evaluation returns a Template object that keeps the interpolation results and the surrounding strings separate. This lets subsequent logic decide whether the interpolation results need any special escaping before concatenating them with the strings around them.
In other words, t-strings are basically f-strings where the final concatenation is delayed. And indeed, you can trivially implement f-strings using t-strings by performing a simple, non-escaped concatenation step: https://peps.python.org/pep-0750/#example-implementing-f-str...
by making it a generic `t` you lose explicit syntax highlighting. Where something like JS template`string` could determine which syntax to use based on the template value.
I supposed when assigning it to a, variable: SyntaxRecognizableTemplate, you could give it the hint necessary.
was this discussed in the PEP?
*edit: reading the PEP-750[1] it doesn't seem like it..
Not sure about introducing yet another string prefix. Between f-strings, raw strings, and i18n stuff, it’s already getting crowded. Curious how readable this will be in large codebases.
Honestly think this is a more useful feature and elegant solution than the walrus operator that was added. Formatting query strings has always felt messy especially with different DBs having their own non-standard ways of doing it.
Seems pretty neat so far, but I don't understand the motivation behind not allowing you to call str(template) and get the template as a normal string. I could imagine it being very useful to be able to gather up the template itself in a string to do stringy things with.
The only reason I could imagine, is if you are trying to protect developers from themselves, which kinda goes against the "we're all adults here" mentality that makes Python so great. I suppose it's easy enough to add that functionality, but come on.
If this is just for sql queries ... it'd be overkill especially where you need to compare the usual PREPARE statements with the hassle of keeping everyone on 3.14 and above.
Sure, this avoids issues with SQL injections. However, I have a hard time imagining any developer who would both make such fundamental errors with f-strings currently and also switching to this option when it ships.
Seems like a self selection which renders this meaningless, to some extent :/
I feel like this can be solved another way. S=f”my good code #### {potentially_evil_user_input} #### my good code again” then work around the ####. Of course, even better, S=evil_user_input and do a scrub on S first.
I really was on the side of being generally willing to accept new python features, but this is getting ridiculous. What an utterly pointless thing to bloat the language with. At this point my moving to clojure as my first line language of choice is only accelerating.
This is of the category "things I wouldn't want to use even for the specific hyper niche things they're intended for". What even does a "t-string" represent? Because it's clearly not a string of any kind, it's a weird kind of function call notation. The programmer sees something that looks like string formatting, but the program executes some arbitrary procedure that might not return a string whatsoever.
I enjoy f-strings, I guess some people need these.
And I love Python but, having been through 2->3 ( occasionally still going through it! ) whenever I see a new language feature my first thought is "Thank goodness it doesn't break everything that went before it".
Python’s new t-strings
(davepeck.org)611 points by tambourine_man 21 April 2025 | 463 comments
Comments
1. Allowing library developers to do whatever they want with {} expansions is a good thing, and will probably spawn some good uses.
2. Generalizing template syntax across a language, so that all libraries solve this problem in the same way, is probably a good thing.
This is such a strange take on t-strings. The only way for anything to infer that the template string is supposed to turn into valid HTML or SQL is to base it of the apparent syntax in the string, which can only be done in an ad-hoc fashion and has nothing to do with the template string feature.
The way the feature has been designed there is no indication in the string itself what type of content it is or what it will eventually be converted to. It’s all handled by the converting function.
As others have added, something like sql”select * from {table}” would have been able to do this, but there’s not even any guarantees that something that is in a template that will be converted into valid sql by a converting function should be any type of valid sql prior to that conversion. For all you know t“give me {table} but only {columns}” might be a converted into valid sql after the template is processed.
This would be the nicest way to use SQL I have seen yet.
I feel like in this sense Go really is interesting by rejecting almost every single feature. Honestly not sure generics were worth it as they add a lot of complexity, and while they are nice, I don't need them very much. The general idea to keep the language at its original focus is the right idea IMO. C++ would be the most extreme case where the language itself barely resembles what it started out as.
I just want this so badly, it's the main reason I drift back to JS:
- t-strings
- f-strings
- %-operator
- +-operator
- str.format()
https://www.psycopg.org/psycopg3/docs/api/sql.html
(while I also agree it gets crowded with yet another string prefix)
I'm a little late to the conversation (and a bit surprised to see this trending on HN) but am happy to answer any questions; I'll try to pop in throughout the day.
Paging asottile - any plans to make a `future-tstrings`? :)
`future-fstrings` (https://pypi.org/project/future-fstrings/) was a real QOL improvement for our team for a year or 2 around 2019 before we got onto Python 3.x.
> To support processing, `Template`s give developers access to the string and its interpolated values before* they are combined into a final string.*
Are there any use-cases where processing a Template involves something other than (i) process each value, then (ii) recombine the results and the string parts, in their original order, to produce a new string? In other words, is the `process_template` function ever going to be substantially different from this (based on `pig_latin` from the article)?
I haven't seen any examples where the function would be different. But if there aren't any, it's strange that the design requires every Template processing function to include this boilerplate, instead of making, say, a `Template.process` method that accepts a `process_value` function.html`<p>${value}</p>` will actually run the function html(template). This means you can use this to "mark" a function in a way that can be detected by static analysis. Many editors will, for example, syntax highlight and lint any HTML marked this way, same with SQL, GraphQL and probably some others too.
The syntax is template literals, not just "tagged templates". Which is a huge difference: template literals still act as real strings. They don't need a tag prefix to work, you have the option to tag them if and when needed.
As far as I understand it, t-strings can't do that. They're not strings, and you can't even coerce them into strings, you have to run them through a processor before they become a string. So they're nothing like JS's template literals, they're syntactic sugar for forming "an instance of an object that needs to be passed into a function that returns a string".
So I don't look forward to folks preferring f-strings over t-strings even when they really shouldn't, simply because "having to constantly convert them from not-a-string to a string is a hassle". If only they'd worked like JS template literals.. that would have been fantastic.
name = "World"
template = t"Hello {(lambda: name)}"
This looks cool
In other words, t-strings are basically f-strings where the final concatenation is delayed. And indeed, you can trivially implement f-strings using t-strings by performing a simple, non-escaped concatenation step: https://peps.python.org/pep-0750/#example-implementing-f-str...
I supposed when assigning it to a, variable: SyntaxRecognizableTemplate, you could give it the hint necessary.
was this discussed in the PEP?
*edit: reading the PEP-750[1] it doesn't seem like it..
[1] https://peps.python.org/pep-0750/#the-interpolation-type
name="A$ron"
z("echo Hello {name}")
Note that this is not an f-string. The z function expands the variables by parsing this string and accessing its caller's local variables.
https://github.com/NightMachinery/brish
[1] https://github.com/pgjones/sql-tstring
Also, don’t get me started on g strings.
The only reason I could imagine, is if you are trying to protect developers from themselves, which kinda goes against the "we're all adults here" mentality that makes Python so great. I suppose it's easy enough to add that functionality, but come on.
Seems like a self selection which renders this meaningless, to some extent :/
This is of the category "things I wouldn't want to use even for the specific hyper niche things they're intended for". What even does a "t-string" represent? Because it's clearly not a string of any kind, it's a weird kind of function call notation. The programmer sees something that looks like string formatting, but the program executes some arbitrary procedure that might not return a string whatsoever.
And I love Python but, having been through 2->3 ( occasionally still going through it! ) whenever I see a new language feature my first thought is "Thank goodness it doesn't break everything that went before it".