HN.zip

Python’s new t-strings

546 points by tambourine_man - 410 comments
serbuvlad [3 hidden]5 mins ago
All things considered, this is pretty cool. Basically, this replaces

    db.execute("QUERY WHERE name = ?", (name,))
with

    db.execute(t"QUERY WHERE name = {name}")
Does the benefit from this syntactic sugar outweigh the added complexity of a new language feature? I think it does in this case for two reasons:

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.

rwmj [3 hidden]5 mins ago
I did a safe OCaml implementation of this about 20 years ago, the latest version being here:

https://github.com/darioteixeira/pgocaml

Note that the variables are safely and correctly interpolated at compile time. And it's type checked across the boundary too, by checking (at compile time) the column types with the live database.

tasuki [3 hidden]5 mins ago
Yes, what you did is strictly more powerful than what the Python people did. And you did it 20 years ago. Well done, have an upvote. And yet, here we are in 2025 with Python popularity growing unstoppably and (approximately) no one caring about OCaml (and all the other languages better than Python). It makes me sad.
sanderjd [3 hidden]5 mins ago
Network effects are a beast!

But my two cents is that we're pretty lucky it's python that has taken off like a rocket. It's not my favorite language, but there are far worse that it could have been.

psychoslave [3 hidden]5 mins ago
You mean like Cobol? Oh wait!
rwmj [3 hidden]5 mins ago
I'm switching between C, OCaml, Python, bash & Rust roughly equally every day (to a lesser extent, Perl as well). Not everything is what gets on the front page of HN.
skeledrew [3 hidden]5 mins ago
It's interesting how the majority has explicitly chosen NOT to use the "better" languages. Is the majority really that bad in their judgment? Or is it that "better" is actually defined by adoption over time?
daedrdev [3 hidden]5 mins ago
It's clearly better in their opinion, they just aren't optimizing for the same metrics that you are. Python is better because it's easy for people to learn, imo.
throwawaymaths [3 hidden]5 mins ago
its not easy to learn. its a challenge even getting it installed and running. what even is a venv? how do you explain that to a beginner?

python is popular because its what teachers teach.

zahlman [3 hidden]5 mins ago
On modern Linux you can type `python` at the command prompt and get a REPL. On Windows you download an installer from the official website (just like one usually does to install anything on Windows), then use `py` at the command prompt.

You don't need to `import` anything to start teaching Python. Even then you can do quite a lot with the standard library. Even then, unless you're using 3.11 or later on Linux you can let Pip install with `--user` until you actually need to isolate things between projects. (And even with new Python on Linux, the instructor can typically avert this by just installing a separate Python in `/usr/local/bin` for example. Yes, that's "cheating", depending on the classroom environment. But that's part of the point: installation hurdles are hurdles for self-learners, not for students.)

You only need to learn about virtual environments once you have projects with mutually conflicting dependencies, and/or once you're at a point where you're ready to publish your own software and should be learning proper testing and development practices. (Which will be largely orthogonal to programming, and not trivial, in any language.)

And when your students do get to that point, you can give them a link such as https://chriswarrick.com/blog/2018/09/04/python-virtual-envi... .

Teachers teach Python because it's easy to teach while still being relevant to the real world, in particular because boilerplate is minimized. You don't have to explain jargon-y keywords like "public" or "static" up front. You don't have to use classes for quite some time (if ever, really). You can express iteration naturally. Types are naturally thought of in terms of capabilities.

In my mind, Python has all the pedagogical advantages of Lisp, plus enough syntactic cues to prevent getting "lost in a sea of parentheses". (Of course, it lacks plenty of other nice Lisp-family features.)

psunavy03 [3 hidden]5 mins ago
If someone is challenged figuring out a venv and they're not an absolute beginner, perhaps they aren't cut out to work in technology. There are countless subjects in the field more challenging and complicated to wrap one's brain around.

Also, in 2025, just use uv.

acdha [3 hidden]5 mins ago
You don’t need to teach it to a beginner. The first of learning doesn’t need more than the standard library and when you need more than that you’re either giving them the single command necessary to run or, more likely, having them use a template project where a tool like Poetry is doing that automatically.

What this usually hits isn’t that managing Python packages is hard in 2025 but that many people do not learn how their operating system works conceptually until the first time they learn to program and it’s easy to conflate that with the first language you learn.

jyounker [3 hidden]5 mins ago
It has become successful largely because it has always had really good foreign function interface. If you have a scientific or mathematical library laying around in C, then you could wire it up to Python, and then suddenly you have all the flexibility of a (fairly clean) scripting language to orchestrate your high speed C.

Good examples of this are numpy and tensorflow.

daedrdev [3 hidden]5 mins ago
someone learning python as their first language knows so little its perfectly fine to let them pollute their global environment. Someone who knows other languages can understand what venv is for.

Instead they can type python to open a shell and use python to immediately run their file.

angra_mainyu [3 hidden]5 mins ago
momentum + ecosystem often play a much larger role than actual language merits.
dhruvrajvanshi [3 hidden]5 mins ago
I think you're being too unfair. People aren't dumb.

It's also about how much better.

Beyond a decent enough type system, the advantages start to flatten and other factors start to matter more.

Can't speak too much for python, but as someone who's written large amounts of code in OCaml and Typescript, the strictest compiler options for Typescript are good enough.

benwilber0 [3 hidden]5 mins ago
Aren't there other benefits to server-side parameter binding besides just SQL-injection safety? For instance, using PG's extended protocol (binary) instead of just raw SQL strings. Caching parameterized prepared statements, etc.

Also:

    db.execute(t"QUERY WHERE name = {name}")
Is dangerously close to:

    db.execute(f"QUERY WHERE name = {name}")

A single character difference and now you've just made yourself trivially injectible.

I don't think this new format specifier is in any way applicable to SQL queries.

WorldMaker [3 hidden]5 mins ago
Templates are a very different duck type from strings and intentionally don't support __str__(). The SQL tool can provide a `safe_execute(Template)` that throws if passed a string and not a Template. You can imagine future libraries that only support Template and drop all functions that accept strings as truly safe query libraries.

> Caching parameterized prepared statements, etc.

Templates give you all the data you need to also build things like cacheable parameterized prepared statements. For DB engines that support named parameters you can even get the interpolation expression to auto-name parameters (get the string "name" from your example as the name of the variable filling the slot) for additional debugging/sometimes caching benefits.

kazinator [3 hidden]5 mins ago
But t"..." and f"..." have different types; we can make db.execute reject character strings and take only template objects.
hombre_fatal [3 hidden]5 mins ago
You solve that with an execute(stmt) function that requires you to pass in a template.

In Javascript, sql`where id = ${id}` is dangerously close to normal string interpolation `where id = ${id}`, and db libs that offer a sql tag have query(stmt) fns that reject strings.

zahlman [3 hidden]5 mins ago
> A single character difference and now you've just made yourself trivially injectible.

No; a single character difference and now you get a `TypeError`, which hopefully the library has made more informative by predicting this common misuse pattern.

masklinn [3 hidden]5 mins ago
> Aren't there other benefits to server-side parameter binding besides just SQL-injection safety? For instance, using PG's extended protocol (binary) instead of just raw SQL strings. Caching parameterized prepared statements, etc.

All of which can be implemented on top of template strings.

> A single character difference and now you've just made yourself trivially injectible.

It's not just a one character difference, it's a different type. So `db.execute` can reject strings both statically and dynamically.

> I don't think

Definitely true.

> this new format specifier is in any way applicable to SQL queries.

It's literally one of PEP 750's motivations.

tczMUFlmoNk [3 hidden]5 mins ago
> > I don't think

> Definitely true.

The rest of your comment is valuable, but this is just mean-spirited and unnecessary.

woodrowbarlow [3 hidden]5 mins ago
nitpicking:

> It's not just a one character difference, it's a different type. So `db.execute` can reject strings both statically and dynamically.

in this case, that's not actually helpful because SQL statements don't need to have parameters, so db.execute will always need to accept a string.

anamexis [3 hidden]5 mins ago
You can just pass it a template with no substitutions.
masklinn [3 hidden]5 mins ago
> db.execute will always need to accept a string.

No. A t-string with no placeholders is perfectly fine. You can use that even if you have no parameters.

davepeck [3 hidden]5 mins ago
> Caching parameterized prepared statements, etc.

I didn’t explicitly mention this in my post but, yes, the Template type is designed with caching in mind. In particular, the .strings tuple is likely to be useful as a cache key in many cases.

rangerelf [3 hidden]5 mins ago
>> I don't think >Definitely true.

I thought we left middle-school playground tactics behind.

willcipriano [3 hidden]5 mins ago

    from string.templatelib import Template

    def execute(query: Template)
Should allow for static analysis to prevent this issue if you run mypy as part of your pr process.

That would be in addition to doing any runtime checks.

benwilber0 [3 hidden]5 mins ago
The first mistake we're going to see a library developer make is:

    def execute(query: Union[str, Template]):

Maybe because they want their execute function to be backwards compatible, or just because they really do want to allow either raw strings are a template string.
masklinn [3 hidden]5 mins ago
> they really do want to allow either raw strings are a template string.

I’d consider that an invalid use case:

1. You can create a template string without placeholders.

2. Even if the caller does need to pass in a string (because they’re executing from a file, or t-strings don’t support e.g. facetting) then they can just… wrap the string in a template explicitly.

VWWHFSfQ [3 hidden]5 mins ago
> It's literally one of PEP 750's motivations.

Python is notorious for misguided motivations. We're not "appealing to authority" here. We're free to point out when things are goofy.

rastignack [3 hidden]5 mins ago
Quite easy to detect with a proper linter.
VWWHFSfQ [3 hidden]5 mins ago
> I don't think this new format specifier is in any way applicable to SQL queries.

Agree. And the mere presence of such a feature will trigger endless foot-gunning across the Python database ecosystem.

InstaPage [3 hidden]5 mins ago
t vs f going to be hard to spot.
acdha [3 hidden]5 mins ago
This is true of many other things, which is why we have type checkers and linters to be perfectly rigorous rather than expecting humans to never make mistakes.
MR4D [3 hidden]5 mins ago
Dang! Thanks for pointing this out.

I had to look SEVERAL times at your comment before I noticed one is an F and the other is a T.

This won’t end well. Although I like it conceptually, this few pixel difference in a letter is going to cause major problems down the road.

pphysch [3 hidden]5 mins ago
How? tstrings and fstrings are literals for completely different types.

CS has survived for decades with 1 and 1.0 being completely different types.

Certhas [3 hidden]5 mins ago
I had an extended debugging session last week that centered on 1 and 1. confusion in a library I have to use...
pphysch [3 hidden]5 mins ago
Yeah, it's a real bummer when that happens. I wish JSON never tried to do types.
MR4D [3 hidden]5 mins ago
Reread my comment. It’s about noticing you have an “f” or a “t” and both are very similar characters.
rocha [3 hidden]5 mins ago
Yes, but you will get an error since string and templates are different types and have different interfaces.
Izkata [3 hidden]5 mins ago
Click "parent" a few times and look at the code example that started this thread. It's using the same function in a way that can't distinguish whether the user intentionally used a string (including an f-string) and a t-string.
zahlman [3 hidden]5 mins ago
Yes, and the parent is misguided. As was pointed out in multiple replies, the library can distinguish whether an ordinary string or a t-string is passed because the t-string is not a string instance, but instead creates a separate library type. A user who mistakenly uses an f prefix instead of a t prefix will, with a properly designed library, encounter a `TypeError` at runtime (or a warning earlier, given type annotations and a checker), not SQL injection.
Izkata [3 hidden]5 mins ago
Because they're both passed to "execute", which can't tell between the f-string and a non-interpolated query, so it just has to trust you did the right thing. Typoing the "t" as an "f" introduces SQL injection that's hard to spot.
vlovich123 [3 hidden]5 mins ago
Assuming `execute` takes both. You could have `execute(template)` and `execute_interpolated(str, ...args)` but yeah if it takes both you'll have challenges discouraging plain-text interpolation.
Izkata [3 hidden]5 mins ago
It would have to be the other way around or be a (possibly major) breaking change. Just execute() with strings is already standard python that all the frameworks build on top of, not to mention tutorials:

https://docs.python.org/3/library/sqlite3.html

https://www.psycopg.org/docs/cursor.html

https://dev.mysql.com/doc/connector-python/en/connector-pyth...

zahlman [3 hidden]5 mins ago
`execute` can tell the difference, because `t"..."` does not create the same type of object that `f"..."` does.
tetha [3 hidden]5 mins ago
Or you could use this in a library like sh with

    sh(t"stat {some_file}")
With t-strings you could run proper escaping over the contents of `some_file` before passing it to a shell.

I'd have to take a look at the order things happen in shell, but you might even be able to increase security/foot-gun-potential a little bit here by turning this into something like `stat "$( base64 -d [base64 encoded content of some_file] )"`.

nhumrich [3 hidden]5 mins ago
You should check out PEP 787
zahlman [3 hidden]5 mins ago
Oh! I missed this one because I've been looking specifically at the Packaging forum rather than the PEPs forum. This looks like a brilliant use case. (I'm aiming for wide compatibility - back to 3.6 - with my current projects, but I look forward to trying this out if and when it's accepted and implemented.)

Now if only the overall `subprocess` interface weren't so complex....

pauleveritt [3 hidden]5 mins ago
We really should just point most of these comments at that PEP. Thanks for getting it out so fast.
tetha [3 hidden]5 mins ago
Hmm, PEP-787 has some interesting discussions around it. I'll have to sort my thoughts on these aspects a bit.
dhruvrajvanshi [3 hidden]5 mins ago
Not Python but this is exactly the idea behind zx

https://github.com/google/zx

zahlman [3 hidden]5 mins ago
3. It prevents the developer from trying

  db.execute(f"QUERY WHERE name = {name}")
or

  db.execute("QUERY WHERE name = %s" % name, ())
or other ways of manually interpolating the string - because `db.execute` can flag a `TypeError` if given a string (no matter how it was constructed) rather than a `Template` instance.
mikeholler [3 hidden]5 mins ago
A potential concern is how close this looks to the pattern they're trying to override.

    db.execute(f"QUERY WHERE name = {name}")
versus

    db.execute(t"QUERY WHERE name = {name}")
notatoad [3 hidden]5 mins ago
The key point is that t-strings are not strings. Db.execute(t”…”) would throw an exception, because t”…” is not a string and cannot be interpreted as one.

In order for a library to accept t-strings, they need to make a new function. Or else change the behavior and method signature of an old function, which I guess they could do but any sanely designed library doesn’t do.

Handling t-strings will require new functions to be added to libraries.

gls2ro [3 hidden]5 mins ago
yes but the bug is writing f instead of t and I assume f will just work.

To clarify even more:

The problem is not writing by mistake t instead of f => this is what we want and then for this we implement a new function

The problem is writing f instead of t => and this will silently work I assume (not a Python dev just trying to understand the language design)

masklinn [3 hidden]5 mins ago
> The problem is writing f instead of t => and this will silently work I assume (not a Python dev just trying to understand the language design)

In the fullness of time it has no reason to. Even in the worst case scenario where you have to compose the query dynamically in a way t-strings can’t support, you can just instantiate a Template object explicitely.

fzzzy [3 hidden]5 mins ago
But won't the f string version fail loudly because there's no name parameter?
benwilber0 [3 hidden]5 mins ago
the {name} parameter is in the locals() dict like it always is
fzzzy [3 hidden]5 mins ago
Good point. Perhaps the database api could refuse strings and require Templates.
bshacklett [3 hidden]5 mins ago
That’s a big breaking change around a brand new feature. I’m sure it could be done well, but it gives me the shivers.
daedrdev [3 hidden]5 mins ago
much better would be execute_template(t"...")
Tenoke [3 hidden]5 mins ago
I don't see what it adds over f-string in that example?
ds_ [3 hidden]5 mins ago
The execute function can recognize it as a t-string and prevent SQL injection if the name is coming from user input. f-strings immediately evaluate to a string, whereas t-strings evaluate to a template object which requires further processing to turn it into a string.
Tenoke [3 hidden]5 mins ago
Then the useful part is the extra execute function you have to write (it's not just a substitute like in the comment) and an extra function can confirm the safety of a value going into a f-string just as well.

I get the general case, but even then it seems like an implicit anti-pattern over doing db.execute(f"QUERY WHERE name = {safe(name)}")

ubercore [3 hidden]5 mins ago
Problem with that example is where do you get `safe`? Passing a template into `db.execute` lets the `db` instance handle safety specifically for the backend it's connected to. Otherwise, you'd need to create a `safe` function with a db connection to properly sanitize a string.

And further, if `safe` just returns a string, you still lose out on the ability for `db.execute` to pass the parameter a different way -- you've lost the information that a variable is being interpolated into the string.

Tenoke [3 hidden]5 mins ago
db.safe same as the new db.execute with safety checks in it you create for the t-string but yes I can see some benefits (though I'm still not a fan for my own codebases so far) with using the values further or more complex cases than this.
ubercore [3 hidden]5 mins ago
Yeah but it would have to be something like `db.safe("SELECT * FROM table WHERE id = {}", row_id)` instead of `db.execute(t"SELECT * FROM table WHERE id = {row_id}")`.

I'd prefer the second, myself.

Tenoke [3 hidden]5 mins ago
No, just `db.execute(f"QUERY WHERE name = {db.safe(name)}")`

And you add the safety inside db.safe explicitly instead of implicitly in db.execute.

If you want to be fancy you can also assign name to db.foos inside db.safe to use it later (even in execute).

sanderjd [3 hidden]5 mins ago
This is just extra boilerplate though, for what purpose?.

I think one thing you might be missing is that in the t-string version, `db.execute` is not taking a string; a t-string resolves to an object of a particular type. So it is doing your `db.safe` operation, but automatically.

panzi [3 hidden]5 mins ago
Of course you can write code like that. This is about making it easier not to accidentally cause code injection by forgetting the call of safe(). JavaScript had the same feature and some SQL libraries allow only the passing of template strings, not normal strings, so you can't generate a string with code injection. If you have to dynamically generate queries they allow that a parameter is another template string and then those are merged correctly. It's about reducing the likelihood of making mistakes with fewer key strokes. We could all just write untyped assembly instead and could do it safely by paying really good attention.
ZiiS [3 hidden]5 mins ago
But if someone omits the `safe` it may still work but allow injection.
thunky [3 hidden]5 mins ago
Same is true if someone forgets to use t" and uses f" instead.

At least db.safe says what it does, unlike t".

ewidar [3 hidden]5 mins ago
Not really, since f"" is a string and t"" is a template, you could make `db.execute` only accept templates, maybe have

`db.execute(Template)` and `db.unsafeExecute(str)`

thunky [3 hidden]5 mins ago
agreed. but then you're breaking the existing `db.execute(str)`. if you don't do that, and instead add `db.safe_execute(tpl: Template)`, then you're back to the risk that a user can forget to call the safe function.

also, you're trusting that the library implementer raises a runtime exception if a string a passed where a template is expected. it's not enough to rely on type-checks/linting. and there is probably going to be a temptation to accept `db.execute(sql: Union[str, Template])` because this is non-breaking, and sql without params doesn't need to be templated - so it's breaking some stuff that doesn't need to be broken.

i'm not saying templates aren't a good step forward, just that they're also susceptible to the same problems we have now if not used correctly.

fwip [3 hidden]5 mins ago
Your linter can flag the type mismatch, and/or the function can reject f"" at runtime. This is because t"" yields a Template, not a str.

Template is also more powerful/concise in that the stringify function can handle the "formatting" args however it looks.

Note also, that there's no requirement that the template ever become a str to be used.

Izkata [3 hidden]5 mins ago
The first one already exists like:

  db.execute("SELECT * FROM table WHERE id = ?", (row_id,))
Mawr [3 hidden]5 mins ago
But you have to remember to call the right safe() function every time:

    db.execute(f"QUERY WHERE name = {name}")

    db.execute(f"QUERY WHERE name = {safe_html(name)}")
Oops, you're screwed and there is nothing that can detect that. No such issue with a t-string, it cannot be misused.
zahlman [3 hidden]5 mins ago
> Then the useful part is the extra execute function you have to write

Well, no, the library author writes it. And the library author also gets to detect whether you pass a Template instance as expected, or (erroneously) a string created by whatever formatting method you choose. Having to use `safe(name)` within the f-string loses type information, and risks a greater variety of errors.

NewEntryHN [3 hidden]5 mins ago
Some SQL engines support accepting parameters separately so that values get bound to the query once the abstract syntax tree is already built, which is way safer than string escapes shenanigans.
ljm [3 hidden]5 mins ago
I’d always prefer to use a prepared statement if I can, but sadly that’s also less feasible in the fancy new serverless execution environments where the DB adapter often can’t support them.

For me it just makes it easier to identify as safe, because it might not be obvious at a glance that an interpolated template string is properly sanitised.

dragonwriter [3 hidden]5 mins ago
> and an extra function can confirm the safety of a value going into a f-string just as well.

Yes, you could require consumers to explicitly sanitize each parameter before it goes into the f-string, or, because it has the structure of what is fixed and what is parameters, it can do all of that for all parameters when it gets a t-string.

The latter is far more reliable, and you can't do it with an f-string because an f-string after creation is just a static string with no information about construction.

teruakohatu [3 hidden]5 mins ago
If I pass an f-string to a method, it just sees a string. If I pass a t-string the method can decide how to process the t-string.
sureglymop [3 hidden]5 mins ago
Wouldn't this precisely lead to sql injection vulnerabilities with f-strings here?
burky [3 hidden]5 mins ago
f-strings won’t sanitize the value, so it’s not safe. The article talks about this.
Tenoke [3 hidden]5 mins ago
The article talked about it but the example here just assumes they'll be there.
sanderjd [3 hidden]5 mins ago
What do you mean by "they"? You mean the template interpolation functions?

Yes, the idea is that by having this in the language, library authors will write these implementations for use cases where they are appropriate.

Tenoke [3 hidden]5 mins ago
The sanitization. Just using a t-string in your old db.execute doesn't imply anything safer is going on than before.
nemetroid [3 hidden]5 mins ago
Your "old" db.execute (which presumably accepts a regular old string) would not accept a t-string, because it's not a string. In the original example, it's a new db.execute.
masklinn [3 hidden]5 mins ago
Using a t-string in a db.execute which is not compatible with t-strings will result in an error.

Using a t-string in a db-execute which is, should be as safe as using external parameters. And using a non-t-string in that context should (eventually) be rejected.

Tenoke [3 hidden]5 mins ago
Again, just because a function accepts a t string it doesn't mean there's sanitization going on by default.
tikhonj [3 hidden]5 mins ago
Yes, but if a function accepts a template (which is a different type of object from a string!), either it is doing sanitization, or it explicitly implemented template support without doing sanitization—hard to do by accident!

The key point here is that a "t-string" isn't a string at all, it's a new kind of literal that's reusing string syntax to create Template objects. That's what makes this new feature fundamentally different from f-strings. Since it's a new type of object, libraries that accept strings will either have to handle it explicitly or raise a TypeError at runtime.

Tenoke [3 hidden]5 mins ago
I'm not sure why you think it's harder to use them without sanitization - there is nothing inherent about checking the value in it, it's just a nice use.

You might have implemented the t-string to save the value or log it better or something and not even have thought to check or escape anything and definitely not everything (just how people forget to do that elsewhere).

sanderjd [3 hidden]5 mins ago
I really think you're misunderstanding the feature. If a method has a signature like:

    class DB:
        def execute(query: Template):
            ...
It would be weird for the implementation to just concatenate everything in the template together into a string without doing any processing of the template parameters. If you wanted an unprocessed string, you would just have the parameter be a string.
Tenoke [3 hidden]5 mins ago
I'm not. Again, you might be processing the variable for logging or saving or passing elsewhere as well or many other reasons unrelated to sanitization.
sanderjd [3 hidden]5 mins ago
Taking a Template parameter into a database library's `execute` method is a big bright billboard level hint that the method is going to process the template parameters with the intent to make the query safe. The documentation will also describe the behavior.

You're right that the authors of such libraries could choose to do something different with the template parameter. But none of them will, for normal interface design reasons.

A library author could also write an implementation of a `plus` function on a numerical type that takes another numerical type, and return a string with the two numbers concatenated, rather than adding them together.

But nobody will do that, because libraries with extremely surprising behavior like that won't get used by anybody, and library authors don't want to write useless libraries. This is the same.

Ukv [3 hidden]5 mins ago
The original comment said that it'd replace

    db.execute("QUERY WHERE name = ?", (name,))
with

    db.execute(t"QUERY WHERE name = {name}")
It's true that in theory `db.execute` could ignore semantics and concatenate together the template and variables to make a string without doing any sanitisation, but isn't the same true of the syntax it was claimed to replace?

Just because templates (or the previous syntax of passing in variables separately) could be used in a way that's equivalent safety-wise to an f-string by a poorly designed library does not mean that they add nothing over an f-string in general - they move the interpolation into db.execute where it can do its own sanitization and, realistically, sqlite3 and other libraries explicitly updated to take these will use it to do proper sanitization.

nemetroid [3 hidden]5 mins ago
Sure, and the safe() function proposed upthread might also just be doing logging.
masklinn [3 hidden]5 mins ago
Because t-strings don't create strings, so if the library doesn't support t-strings the call can just error.
sim7c00 [3 hidden]5 mins ago
it makes it so people too lazy to make good types and class will be getting closer to sane code without doing sane code...

imagine writing a SqL where u put user input into query string directly.

now remember its 2025, lie down try not to cry.

evertedsphere [3 hidden]5 mins ago
safety against sql injection
int_19h [3 hidden]5 mins ago
Python is not the first one to get this feature. It's been present in JS for some time now, and before that in C# (not sure if that's the origin or they also borrowed it from somewhere). Python adopted it based in part on successful experience in those other languages.
serbuvlad [3 hidden]5 mins ago
That's really cool. I don't use JS or C#, so I wasn't aware of this, but it's a good idea.
NewEntryHN [3 hidden]5 mins ago
Assuming you also need to format non-values in the SQL (e.g. column names), how does the `execute` function is supposed to make the difference between stuff that should be formatted in the string vs a parametrized value?
masklinn [3 hidden]5 mins ago
Same as currently: the library provides some sort of `Identifier` wrapper you can apply to those.
NewEntryHN [3 hidden]5 mins ago
Fair enough. It would be nice if Python allowed to customize the formatting options after `:`

This way you could encode such identifier directly in the t-string variable rather than with some "out-of-band" logic.

masklinn [3 hidden]5 mins ago
> Fair enough. It would be nice if Python allowed to customize the formatting options after `:`

It does, the `Interpolation` object contains an arbitrary `format_spec` string: https://peps.python.org/pep-0750/#the-interpolation-type

However I think using the format spec that way would be dubious and risky, because it makes the sink responsible for whitelisting values, and that means any processing between the source and sink becomes a major risk. It's the same issue as HTML templates providing `raw` output, now you have to know to audit any modification to the upstream values which end there, which is a lot harder to do than when "raw markup" values are reified.

> rather than with some "out-of-band" logic.

It's the opposite, moving it to the format spec is out of band because it's not attached to values, it just says "whatever value is here is safe", which is generally not true.

Unless you use the format spec as a way to signal that a term should use identifier escaping rules rather than value escaping rules (something only the sink knows), and an `Identifier` wrapper remains a way to bypass that.

pphysch [3 hidden]5 mins ago
> Unless you use the format spec as a way to signal that a term should use identifier escaping rules rather than value escaping rules (something only the sink knows)

This should be quiet common in the SQL applications. It will be nice to write t"select {name:id} from {table:id} where age={age}" and be confident that the SQL will be formatted correctly, with interpolations defaulting to (safe) literal values.

mcintyre1994 [3 hidden]5 mins ago
The article does mention that the function receiving the template has access to those formatting options for each interpolation, so presumably you could abuse the ones that are available for that purpose?
amelius [3 hidden]5 mins ago
One thing it misses is compile-time checks for e.g. the format spec.
karamanolev [3 hidden]5 mins ago
Doesn't all of Python miss that, having (close to) no compile time?
amelius [3 hidden]5 mins ago
Python does some checks before it runs code. E.g.:

    print("hello")

    def f():
        nonlocal foo
gives:

    SyntaxError: no binding for nonlocal 'foo' found
before printing hello, and note that f() wasn't even called.
nomel [3 hidden]5 mins ago
I think it's just giving an error because a valid AST can't be made, which means valid bytecode can't be made. "<word> <word>" is only valid syntax if one is a reserved word. `nonlocal(foo)` is just fine, of course.
zahlman [3 hidden]5 mins ago
No, it gives an error because `nonlocal foo` requests that the name `foo` be looked up in a closure, but `f` doesn't have such a closure (the `foo` defined outside the function is global instead). `nonlocal` is the same sort of keyword as `global` but for enclosing functions instead of the global namespace; see also https://stackoverflow.com/questions/1261875 .
pansa2 [3 hidden]5 mins ago
> "<word> <word>" is only valid syntax if one is a reserved word.

`nonlocal` is a keyword

VWWHFSfQ [3 hidden]5 mins ago
> Allowing library developers to do whatever they want with {} expansions is a good thing, and will probably spawn some good uses.

I completely disagree with this. Look what happened to Log4J when it was given similar freedoms.

serbuvlad [3 hidden]5 mins ago
I think this would have solved the log4j vulnerability, no?

As I understand it, log4j allowed malicious ${} expansion in any string passed to logging functions. So logging user generated code at all would be a security hole.

But Python's t-strings purposely _do not_ expand user code, they only expand the string literal.

pinoy420 [3 hidden]5 mins ago
Now instead of being explicit all it takes is someone unfamiliar with t strings (which will be almost everyone - still few know about f strings and their formatting capabilities) to use an f instead and you are in for a bad time.
mcintyre1994 [3 hidden]5 mins ago
Any sane library will just error when you pass a string to a function that expects a template though. And that library will have types too so your IDE tells you before you get that far.
Dx5IQ [3 hidden]5 mins ago
Such library functions tend to also accept a string as a valid input. E.g. db.execute from the GP usually works with strings to allow non-parametrized SQL queries.
kccqzy [3 hidden]5 mins ago
The library should just refuse strings. If a non parametrized query is desired, it could require the user to supply a t-string with no {}.
_Algernon_ [3 hidden]5 mins ago
This would break backwardcompatibility pretty hard. In many cases it may not be worth it.
hombre_fatal [3 hidden]5 mins ago
Javascript already has prior art here.

A library can extend an existing database library like 'pg' so that PgClient#query() and PgPool#query() require string template statements.

That way 'pg' can continue working with strings, and people who want nice templated strings can use the small extension library, and the small extension library makes it impossible to accidentally pass strings into the query functions.

eichin [3 hidden]5 mins ago
But now at least the language has the necessary rope (and an opportunity for a cultural push to insist on it.)
falcor84 [3 hidden]5 mins ago
That is an issue, but essentially it boils down to the existing risk of unknowledgeable people not escaping untrusted inputs. The solution should be more education and better tooling (linters, SAST), and t-strings are likely to help with both.
masklinn [3 hidden]5 mins ago
t-strings allow building APIs which don't accept strings at all (or require some sort of opt-in), and will always error on such. That's the boon.

Having to write

    cr.execute(t"...")
even when there's nothing to format in is not a big imposition.
hackrmn [3 hidden]5 mins ago
I suppose lack of overlap in the "interface surface" (attributes, including callables) between `str` and `Template` should nip the kind of issue in the bud -- being passed a `Template` and needing to actually "instantiate" it -- accessing `strings` and `values` attributes on the passed object, will likely fail at runtime when attempted on a string someone passed instead (e.g. confusing a `t`-string with an `f`-string)?
sanderjd [3 hidden]5 mins ago
No, because they don't return a string, so good library authors will raise a type error when that happens, for exactly this reason.
florbnit [3 hidden]5 mins ago
> 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.

spankalee [3 hidden]5 mins ago
I think you'll just see a pattern like:

    html(t"<h1>Hello</h1>")
And highlighters and static analyzers will key off of this.

JavaScript's tagged template literals are actually about as flexible as this, since you can dynamically choose the tag function, it's just very rare to do so, so tools assume a lot based on the name of the function. Python tools can basically do the same thing, and just not support t-strings that aren't nested inside a well-named processing function.

dandellion [3 hidden]5 mins ago
Another option would be type hints, something like `title: HTMLTemplate = t"<h1>Hello</h1>"`.
pauleveritt [3 hidden]5 mins ago
The original PEP and the original discussion had this in scope. We removed it to let this emerge later. There are different ways to signal the language -- some more friendly to tooling, some more robust.
lolinder [3 hidden]5 mins ago
PyCharm (as well as the other JetBrains IDEs) has for at least 10 years supported language injection in Python strings using a comment [0]. Worst case scenario there's no reason whatsoever that that couldn't be used by formatters to apply auto-formatting in a structured and deterministic way.

But that's far from the only option, either! IntelliJ for Java also supports annotations on arguments [1] that then mean that you get syntax highlighting everywhere you use a string literal with the given function:

    public void query(@Language("SQL") String sql)
In Python, typing.Annotated appears to have been specifically designed for purposes like this [2]:

> If a library or tool encounters an annotation Annotated[T, x] and has no special logic for the metadata, it should ignore the metadata and simply treat the annotation as T. As such, Annotated can be useful for code that wants to use annotations for purposes outside Python’s static typing system.

So something like this should be perfectly viable:

    SQL = Annotated[Template, "language", "SQL"]
    
    def query(sql_query: SQL):
        # do stuff with Template to sanitize
    
    query(t"SELECT * FROM foo WHERE bar={bar}")
Now, where you're right is that we shouldn't actually require template strings to accomplish this! As noted, JetBrains has been doing this since forever. But maybe template strings will be useful enough for purposes like this that the tooling will actually evolve to support it in formatters and other editors (and maybe PyCharm can get some of the better support that Java has from JetBrains).

[0] https://www.jetbrains.com/help/pycharm/using-language-inject...

[1] https://www.jetbrains.com/help/idea/using-language-injection...

[2] https://docs.python.org/3/library/typing.html#typing.Annotat...

davepeck [3 hidden]5 mins ago
> This is such a strange take on t-strings

I understand why this seems strange at first!

As Paul mentioned, we spent quite a lot of time considering these issues as PEP 750 came together. In the end, we concluded (a) the PEP leaves open quite a few potential approaches for tools to adopt (not just the one you suggest, as others here have pointed out), and (b) it's ultimately something that the broader tooling community needs to rally around and should probably be out of scope for the PEP itself.

So, with that background in mind, I am indeed hopeful we'll see the ecosystem adapt! :-)

acdha [3 hidden]5 mins ago
Couldn’t you do this with a type annotation? e.g. SQLAlchemy could have a SQL type so tools like mypy could see a Template instance and confirm you’re using it safely but Black, Ruff, or SQLFluff could look for the more specialized Annotated[Template, SQL] to realize that the template could be formatted as SQL, and something like Django could even have Annotated[Template, Email], Annotated[Template, HTML], or Annotated[Template, JSX] to indicate what context the same templating syntax is targeting.
pauleveritt [3 hidden]5 mins ago
This is what we discussed in the first revision of the PEP (the use of `Annotated`.) But we found out: linters don't know anything about the Python type system.

We hope to get a community around all of this, stuff at PyCon US, EuroPython, etc. and work some of this out. The JSX/TSX world really has good tooling. We can provide that for those that want it, perhaps better on some aspects.

acdha [3 hidden]5 mins ago
Interesting, thanks for the background. I’ve been curious what Astral is going to do in the space but also worry about what happens when their funding runs out.
Someone [3 hidden]5 mins ago
> 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

Not the only thing. You can also look at how it is used. Your editor could know of how some popular libraries use t-strings, track which t-strings get passed into functions from those libraries, and use that to assume what grammar the t-string should follow.

Is that cheating? In some sense, yes, but it also is useful and likely will be worth it for quite a few programmers.

pphysch [3 hidden]5 mins ago
Python has had type annotations for a decade, and modern IDEs can interpret them.

Writing `query: SQL = t"SELECT ..."` is a small price to pay for such a DX boost.

TekMol [3 hidden]5 mins ago
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.

zahlman [3 hidden]5 mins ago
You would need triple-quotes to span multiple lines, but yes, that is exactly how it's intended to work. `db.get` will receive a Template instance, which stores string parts something like `('\n SELECT * FROM users\n WHERE city=', ' AND age>', '\n')` and interpolated parts like `(Interpolation('London'), Interpolation(21))`. It's then responsible for assembling and executing the query from that.
meander_water [3 hidden]5 mins ago
This definitely seems like one of the motivations for implementing this feature in the first place - https://peps.python.org/pep-0750/#motivation.

Having more control over the interpolation of string values is a win IMO.

fweimer [3 hidden]5 mins ago
The SQLite extension for Tcl offers something similar:

    db1 eval {INSERT INTO t1 VALUES(5,$bigstring)} 
https://sqlite.org/tclsqlite.html#the_eval_method
wizzwizz4 [3 hidden]5 mins ago
As I understand, that's less powerful, because you can do:

    t"INSERT INTO mytable VALUES ({s}, {s[::-1]})"
but you can't do:

    mydb eval {INSERT INTO mytable VALUES ($s, [string reverse $s])}
Instead, you have to write:

    set t [string reverse $s]
    mydb eval {INSERT INTO mytable VALUES ($s, $t)}
There's no reason you couldn't have such power in Tcl, though: it's just that the authors of SQLite didn't.
mcintyre1994 [3 hidden]5 mins ago
That’s the sort of thing people have built with the equivalent feature in JavaScript, so it should do. Eg https://github.com/andywer/squid is a nice example.
bombela [3 hidden]5 mins ago
Yes, it's quite delightful.
zelphirkalt [3 hidden]5 mins ago
Isn't the actually proper way to use prepared statements anyway? If we are doing that properly, then what does this t string business buy us for SQL usage from Python?
scott_w [3 hidden]5 mins ago
Because, as the article states, people aren’t using prepared statements. Instead, they pass f-strings because they’re more convenient.
vultour [3 hidden]5 mins ago
Except to maintain backwards compatibility we're probably going to get new methods that only accept templates, completely circumventing any effort to stop people passing in strings.

Prepared statements were the recommended way to run SQL queries when I was starting with PHP 15 years ago, anyone writing code vulnerable to SQL injection at this point should not be writing code.

scott_w [3 hidden]5 mins ago
Well yes but the alternative is to never make language improvements because legacy code exists.
hedgehog [3 hidden]5 mins ago
f strings are syntax rather than a type, the resulting templates look like a reasonable way to specify a prepared statement.
neonsunset [3 hidden]5 mins ago
> This would be the nicest way to use SQL I have seen yet.

EF/EF Core has existed for years :)

https://learn.microsoft.com/en-us/ef/core/querying/sql-queri...

nurettin [3 hidden]5 mins ago
I've used it for years. In order to generate the models you had to use the visual designer which was slow and buggy.

Generally annoying experience if you have to clock in and out every day to watch that UI break your database relations whenever you click save.

neonsunset [3 hidden]5 mins ago
No one uses it today, or in the last 5 years or so I presume. You use https://learn.microsoft.com/en-us/ef/core/modeling/#use-data...

This was a completely separate, legacy extension of VS, not EF let alone EF Core.

nurettin [3 hidden]5 mins ago
Completely separate is pushing it since it was recommended by Microsoft, but yes, I am old and times have changed.
jbaiter [3 hidden]5 mins ago
Thanks, I hate it. While it's nice syntactic sugar, the difference between an SQL injection vulnerability and a properly parametrized query is now a single letter that's easily missed
JimDabell [3 hidden]5 mins ago
The t-string produces a Template object without a __str__() method. You can’t mistakenly use an f-string in its place. Either the code expects a string, in which case passing it a Template would blow it up, or the code expects a Template, in which case passing it a string would blow it up.
crazygringo [3 hidden]5 mins ago
> or the code expects a Template, in which case passing it a string would blow it up.

That's where the problem is though -- in most cases it probably won't blow up.

Plenty of SQL queries don't have any parameters at all. You're just getting the number of rows in a table or something. A raw string is perfectly fine.

Will sqlite3 really disallow strings? Will it force you to use templates, even when the template doesn't contain any parameters?

You can argue it should, but that's not being very friendly with inputs, and will break backwards compatibility. Maybe if there's a flag you can set in the module to enable that strict behavior though, with the idea that in a decade it will become the default?

WorldMaker [3 hidden]5 mins ago

    db.execute(t"Select Count(1) from someTable")
It's one extra letter to "force" for an unparameterized query over a "raw string". The t-string itself works just fine without parameters.

There's definitely a backwards compatibility hurdle of switching to a template-only API, but a template-only API doesn't look that much "less friendly" with inputs, when the only difference is a `t` before every string, regardless of number of parameters.

crazygringo [3 hidden]5 mins ago
Sure, but it's just I don't have to do that anywhere else.

I never put an f in front of a string if I'm not putting variables within it.

And I'm generally used to Python inputs being liberal. I can usually pass a list if it expects a tuple; I can pass an int if it expects a float; often I can pass an item directly instead of a tuple with a single item. Regex functions take regular strings or regex strings, they don't force regex strings.

Being forced to use a single specific type of string in all cases is just very different from how Python has traditionally operated.

It's safer, I get that. But it's definitely less friendly, so I'll be curious to see how module maintainers decide to handle this.

WorldMaker [3 hidden]5 mins ago
> Being forced to use a single specific type of string in all cases is just very different from how Python has traditionally operated.

Maybe that's partly the disconnect here? "t-string" is probably a confusing colloquial name because they aren't strings, they are Templates. The runtime type is a Template. It is a very different duck-type from a string. As a duck-typable object it doesn't even implicitly or explicitly act like a string, there's intentionally no `__str__()` method and `str(someTemplate)` doesn't work like you'd expect. It shouldn't be a surprise that there is also no implicit conversion from a string and you have to use its own literal syntax: it isn't a string type, it's a Template type.

Python here is still liberal with respect to Templates (it is still a duck type). If a function expects a Template and you don't want to use the t"" shorthand syntax nor use the Template constructor in string.templatelib, you just need a simple class of object that has an `__iter__()` of the correct shape and/or has `strings` and `values` tuples.

Sure, it may make sense for some types of APIs to support a Union of str and Template as "liberal" options, but it's a different class of liberal support from Union of list and tuple or Union of int and float which are closer "domains" of types. A Template isn't a string and at runtime looks nothing like one (despite how syntactically it looks like one at "compile time"). Given `__iter__()` in Template, it may make more sense/would be more "natural" to Union Template with List or Tuple more than with a single string.

scott_w [3 hidden]5 mins ago
Er… that’s just not correct? Python can be more liberal but it’s not always. It depends entirely on the tooling. Libraries will take time to catch up but I can definitely see people creating libraries that enforce t-strings, even if they’re deconstructing them under the hood for legacy libraries.
crazygringo [3 hidden]5 mins ago
What's not correct? Python inputs usually are liberal. I didn't say always.

Are you claiming it's traditionally common in Python to be strict with inputs, and that being liberal is the exception?

scott_w [3 hidden]5 mins ago
That Python lets you blindly interchange different types for no good reason. It simply doesn’t.

Yes, it’s common for Python to be strict for inputs when the types are different. For example, try:

Decimal(‘3.0’) / 1.5

You’ll get an error and for good reason.

crazygringo [3 hidden]5 mins ago
But... it usually does. For example, try:

    Decimal('3.0') / 2
It works fine. It doesn't work with a float, which is for good reason. That's the whole point -- its general philosophy is to be pretty liberal with types, except when there's a good reason not to be. Heck, you can even do dumb things like:

    4 + True
And get 5 back. If that's not "blindly interchanging different types for no good reason" then I don't know what is. You can even multiply your Decimal object by False and get an answer...

Or it's like my original example -- the Regex module isn't restricted to r-strings. It happily works with regular strings. Python's general philosophy is to handle input liberally. Even type hinting is an add-on. Now, it doesn't go as far as JavaScript in allowing e.g. "4"+1, but it's still awfully liberal. I just don't see how you can claim otherwise.

scott_w [3 hidden]5 mins ago
I know about Decimal/int mixing but that’s for a good reason: it’s fine to intermix here. But not for floats (precision issues). The bool/int mixing isn’t “good.” It’s a bad implementation detail that Python is stuck maintaining forever. I’m actually stunned that you think to use this as an example when I think I’d fire any programmer that did that in my team for gross negligence.

The reason it works is because Python functionally has no bool type. True and False are just integers with names. It’s stupid and shouldn’t work like that but it does for historic reasons.

Your example of regex makes no sense either. There is no difference between strings and r-strings. They’re literally the same thing to the interpreter, so how could the regex functions enforce you use r-strings? Maybe they should be different but, for historic reasons, they can’t be without Python 4.0.

crazygringo [3 hidden]5 mins ago
> I’m actually stunned that you think to use this as an example when I think I’d fire any programmer that did that in my team for gross negligence.

You seem to be having a different conversation than I am.

I'm just describing Python as it is. I'm not defending it. I know why you can add True to a number, or else I wouldn't have come up with the example. And I know perfectly well that r-strings are just strings. Python easily could have made them a distinct object, to force people from ever making backslash errors, and restricted Regex functions to them, but didn't.

My only point has been, "Pythonic" things tend to be pretty liberal in what they accept. Type hints aren't even enforced, when they exist at all. You seem to think it shouldn't be that way. Great! But regardless, claiming it's not that way -- that Python is somehow this strict language -- is just mischaracterizing it.

scott_w [3 hidden]5 mins ago
> My only point has been, "Pythonic" things tend to be pretty liberal in what they accept

Being able to use a string as a string and an int as an int are not “pretty liberal in what they accept,” it’s just programming language theory 101! I think you’re mistaking duck typing for “liberal acceptance,” which are not the same thing. There’s always been an expectation that you should use compatible interfaces, even within the standard library. I’ve been bitten enough times by passing a generator in when a function expects a list, for example.

crazygringo [3 hidden]5 mins ago
I'm not mistaking it at all. Yes, duck typing is very much liberal acceptance, but Python code tends to go much farther. I could give a million examples -- like how in Python's isinstance() the second argument can be a type or a tuple of types, or in sqlite3 that you can run queries on a connection or on a cursor, and don't even get me started on Matplotlib or Numpy. It's just idiomatic in Python to make things easy for the programmer by accepting multiple types when possible. If you don't recognize this as the common Python pattern, I literally don't know what else to tell you.
0cf8612b2e1e [3 hidden]5 mins ago

  I never put an f in front of a string if I'm not putting variables within it.
Linters will even complain if you have a f string without variables. I assume it will be the same for t strings.
davepeck [3 hidden]5 mins ago
For the reasons discussed above, I'm not sure that it will be the case for t-strings. I think it'll take a little while for frameworks/libraries to adapt (while still maintaining backward compatibility) and a while for best practices to find their way into our linting and other tools.
0cf8612b2e1e [3 hidden]5 mins ago
If you can use a string anywhere you can use a t-string, then a non parametrized t-string is a code smell (lining error). If there is a dedicated template-string API, then there is the implicit threat you are breaking backwards compatibility to stop using regular strings.
davepeck [3 hidden]5 mins ago
> If you can use a string anywhere you can use a t-string

You can't; they're different types. t-strings are not `str`

It's up to good framework/API design to take advantage of this.

0cf8612b2e1e [3 hidden]5 mins ago
A library writer ultimately has to decide if they accept both types. For a database cursor, do you take regular strings + parameter arguments and template strings? Or dedicate a new API to the idea?

  cursor.execute(“select * from x where foo=?”, {foo=1})
  # while also allowing
  cursor.execute(t“select * from x where foo={foo}”)
  #Vs 
  cursor.executetemplate(“select * from x where foo={foo}”)
If ‘execute’ takes string and t-string, then I would consider it a problem to use a t-string without parameters. If there is a novel API just for t-strings, then you are implying widespread breaking changes as you have a schism between the two ways of providing parameters.
davepeck [3 hidden]5 mins ago
My point was that library authors will need to consider this carefully. If you're writing a library where injection attacks matter, then -- long term -- you almost certainly do not want a single method that accepts `Union[str, Template]`. You probably either want to avoid accepting `str` entirely, or perhaps provide two separate methods. Some period of deprecation seems inevitable.
WorldMaker [3 hidden]5 mins ago
Yeah, "t-string" is possibly a misnomer, because they are in fact at runtime a Template object (from string.templatelib).
maleldil [3 hidden]5 mins ago
Linters complain because f"hello" and "hello" are the _exact_ same string. t"hello" isn't even a string.
politelemon [3 hidden]5 mins ago
And I'm guessing lots of code will expect strings to maintain backward compatibility.
sanderjd [3 hidden]5 mins ago
I think it's way more likely that existing libraries will introduce new methods that use t-strings and are type safe, rather than entirely defeat the purpose of having a t-string API.
Mawr [3 hidden]5 mins ago
I'm guessing no existing functions will be extended to allow t-strings for this very reason. Instead, new functions that only accept t-strings will be created.
xorcist [3 hidden]5 mins ago
There's an obvious risk here, same as with strcpy (no, strncpy.. no, strlcpy... no, strcpy_s) that documentation tends to outlive code, and people keep pasting from tutorails and older code so much that the newer alternatives have a hard time cutting through the noise.

I would argue that as bad as some w3schools tutorials were, and copying from bad Stackoverflow answers, going back to MSA and the free cgi archives of the 90s, the tendency of code snippets to live on forever will only be excarbated by AI-style coding agents.

On the other hand, deprecating existing methods is what languages do to die. And for good reason. I don't think there's an easy answer here. But language is also culture, and shared beliefs about code quality can be a middle route between respecting legacy and building new. If static checking is as easy as a directive such as "use strict" and the idea that checking is good spreads, then consesus can slowly evolve while working code keeps working.

sanderjd [3 hidden]5 mins ago
It's pretty common for Python libraries to deprecate and remove functionality. It makes people mad, but it's a good thing, for this reason.
tubthumper8 [3 hidden]5 mins ago
Do the python type checkers / linters / whatever have the ability to warn or error on calling certain functions? That would be nice to eventually enforce migration over to the newer functions that only take a t-string template
mb5 [3 hidden]5 mins ago
mos_basik [3 hidden]5 mins ago
Yeah. A while back I was poking through some unfamiliar code and noticed that my editor was rendering a use of `datetime.utcnow()` as struck through. When I hovered it with my mouse, I got a message that that function had been deprecated.

Turns out my editor (vscode) and typechecker (pyright) saw that `datetime.utcnow()` was marked as deprecated (I know one can use the `@deprecated` decorator from Python 3.13 or `__future__` to do this; I think it was done another way in this particular case) and therefore rendered it as struck through.

And it taught me A) that `utcnow()` is deprecated and B) how to mark bits of our internal codebase as deprecated and nudge our developers to use the new, better versions if possible.

b3orn [3 hidden]5 mins ago
If it's not a completely new library written exclusively around templates, such code currently accepts strings and will most likely continue to accept strings for backwards compatibility.
yk [3 hidden]5 mins ago
That's entirely implementation dependent. For existing libraries I would expect something like

    def get(self, query):
        if isinstance(query, template):
            self.get_template(query)
        else:
            self.get_old(query) #Don't break old code!
ewidar [3 hidden]5 mins ago
it would likely be safer to have a safe (accepting Templates) and an unsafe (accepting strings) interface.

Now whether maintainers introduce `getSafe` and keep the old behavior intact, or make a breaking change to turn `get` into `getUnsafe`, we will see

darthwalsh [3 hidden]5 mins ago
And they could add deprecation warnings gradually
solatic [3 hidden]5 mins ago
That would be a great argument if Python wasn't a language that let you reach into the internals and define __str__() for things you shouldn't be defining it for. And that is something people will definitely do because, you know, they just need something to friggin work so they can get whatever ticket closed and keep some metric happy tracking time-to-close
scott_w [3 hidden]5 mins ago
Programmers being lazy and shit at their jobs is not a reason to not improve the language.
baggiponte [3 hidden]5 mins ago
Type checkers to the rescue ahaha I think db.get could also raise if the type does not match?
TekMol [3 hidden]5 mins ago
I guess that is a misunderstanding on your side, about how templates work. Less hate and more love might help to avoid this type of hotheaded misconception ;-)

Why do you think changing a letter would cause a vulnerability? Which letter do you mean?

codesnik [3 hidden]5 mins ago
f'' vs t'' probably.
melodyogonna [3 hidden]5 mins ago
Those are two different types
tannhaeuser [3 hidden]5 mins ago
Wow that's only slightly better than using the lowercase letter L vs the digit 1 or letter O vs zero to convey a significant difference.
hyperbovine [3 hidden]5 mins ago
OP is referring to swapping t with f.
TekMol [3 hidden]5 mins ago
That would result in a string passed to get() and raise an error as get() operates on a template, not on a string.
baegi [3 hidden]5 mins ago
except if get() can also accept a raw string, which is likely
mcintyre1994 [3 hidden]5 mins ago
No sane library is going to do that. If they do let you pass a raw string it should be a different function with the risks clearly documented.

The thing this replaces is every library having their own bespoke API to create a prepared statement on their default/safe path. Now they can just take a template.

crazygringo [3 hidden]5 mins ago
How about every library that wants to preserve backwards compatibility?

Or are you suggesting that e.g. every database module needs to implement a new set of query functions with new names that supports templates? Which is probably the correct thing to do, but boy is it going to be ugly...

So now you'll have to remember never to use 'execute()' but always 'execute_t()' or something.

WorldMaker [3 hidden]5 mins ago
You don't have to remember it, you can use deprecation warnings and lint tools to remind you. (Until eventually the safe API is the only API and then you really have nothing to remember.)
mcintyre1994 [3 hidden]5 mins ago
I’d assume their current safe function isn’t taking a string, and is taking some sort of prepared statement? So they could have it take either their prepared statement or a template, and deprecate their prepared statement.

If a library has functions taking a string and executing it as SQL they probably shouldn’t make that take a template instead, but I’d hope that’s a separate explicitly unsafe function already.

crazygringo [3 hidden]5 mins ago
For sqlite3, it absolutely takes a regular string.

If you want to substitute parameters, you put a '?' in the string for each one, and provide an additional (optional) tuple parameter with the variables.

So no, there's no explicitly unsafe function. That's my point.

mcintyre1994 [3 hidden]5 mins ago
Gotcha. I’d guess they’d want to deprecate that function and create a new one that only accepts a template then, which is definitely annoying! I figured they’d already have more separation between prepared and raw strings which would make it easier.
orphea [3 hidden]5 mins ago
Why would it?
yxhuvud [3 hidden]5 mins ago
Also I wonder how easy it will be to shoot oneself in the foot. It may be easy to accidentally make it to a string too soon and not get the proper escapeing.
scott_w [3 hidden]5 mins ago
That’s a library author problem, so it’s less likely since library authors tend to be fewer in number and, for popular libraries, get a reasonable number of eyes on this type of change.
nu11ptr [3 hidden]5 mins ago
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.

oliwarner [3 hidden]5 mins ago
Python has always been a batteries-included language, so having a go at templated string interpolation —a feature other languages have had for decades— seems like a strange gripe.

It's far more essential than little utilities like textwrap or goliath packages like Python's bundled tkinter implementation.

phreeza [3 hidden]5 mins ago
Batteries included to me also meant a large standard library, not a large language.
nu11ptr [3 hidden]5 mins ago
What other languages have this feature? I'm not aware of any
pansa2 [3 hidden]5 mins ago
> [T-strings] are the pythonic parallel to JavaScript’s tagged templates.
thatnerdyguy [3 hidden]5 mins ago
C# has InterpolatedStringHandler which isn't quite the same thing, but (in my understanding), trying to address the same core issues.
WorldMaker [3 hidden]5 mins ago
C# InterpolatedString is very close, with the twisty bit being that C# can rely on static typing for safety so the "f-string" and "t-string" variants use the same literal syntax and depend on what function they are passed to, whereas in both Python and Javascript they have different literal syntaxes. Python chose to use a different literal prefix to its literals ("f" versus "t") and Javascript chose to use a function-call syntax as prefix (`templateString` versus html`templateString` where html is a function in scope).
neonsunset [3 hidden]5 mins ago
For the case like here it’s closer to FormattableString that’s what EF Core’s composable FromSql method works on top of. Both address custom interpolation but from different angles / for different scenarios.
jerf [3 hidden]5 mins ago
Many languages have similar features.

For instance, Python has the % operator that is a template format that allows interpolating values based on a template string with a variety of printf-like features: https://python-reference.readthedocs.io/en/latest/docs/str/f...

Also, Python has the .format method on strings, which is a template format that allows interpolating values based on a template string: https://www.geeksforgeeks.org/python-string-format-method/

As another example, Python has f-strings that are a template format that allows interpolating values based on a template string: https://www.geeksforgeeks.org/formatted-string-literals-f-st...

Also, you can also find languages like Python that have a rich ecosystem of third party templating solutions. These are often intended for things like rendering entire web pages but many of them have relatively simple ways of using their templating functionality in a fairly reasonable amount of code, if you just want to have a template format that allows interpolating values based on a template string.

So, as you can see, many other languages have this feature, as you can tell from all the examples I have shown you here.

(To spell it out for those who may find this too subtle... somehow... I'm not a fan of this simply because Python has gone from "There should be one-- and preferably only one --obvious way to do it." to "there's half-a-dozen ways to do it and if they are all wrong Python 3.x+1 will introduce a seventh" and I'm just not seeing the benefits worth the tradeoffs here.)

viccis [3 hidden]5 mins ago
What would you say is the alternative?
hocuspocus [3 hidden]5 mins ago
Scala since 2014.

Java 22 had the feature as preview but it was removed in 23, it'll come back after some refinement.

martinky24 [3 hidden]5 mins ago
They literally discuss this in the article.
murkt [3 hidden]5 mins ago
This is a pretty simple and useful feature. I wouldn’t say that it bloats the language too much. Descriptors and metaclasses are much more complicated and have a lot more implications and have been in the language for a veeeeery long time. Is it decades already?
nu11ptr [3 hidden]5 mins ago
This feature is not complicated, but one must keep every feature that can possibly be seen in code in their head. Even if it is familiar now, what happens when you use the feature in the one small section of code where it fits, nowhere else, and then read that code 2 years later? This is the problem with adding useful features that are only used in a few key places. I'm not saying Go is a perfect language, far from it, but limiting # of features as a general goal is something more languages should strive for IMO.
murkt [3 hidden]5 mins ago
I am not arguing against that language ideally should have less features. I am arguing with “Python is getting huge”, because it’s huge and has been for many-many years :)
nu11ptr [3 hidden]5 mins ago
True - cat is well out of the bag at this point
pansa2 [3 hidden]5 mins ago
Yeah, Python hasn’t been a simple language for a long time, if ever. That’s probably the biggest misconception about the language - that its friendly syntax implies simple semantics. It’s not true at all.
tetha [3 hidden]5 mins ago
I would say python in it's entirety is one of, if not the deepest and potentially most complex language I know. C++ is the other contender. The things you could do with metaclasses, multiple inheritance and operator overloading are quite staggering.

I'm just glad you don't have to think or even use this as a normal user of the language, most of the time or at all.

sanderjd [3 hidden]5 mins ago
I'm truly glad that Go exists for people who like languages that are simple even to the point of frustration and I hope it never changes. But I'm glad that other languages exist for those of us for whom learning some syntax is not a barrier and having convenient ways to do common things is highly valued.
acdha [3 hidden]5 mins ago
There’s an interesting trade off around maintenance. Python’s stdlib means there’s more consistency across projects and you can assume basic things like error handling which you had to check individually on each Go program, which is the kind of stuff which adds up when you have a lot of small programs.

This is especially noticeable with AWS Lambda where you can have a lot of useful stuff running for years without doing more than bumping the runtime version every year or two, but also that is one highly opinionated architecture so it’s not globally optimal for everyone.

gnabgib [3 hidden]5 mins ago
Big discussion (414 points, 10 days ago, 324 comments) https://news.ycombinator.com/item?id=43647716
damnitbuilds [3 hidden]5 mins ago
Maybe it's good to come back to a discussion after a few days, more informed and with a cooler head ?

;-)

nikisweeting [3 hidden]5 mins ago
This is pretty cool, if we're porting JS features do we get dictionary unpacking/destructuring next?

I just want this so badly, it's the main reason I drift back to JS:

    >>> {a, b=45, c=None, **d} = {'a': 234, xzy: 32456}
    >>> print(a, b, c, d)
    234 45 None {'xyz': 32456}
zahlman [3 hidden]5 mins ago
Another alternative:

  >>> a, b, c, d = (lambda a, b=45, c=None, **d: (a, b, c, d))(**{'a': 234, 'xyz': 32456})
The parentheses for this are admittedly a bit tricky.
enragedcacti [3 hidden]5 mins ago
just use this very sane pattern /s:

    >>> match {'b': 45, 'c': None}|{'a': 234, 'xyz': 32456}:
    >>>     case {'a': a, 'b': b, 'c': c, **d}: pass
    >>> print(a, b, c, d)
    234 45 None {'xyz': 32456}
runekaagaard [3 hidden]5 mins ago
It feels a bit like "cheating" that new x-string features are built-in only. It would be cool to be able to do:

    from foo import bar
    bar"zoop"
zahlman [3 hidden]5 mins ago
In my own language design (nothing public yet - need to prioritize more practical things at the moment) this is definitely on the menu. Aside from a minimal set, keywords are implemented as compile-time macros; and it's intended that they can be extended, both "natively" and in the implementation language, by writing AST-manipulation code. But the handling of arithmetic expressions, as well as the broader line/block structure, is hard-coded. (I draw inspiration from both Python and the Lisp family.)
masklinn [3 hidden]5 mins ago
A t-string is a literal for a Template object which is a data holder, it doesn't actually do anything, so you would simply call

    bar(t"zoop")
cb321 [3 hidden]5 mins ago
This is exactly how Nim is. The f-string like equivalent uses a macro called "fmt" which has a short alias "&". So you can say:

    import std/strformat
    let world = "planet"
    echo &"hello {world}"
The regular expression module does a similar thing with a `re"regular expression"` syntax or std/pegs with peg"parsing expression grammar" and so on. There are probably numerous other examples.

In general, with user-defined operators and templates and macros, Nim has all kinds of Lisp-like facilities to extend the language, but with a more static focus which helps for both correctness and performance.

nhumrich [3 hidden]5 mins ago
This was the original proposed idea in the PEP (750), but it changed overtime. There is a section in the PEP to explain why it changed to t-strings if you are interested.
zahlman [3 hidden]5 mins ago
PEP 638 has always seemed to me like something of a generalization of the idea. But that really feels to me like a 4.0 feature, or rather something that needs to be designed for from the beginning. (Which is why I've done a bit of that in my own mind...)
Timwi [3 hidden]5 mins ago
True. Then you could use

  sql"..."
  html"..."
for each of the given examples and achieve some runtime type safety.
jbverschoor [3 hidden]5 mins ago
And you'd end up with almost no improvement.

If you pass a "t-string" to a framework, it can force escaping.

What you suggest is to rely on escaping by the user (dev), who, if he was aware, would already escape.

Unless you'd suggest that it would still return a template, but tagged with a language.

mcintyre1994 [3 hidden]5 mins ago
FWIW the JS equivalent is a template but tagged with a language. It has all the benefits of this template, but IDEs can easily syntax highlight the string. That seems like it would be a bit trickier to do with the Python one which is a shame.
thund [3 hidden]5 mins ago
Use a function?

    bar(“zoop”)
It’s just syntax, like we used to have

    print “foo” 
that later became

    print(“foo”)
dec0dedab0de [3 hidden]5 mins ago
meh, the difference between bar"zoop" and bar("zoop") isn't really big enough to be worth it.

I like F strings a lot, but for the most part I think all of the various X-strings should just be classes that take a string as an argument.

metrognome [3 hidden]5 mins ago
Zen of Python in 2025:

  There should be one-- and preferably only one --obvious way to do it.
Python String Formatting in 2025:

- t-strings

- f-strings

- %-operator

- +-operator

- str.format()

davepeck [3 hidden]5 mins ago
And $-strings (PEP 292) as well. :-)

I see each of these as distinct but overlapping; I'm (slowly) writing a guide to string formatting with all of these in mind, trying to emphasize when I'd choose one over the other. (fwiw I personally avoid % and + these days; $ is pretty uncommon in practice; f-, t-, and .format() all seem to have good unique uses.)

a_t48 [3 hidden]5 mins ago
The last three generally shouldn't be used (`+` is sometimes fine, but not really for formatting), but I doubt we would ever get a py4 that removes them, given the stomachaches that py3 caused. It does feel odd that a t-string is just an f-string without args applied.
alfalfasprout [3 hidden]5 mins ago
It's my understanding that they're still recommended for logging since with f-strings you always pay the formatting cost (but with str.format it's deferred).
sanderjd [3 hidden]5 mins ago
t-strings aren't really like these others.

It's definitely true that those four string formatting techniques violate the "one obvious way" advice.

hughw [3 hidden]5 mins ago
I could scarcely believe this new t-string wasn't a joke. As an occasional, grudging Python programmer, I rue the accretion of string formatters over the years. My code bears the history of successive, imperfectly understood (by me)formatters.

"Situation: There are 14 competing standards...." https://xkcd.com/927/

davepeck [3 hidden]5 mins ago
Hi! I wrote this. :-)

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.

maxloh [3 hidden]5 mins ago
Hi. I come from a JavaScript background.

I am wondering what is the reason behind not using a similar syntax to JavaScript? Seems simpler to me.

  # Compare this:
  template = t"<p>{evil}</p>"
  safe = html(template)

  # To this:
  safe = html"<p>{evil}</p>"
davepeck [3 hidden]5 mins ago
The PEP originally started with a similar-to-javascript syntax but over time we decided it wasn't the right way to expose these ideas in Python. There's more detail about why this approach was rejected in the PEP: https://peps.python.org/pep-0750/#arbitrary-string-literal-p...
varunneal [3 hidden]5 mins ago
Would be interested in inclusion of PEP 292 [1] in your discussion here, which introduced `string.Template`. Is this Template going to be deprecated?

[1] https://peps.python.org/pep-0292/

davepeck [3 hidden]5 mins ago
PEP 292's `string.Template` will remain; there are no plans to deprecate it.

PEP 750's `string.templatelib.Template` is a separate and unrelated type. Amongst many differences, unlike PEP 292, `Template` has a literal form too.

I'm hopeful that the confusion will be minimal; in practice, PEP 292 (aka $-strings) is used only in specialized cases, like flufl.i18n, a really deep I18N framework.

18172828286177 [3 hidden]5 mins ago
This is super cool, thank you.
mounir9912 [3 hidden]5 mins ago
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:

   evil = "<script>alert('bad')</script>"
   template = t"{evil}"
   safe = html(template)
Why not just:

    evil = "<script>alert('bad')</script>"
    safe = f"{html(evil)}"
Or even before creating the f-string. Is it just about not forgetting the sanitization/string manipulation part and forcing you to go through that?
zahlman [3 hidden]5 mins ago
> Is it just about not forgetting the sanitization/string manipulation part and forcing you to go through that?

This is a very big deal! It's also about centralizing that work. Now that sanitization can occur in the consumer of the t-string (for example, the API to your HTML renderer), rather than in every f-string.

scott_w [3 hidden]5 mins ago
Pretty much, yeah. The article highlights that people were using f-strings directly, and they wanted to provide an alternative for lightweight template/interpolation.
mounir9912 [3 hidden]5 mins ago
I feel like I'm still missing something when they're saying this about the example(s):

"Neither of these examples is possible with f-strings. By providing a mechanism to intercept and transform interpolated values, template strings enable a wide range of string processing use cases."

As far as I can see, anything you do with the template, you could do before building the f-string or inline as in my intial example.

davepeck [3 hidden]5 mins ago
With f-strings, you cannot write code to determine which portions of the resulting `str` were static and which were dynamic; with t-strings, you can. *

(As to your initial example: it's worth considering what will happen as you compose multiple bits of HTML via nesting to generate a large final page. The developer experience may become... unideal.)

* (barring undesirable hacks with inspect, etc.)

scott_w [3 hidden]5 mins ago
You wouldn’t really do your example, though. If you’re using an f-string, you’d just directly interpolate, because it’s convenient. You wouldn’t use an extra library to properly make it safe, otherwise you’d just use a proper template library and language.

This gives you a convenient middle ground where you don’t need to learn a template library but still get safety. I can’t think of the code right now but I could see this being useful to pass in some dynamic HTML to, say, Django without having to remember to turn off escaping for that section. It can also be convenient for writing raw SQL without having to use prepared strings.

mos_basik [3 hidden]5 mins ago
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`? :)

`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.

pauleveritt [3 hidden]5 mins ago
Some prior art: https://pypi.org/project/tagged/

In fact, the repo of a companion project from the author has the ticket that spawned the work leading to t-strings: https://github.com/jviide/htm.py/issues/11

ayhanfuat [3 hidden]5 mins ago
I asked asottile for his opinion on PEP 750 (or rather, a previous version of it), and he wasn’t really in favor. So I think it’s unlikely that we’ll see a future-tstrings from him. :)
nezirus [3 hidden]5 mins ago
Maybe this could be useful to libraries like psycopg3 to use something more simple/natural instead of this:

https://www.psycopg.org/psycopg3/docs/api/sql.html

(while I also agree it gets crowded with yet another string prefix)

dtech [3 hidden]5 mins ago
That's the exact use case. Basically these are syntactic sugar for a very similar function signature.
sashank_1509 [3 hidden]5 mins ago
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++
jsnell [3 hidden]5 mins ago
It being a language feature gives you controlled access to the lexical scope, such that the template string can refer to variables by name rather than having to pass each value as a parameter. Doing it via parameters is repetitive and/or error-prone.
linsomniac [3 hidden]5 mins ago
You CAN get access to the calling scope of a function. Something like:

    current_frame = inspect.currentframe()
    env = current_frame.f_back.f_locals.copy()
I did it in uplaybook so that you can do things like:

    for module in ['foo', 'bar', 'baz']:
        ln(path="/etc/apache2/mods-enabled", src="/etc/apache2/mods-available/{{ module }}.load")
This is an ansible-like tooling with a python instead of YAML syntax, hence the Jinja2 templating.
nhumrich [3 hidden]5 mins ago
This feature actually can't be a library. It needs to be able to preserve the string before the variables are passed in, which a library would be unable to do because f-strings immediately replace all the values. The whole premise of t-strings is to know which values where "hard coded" and which ones are variables. A library can't possibly know that. And a function call wouldn't have access to the local variables. The only way to do this without language support is to pass `locals()` into every function call.
dec0dedab0de [3 hidden]5 mins ago
Now you have me wondering how difficult it would be to have a class that takes a string and parses through globals to find the context it was called from. maybe causing an exception and abusing a traceback? or maybe we just find our own objectid.... gahh I have to try it now, but I'm setting a timer.
dec0dedab0de [3 hidden]5 mins ago
It was easier than I thought, but also took longer than I wanted to. turns out the inspect module provides what you need to pull it off.

This dummy example splits a string that it was given, then if one of those values is in the callers context it saves those in self.context, and has an output function to assemble it all together. Obviously this example is not very useful, but it shows how a library could do this as a class or function without the user having to pass in locals().

  import inspect
  class MyXString:
      """ will split on whitespace and replace any string that is a variable name 
    with the result of str(variable)"""
        def __init__(self, string):
            self.string = string
            caller_locals = inspect.currentframe().f_back.f_locals
            self.context = {}
            for key in  set(string.split()):
                if key in caller_locals:
                    self.context[key] = caller_locals[key]
  
        def output(self):
            output = self.string
            for k,v in self.context.items():
                output = output.replace(k,str(v))
            return output
nhumrich [3 hidden]5 mins ago
This looks like a performance nightmare, and would likely never have IDE integration. So I guess I was wrong. It could be a library. But will be much better supported officially.
zephyrfalcon [3 hidden]5 mins ago
You don't have to go through all the globals, you just have to get the caller's namespace, which is fairly simple. See e.g. https://stackoverflow.com/a/6618825/27426

For this reason, I think it's not true that this absolutely had to be a language feature rather than a library. A template class written in pure Python could have done the same lookup in its __init__.

dec0dedab0de [3 hidden]5 mins ago
That is exactly what I ended up doing:

https://news.ycombinator.com/item?id=43752889

Mawr [3 hidden]5 mins ago
It's not as simple as "more features" == "closer to C++". Features are not equal to each other in terms of impact on language complexity.

t-strings don't interact with anything else in the language; they, as you yourself pointed out, could almost be an isolated library. That makes them low impact.

This is also true syntactically; they're just another type of string, denoted by "t" instead of "f". That's easy to fit into one's existing model of the language.

Moreover, even semantically, from the point of view of most language users, they are equivalent to f-strings in every way, so there's nothing to learn, really. It's only the library writers who need to learn about them.

Then we have to consider the upsides - the potential to eliminate SQL and HTML injection attacks. The value/cost is so high the feature a no-brainer.

perlgeek [3 hidden]5 mins ago
If it's not a language feature, there's always a risk of fragmentation. Some people won't use it because it adds another dependency, that means fewer programmers will be familiar with it. Others will come up with their own, slightly incompatible implementation. See for example Perl and its various Object Orientation frameworks (Moose, Mouse, Moo, Mo, M, Mojolicious comes with its own...)

Other languages have a policy of prototyping such things out of core, and only adding it to the core language if it gains traction. Of course that works better if the language has a mechanism for extending the syntax out of core.

dec0dedab0de [3 hidden]5 mins ago
putting it in the standard library would handle those issues.
sanderjd [3 hidden]5 mins ago
Counterpoint: It's good to add well designed and useful features to programming languages.
dsego [3 hidden]5 mins ago
Because JS has it in the form of tagged template literals.
chrisrodrigue [3 hidden]5 mins ago
We have a handful of ways to create strings now.

Meanwhile, pytest is still not part of the standard library.

kamikaz1k [3 hidden]5 mins ago
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..

[1] https://peps.python.org/pep-0750/#the-interpolation-type

davepeck [3 hidden]5 mins ago
We pushed these questions out of the PEP to keep its scope constrained. (See https://peps.python.org/pep-0750/#mechanism-to-describe-the-...)

But yes, the PEP leaves open an important question: how will tools decide to work with common types of content in t-strings, like HTML or SQL?

There are simple approaches that can be taken in the short term (content sniffing) and more robust approaches (type annotations, perhaps) that will take time and the broader tooling community to develop.

pauleveritt [3 hidden]5 mins ago
It was discussed in the first revision and discussion of the PEP. The decision was made to move that to follow-on work, as we discovered more about what tooling needs.

As an example, I was excited about using `Annotated` on the function to indicate the language it expected. Turns out, a lot of linters know nothing about the type system.

feraidoon [3 hidden]5 mins ago
I had something like this implemented for safely executing shell commands, by using metaprogramming:

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

pansa2 [3 hidden]5 mins ago
> 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.
zahlman [3 hidden]5 mins ago
>But if there aren't any, it's strange that the design requires every Template processing function to include this boilerplate

Other replies gave examples of other use cases. But the neat thing about Python is that you don't need to "include this boilerplate" for the common cases. It can be wrapped up in a decorator (which could be included in `templatelib`). Or, as you say, in a method on the Template class.

I think I'd implement it as a generator, calling `process_value` (defaulting to `str`) on the Interpolations, so that the caller can still do more with the results (or just `''.join` them).

But these are separate considerations; nothing prevents implementing them later, or indeed adding them to the implementation before the 3.14 release.

WorldMaker [3 hidden]5 mins ago
Templates don't even have to be processed into a string. The article shows an example where the Template is processed into an HTML mini-DOM. It's maybe not obvious because the DOM object is immediately stringified to show sample output, but you could imagine manipulating the DOM object in a few more steps before stringifying it, or maybe you are running in WASM in a browser and using that mini-DOM directly as a Virtual DOM passed to JS to work with.

Also, in addition to the other SQL example using "?" to fill in the "holes" for parameters in an SQL friendly way, some DBs also support named parameters, so the "hole" in the string form might be naively replaced with something like `f"@{item.expression}"` and that also forms the key in a dict to pass as parameters. (You'd want to make sure that the expression inside the template is useful as a parameter name, and not something more exotic like {1 + 3} or {thing for thing in some_list}, in which cases you are probably auto-assigning some other parameter name.)

pauleveritt [3 hidden]5 mins ago
Nearly everything you just described is being worked on. It's amazing how accurately you have described it. We hope to demo and explain at PyCon US.
rfoo [3 hidden]5 mins ago
There are a lot of examples about SQL in comments. In the SQL case you want something like:

  def process_template(template: Template) -> tuple[str, tuple]:
    sql_parts = []
    args = []
    for item in template:
      if isinstance(item, str):
        sql_parts.append(item)
      else:
        sql_parts.append("?")
        args.append(process_value(item.value))
    return "".join(sql_parts), tuple(args)
(of course it would be more nuanced, but I hope you get the point)
pansa2 [3 hidden]5 mins ago
Yes that makes sense, thanks.

Also, my comment was about the amount of boilerplate required, but that can be vastly reduced by writing `process_template` in a more functional style instead of the highly-imperative (Golang-like?) style used in the article. The first `process_template` example is just:

    def process_template(template: Template) -> str:
        return ''.join(interleave_longest(template.strings, map(process_value, template.values)))
And the second is something like:

    def process_template(template: Template) -> tuple[str, tuple]:
        return (
            ''.join(interleave_longest(template.strings, ['?'] * len(template.values))),
            map(process_value, template.values)
        )
franga2000 [3 hidden]5 mins ago
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.

nhumrich [3 hidden]5 mins ago
The PEP was initially proposed this way. But due to various reasons, making it an open namespace was considered to overcomplicate the language (read: understanding coffee when reading it). Alternatively, there doesn't really seem to be much loss of ability with t-strings. Libraries can require a template as it's accepted type, instead of having to invent their own custom type and named template.
conartist6 [3 hidden]5 mins ago
For the record the JS thing desugars to the exact same as the Python thing, so it is no more or less safe to do the syntax highlighting in Python as it is in JS.
Timon3 [3 hidden]5 mins ago
It desugars similarly, but the Python version doesn't have a name. Any t-string is a t-string, there's no HTML t-string or SQL t-string or anything like that. It's just a t-string you can pass to a function:

    html_string = t"<something />"
    sql_string  = t"SELECT * FROM something"
In JS, the string has a prefix that can differ between languages, e.g.:

    const htmlString = html`<something />`
    const sqlString  = sql`SELECT * FROM something`
and so on. See the difference?
masklinn [3 hidden]5 mins ago
Except your labels are incorrect because neither `html_string` nor `sql_string` are strings, they're both Template objects, and the sink function is the one which processes it. No processing has happened to them by the end of the snippet, beyond creating the template object itself.
Timon3 [3 hidden]5 mins ago
Sure, choose different variable names, who cares. The essential difference is that the language is referenced at the declaration site, not the usage site, which makes the syntax highlighting far easier.

Please engage with my point instead of criticizing trivialities.

masklinn [3 hidden]5 mins ago
> Please engage with my point instead of criticizing trivialities.

Your complete misunderstanding of what's happening is not a triviality.

> The essential difference is that the language is referenced at the declaration site, not the usage site, which makes the syntax highlighting far easier.

Javascript has no built-in template tags beyond `String.raw`. If tooling has the capabilities to infer embedded language from arbitrary third party libraries, I would hope they have the ability to do utterly trivial flow analysis and realise that

    html(t"<something />")
means the template string is pretty likely to be HTML content.
conartist6 [3 hidden]5 mins ago
Yes. It's a weak heuristic, but it's EXACTLY the same weak heuristic that JS is applying!

IN other words, since custom template tags in JS *are literally just function calls* when a JS environment syntax highlights the code as HTML it's doing so based on an extremely weak heuristic (the identifier for the interpolation function is named "html"). Both Python and JS have the same problem.

svieira [3 hidden]5 mins ago
I think his point would be clearer if we focused on typing the usages statically. Consider `html(this_is_a_sql_template)` vs. `html"SELECT * FROM ..."` or `thing_that_consumes_html_template_type(oops_a_sql_template_type)`.
Timon3 [3 hidden]5 mins ago
Come on, you're just being rude for no good reason. A badly chosen variable name doesn't show a "complete misunderstanding". Yes, the variable should have been named `html_template` instead of `html_string` - how often do I have to acknowledge this before you accept it?

And it's obviously more complex to do syntax highlighting when the declaration site and usage site are possibly split apart by variable assignments etc. Yes, in the case you showed syntax highlighting is easy, but what if the `html` function takes more parameters, doesn't take the template as the first parameter, etc? There's a lot of possible complexity that tagged template literals don't have. Thus they are easier to do highlighting for. This is objectively true.

conartist6 [3 hidden]5 mins ago
Tagged template literals in JS have all that complexity. All of it. That tools you trust lie and pretend that's not the case doesn't make the language spec say anything different
Timon3 [3 hidden]5 mins ago
No, they literally don't, because they don't support these features!

You can't split apart the declaration of the template literal and the "tagging". The tag is always part of the declaration, which it doesn't have to be in Python, as I've showed.

You can't pass additional parameters to the tag function, it's always just the template & values. In Python, you can pass as many parameters as you want to the usage site, e.g.

    some_value = html(True, t"<something />", 42)
conartist6 [3 hidden]5 mins ago
It just sounds like you don't know JS very well, because in JS you can definitely split apart the declaration and the tag with ease. The thing implementing the tag is just a function after all: https://jsfiddle.net/rd2f1kot/
Timon3 [3 hidden]5 mins ago
Sorry, but do just not want to understand what I'm talking about? Your example doesn't show what you're saying it does.

In Python you can do this:

    bar = "hello world"
    template = t"<something foo={bar} />"
    string = html(template)
This is simply not possible in JS, because the template literal always must have the tag attached. You can't split them apart. If you try:

    const bar = "hello world"
    const template = `<something foo=${bar} />`
you already have a string in the `template` variable. There's no access to the individual values anymore. It's already done. It's a string. No template. You can't pull apart the literal declaration and the tagging.

Are we now done with this ridiculous game of deliberate misunderstandings?

conartist6 [3 hidden]5 mins ago
Sorry for some reason I can't reply to the deeper posts. Depth limit I guess. You asked which editors could support syntax highlighting once the tag and the content that needs to be syntax highlighted are split apart. Currently there are none, though I am writing one that will be able to.

Python should be able to detect the magic syntactic pattern just the same way JS does and use that for syntax higlighting. In JS the magic syntactic pattern for triggering HTML syntax highlighting is:

  html`<doc/>`
In Python the magic syntactic pattern would be:

  html(t"<doc />")
My point in showing that JS counterexample was to demonstrate that the real reason people don't do that kind of thing isn't that they can't, it's that they like having syntax highlighting. That means the approach should work just as well in Python. This is the case even though the heuristic involved is very weak. Changing the name of the identifier or breaking into multiple expressions would be enough to break the heuristic in either language, which is why I think it's a really weak heuristic and dangerous pitfall for developers who might mistake the coloring for information about how the runtime sees that data (which is normally what syntax highlighting is for)
Timon3 [3 hidden]5 mins ago
If I may give you some advice: you could have made this point without telling me I don't know JS well, without misrepresenting me and without wasting both of our time by just making this point from the start. Because you are technically correct that, in the limited case of a template immediately being passed to a function, syntax highlighting can use the same heuristics that are currently used in JS. But, as I assumed previously, in doing so you're simply ignoring my point: syntax highlighting for tagged template literals isn't applicable to only a subset of uses because they are by design less complex. Your counter example was using a different template tag and not using the actual tag as a template tag, which obviously doesn't work in the context of syntax highlighting for that template tag.

Had you not played games with this technicality, we could both have saved a lot of time. Hope you had fun I guess.

conartist6 [3 hidden]5 mins ago
I just don't see the same thing as you. This is what you say is simply not possible:

  let passthrough = (...args) => args;
  let bar = "hello world";
  let template = passthrough`<something foo=${bar} />`;
  string = html(...template);
Timon3 [3 hidden]5 mins ago
So now we have moved from "tagged template literals have all the complexity of Python templates" to "if you define a specific template tag and use that, you can replicate one feature of Python templates". No mention of the other example I mentioned (which, by the way, wasn't an exhaustive list).

Now, I'll just ignore that you're still deliberately misrepresenting me in the hopes of gaining some actual knowledge from this discussion - please show me: which editor supports syntax highlighting for the specific example you just mentioned? After all, that was the topic of discussion - not whether it's possible to delay "tagging", but whether it's easier to do syntax highlighting for JS template tags. Please show me an existing editor, IDE or git repo for a tool that would highlight your specific example based on the `html(...template)` call in line 4 (so no heuristics based on line 3). Surely you're not just throwing random examples at the wall to see what sticks, right? You've actually been following the discussion and are arguing in the context of it?

conartist6 [3 hidden]5 mins ago
Oh now I can reply. Thread continued above: https://news.ycombinator.com/item?id=43754585
conartist6 [3 hidden]5 mins ago
JS references the language at the usage site, exactly like Python. There is no difference here in how the two languages behave.
Timon3 [3 hidden]5 mins ago
No, it doesn't, it references the language at the declaration site, because the declaration site always is the usage site. You can't split them apart. You can split them apart in Python - see the example in my first comment.
masklinn [3 hidden]5 mins ago
The JS version actually desugars to something much more primitive, and less convenient: in JS a template tag receives one parameter which is an array of n+1 strings, and then n parameters for the interpolated values, and has to iterate alternatively on both sequences.

You can do that in python by accessing the `strings` and `values`, but I expect most cases will simply iterate the template, yielding a unified typed view of literal strings and interpolated values.

fph [3 hidden]5 mins ago
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?
nhumrich [3 hidden]5 mins ago
I suggest you read the PEP, there is a section on i18n. Short version is it's not designed for this use case.

As for variables and arbitrary code/lambdas, yes: t-strings can do that, just like f-strings

vFunct [3 hidden]5 mins ago
Would like to see Django fully use these to replace a lot of it's own complicated template syntax.
haberman [3 hidden]5 mins ago
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...

    f'...' -> str

    t'...' -> Template
    foo(t: Template) -> str
dheera [3 hidden]5 mins ago
> This lets subsequent logic decide whether the interpolation results need any special escaping before concatenating them with the strings around them

This sounds like unnecessary fluff in what was supposed to be a simple language. I'm worried Python is turning into C++42 with 65535 ways to do one simple thing.

Why not just:

    f'SELECT * FROM `{esc(table)}` WHERE name = "{esc(name)}"'
Nice and simple.
WorldMaker [3 hidden]5 mins ago
It's easy to forget the `esc` function. How does the recipient check (or type check) that it was called in all the right places?

Most DBs support parameterized queries which can be cached for performance. How do you pick out the parameters from that and replace those parts of the strings with the DB's parameter placeholders?

    t'Select * from {table} where name = {name}'
Looks very similar, but execution engine has access to all the individual parts, making it very easy to add placeholders such as:

    ('Select * from ? where name = ?`, table, name)
Or even (if the DB supports it), has access to the expressions inside the string and can use named parameters:

    ('Select * from @table where name = @name', { "table": table, "name": name })
That's really nice for debugging, depending on your DB engine.

In every DB engine that supports it, parameterized SQL is even safer than escape syntaxes because parameters are passed in entirely different parts of the binary protocols and don't need to rely on just string manipulation to add escape sequences.

pansa2 [3 hidden]5 mins ago
While your code is a valid alternative way to implement @haberman's description, the feature is actually much more flexible.

The "subsequent logic" has full access to the interpolation results and strings. Not only can it escape the results, it can do whatever it wants to them. It can also do whatever it wants to the strings, and then combine everything in any way it likes - it's not even necessary that the final result is a string.

pauleveritt [3 hidden]5 mins ago
The other PEP example shows generating HTML attributes from a passed-in dictionary. HTML has a number of places where this is helpful, if you have original data.
pragma_x [3 hidden]5 mins ago
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.
sanderjd [3 hidden]5 mins ago
This will be trivial to implement (it is just a check against the type that t-strings return), so I'm sure it will be done, if it hasn't already.
xixixao [3 hidden]5 mins ago
The runtime and static type of the template string is different to fstring (Template vs str). Unless you’re passing them somewhere that accepts strings, you can’t go wrong.

I actually quite like the simplicity of this design over tagged literals in JS.

sgt [3 hidden]5 mins ago
Will it be a performance boost if Django's template engine started using t-strings internally?
pwdisswordfishz [3 hidden]5 mins ago
Why would mere syntax make a difference?
sgt [3 hidden]5 mins ago
No, I mean that if Django's template engine internally switched over its implementation to enable templating (as a functionality) to use these new t-strings. If t-strings are more highly optimized within Python, it could mean a performance boost.
pizza [3 hidden]5 mins ago
Looks useful for embedding interpreters
est [3 hidden]5 mins ago
https://peps.python.org/pep-0750/#approaches-to-lazy-evaluat...

name = "World"

template = t"Hello {(lambda: name)}"

This looks cool

mont_tag [3 hidden]5 mins ago
How does this Python docs example work with t-strings?

    cur.executemany("INSERT INTO movie VALUES(?, ?, ?)", data)
Can SQLite3 cache the query as it does now?
roywiggins [3 hidden]5 mins ago
It seems to me that it would be straightforward to parse a Template's parameters back into ? placeholders?
mont_tag [3 hidden]5 mins ago
No, it would fail upstream before the template post-processing even begin.

The template object itself could not be formed because the each name must be a visible variable name.

This is the same error that you would get with an f-string that contained an undefined variable name.

sanderjd [3 hidden]5 mins ago
I'm intrigued but confused by your comment. I think it could be done like this:

    cur.executemany(t"INSERT INTO movie VALUES({data})")
Then the Template object would have a parameter that is a list, and it could turn that into the right number of "?"s to pass into the database driver along with the data.

What am I missing?

enescakir [3 hidden]5 mins ago
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.
wodenokoto [3 hidden]5 mins ago
I'm of the opposite opinion. Let's set the prefixes free!

    from sql import sql

    query = sql"SELECT user_id, user_name FROM {user_table_versioned} WHERE user_name = {user_name}"
masklinn [3 hidden]5 mins ago
dmurray [3 hidden]5 mins ago
How would this be different from a function sql() that operates on one of these new t-strings?

The syntactic sugar of changing it from sql(t"...") doesn't seem particularly valuable. The novel thing about t-strings is that they change the parsing at compile-time.

Timwi [3 hidden]5 mins ago
> The syntactic sugar of changing it from sql(t"...") doesn't seem particularly valuable.

It's valuable because:

- IDEs could then syntax-highlight SQL inside of SQL strings and HTML inside of HTML strings

- You can't accidentally pass an HTML string to your SQL library

wodenokoto [3 hidden]5 mins ago
It’s different from a function the same way f”” is different from f(“”) and t”” is different from t(“”)

There’s nothing stopping you from building a Python function that parses a string looking for {} and then searching globals for those variables. And you can extend that to also do some code execution and formatting.

To me the real sugar of f-strings is that the editor knows that it’s a template and not just a string. Expanding this to having SQL and regex syntax highlighting, linting and code formatting inside my Python code is a pretty cool prospect.

stavros [3 hidden]5 mins ago
It wouldn't be different, but it would be more convenient because we no longer have to count the number of %s, you'd put the variable inline.
masklinn [3 hidden]5 mins ago
That's... already the case of t-strings?
stavros [3 hidden]5 mins ago
Yes, that's my point. The GP was already talking about a t-string.
masklinn [3 hidden]5 mins ago
dmurray was comparing a hypothetical sql"..." with sql(t"..."). There are no %s either way.
mcintyre1994 [3 hidden]5 mins ago
This is how JavaScript does it with tagged template literals.

Your sql there would just be a function that receives the array of strings/values and returns whatever.

dsego [3 hidden]5 mins ago
This is what JS does with tagged template literals. https://github.com/dsego/sql_tag
mulmboy [3 hidden]5 mins ago
Are there string prefixes for i18n stuff?
Biganon [3 hidden]5 mins ago
They're probably talking about the convention of using _ as an alias for `translate`
albert_e [3 hidden]5 mins ago
"Yet another" is not my main worry

The concept of prefixes itself feels a little deviant from readable code that is close to human language -- which is the spirit of Python

empiko [3 hidden]5 mins ago
Additionally, it will probably be confusing that it is called a t-string but it is actually a constructor for a Template object and not string at all. I would rather see a new special term `template` than this.
Timwi [3 hidden]5 mins ago
The single letter f or t does make it unnatural to read, but if it were sql"..." or html"...", I think that would help with that.
toxik [3 hidden]5 mins ago
Should have been a keyword.

    a = template "foo {bar}"
As should raw and format.
toxik [3 hidden]5 mins ago
So should f strings now go away? They are just a special case of t strings.

Also, don’t get me started on g strings.

perlgeek [3 hidden]5 mins ago
> So should f strings now go away?

No

> They are just a special case of t strings.

Not really, because they produce a string right away instead of a template.

nhumrich [3 hidden]5 mins ago
No. F-strings should still be the default. T-strings aren't usable directly, and don't have a `str` like API, so they aren't even compatible, intentionally.
permo-w [3 hidden]5 mins ago
is this just a copy paste of the PEP announcement?
oulipo [3 hidden]5 mins ago
Nice, so it's a kind of "limited DSL" inside python that's easy to extend
pauleveritt [3 hidden]5 mins ago
The PEP originally portrayed this as "for DSLs"
dec0dedab0de [3 hidden]5 mins ago
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.

gaogao [3 hidden]5 mins ago
The traditional way at the megacorp for something like that is unsafe_str_DO_NOT_USE_OR_YOU_WILL_BE_FIRED(template)
0xFEE1DEAD [3 hidden]5 mins ago
Call me a monarchist, but I think Python has changed for the worse ever since Guido van Rossum stepped down.
pansa2 [3 hidden]5 mins ago
I don't think things would be very different if Guido were still BDFL. He's second-author of the t-strings PEP, and has been an author of most other major PEPs in recent releases (including the walrus operator, PEG parser, and pattern matching).
sanderjd [3 hidden]5 mins ago
It's so fascinating to see so many people, when faced with a simple and useful programming language feature, get all up in arms about how it's the end of the world for that language.

I honestly feel like a lot of people just seem bored and looking for stuff to be mad about.

ashwinsundar [3 hidden]5 mins ago
In what ways has it changed for the worse?
nhumrich [3 hidden]5 mins ago
I for one think python has never been better.
TheRealPomax [3 hidden]5 mins ago
> 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.

morkalork [3 hidden]5 mins ago
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.
avereveard [3 hidden]5 mins ago
what's the tldr difference between this and .format ?
jitl [3 hidden]5 mins ago
It’s injection safe and compostable, and the resulting object retains the original values you interpolate in. This makes it useful for building SQL queries with prepared arguments for example.
pwdisswordfishz [3 hidden]5 mins ago
Compostable? The Green New Deal has gone too far.
hk__2 [3 hidden]5 mins ago
It’s on a meta level: instead of formatting the string, it returns an object that contains both the format string and its argument. Library author can then implement whatever format function they want, for example one that escapes the interpolated strings.
OJFord [3 hidden]5 mins ago
f-strings are syntactic sugar for .format, e.g.:

    f"foo is {foo} and {bar=}"
    "foo is {} and bar={}".format(foo, bar)
are equivalent.

t-strings are actually not strings, but Template objects, giving access to both the templating string and the parameters for processing. Sibling comments describe it as a custom .format implementation in that sense - it's f-string-like sugar where you're also allowed to take control of the .format function that it's sugar for.

zahlman [3 hidden]5 mins ago
f-strings are actually translated into formatting and concatenation of the pieces at a bytecode level; they don't call `.format` under the hood and thus aren't what I'd call "syntactic sugar" in the traditional sense. But yes, the equivalence holds.

t-strings, of course, (will) translate at the bytecode level into instantiation of the Template object (and whatever code is needed to compute the Interpolation values in-place).

earnestinger [3 hidden]5 mins ago
It’s custom .format implementation. (You access the placeholder and value and produce the string)
m2f2 [3 hidden]5 mins ago
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.
orthoxerox [3 hidden]5 mins ago
It's also for logging:

    log.debug(f"The value of counter was {counter}, the nonce was {nonce}")
builds a new string every time the interpreter hits this line. Whereas

    log.debug(t"The value of counter was {counter}, the nonce was {nonce}")
passes a Template to the debug() function that bails out if debug mode is not on and doesn't build a string.
bazoom42 [3 hidden]5 mins ago
Could also be used to prevent html injection.
nhumrich [3 hidden]5 mins ago
It's for SQL, HTML, and shell. But idk how solving injection, a top on the OWASP list forever is considered "overkill".
7734128 [3 hidden]5 mins ago
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 :/

masklinn [3 hidden]5 mins ago
> 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.

t-strings are a different type (both static and dynamic), f-strings are not. So t-strings can be mandated at the API level, forcing the developer into "proper" usage.

That is, you need third-party tools to differentiate between

    some_call("safe literal string")
and

    some_call(f"unsafe dynamically created string")
That is not the case when it comes to t-strings, `some_call` can typecheck internally that it got a t-string, and reject regular strings entirely.

Although some space probably needs to be made for composing t-strings together in case of e.g. runtime variation in items or their count. Facetting for instance. I don't know if that's a native affordance of t-strings.

Timwi [3 hidden]5 mins ago
But that would require any SQL library you're currently using to make the breaking change of no longer allowing strings.
sanderjd [3 hidden]5 mins ago
Yes. That's exactly what will happen, over time.
nhumrich [3 hidden]5 mins ago
Yep. Probably worth it. You could also do this with a monkeypatch method to "opt in" to this change.
baggiponte [3 hidden]5 mins ago
sqlalchemy doesn’t really accepts strings - if you do, you need to pass them into a “conn.execute(text(…))”, so end users should not face a breaking change.
masklinn [3 hidden]5 mins ago
Yes?
sanderjd [3 hidden]5 mins ago
Eventually, you won't be able to pass a string into the commonly-used DB libraries.
russfink [3 hidden]5 mins ago
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.
youruinedpython [3 hidden]5 mins ago
[flagged]
JohnKemeny [3 hidden]5 mins ago
As they mention in PEP 750,"we expect many more developers will use t-strings than write t-string processing functions."
damnitbuilds [3 hidden]5 mins ago
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".

stavros [3 hidden]5 mins ago
Yeah but it's been 17 years, maybe it's time to put the PTSD behind us. We're almost at a point where the current generation of programmers wasn't even programming when that happened.
rglullis [3 hidden]5 mins ago
> We're almost at a point where the current generation of programmers wasn't even programming when that happened

I've been programming with Python since 2006, I think most of the systems were based on 2.4 at the time. I've been one of those who switched to Python 3 somewhat late, waiting for some major libraries to ship python 3 packages - celery and Twisted were one of the biggest holdouts - so I remember that the first project where all my dependencies were ready for python 3 was around 2015.

This is to say: even seasoned developers who were conservative around the migration have spent more time working with Python 3 than Python 2. There simply is no reason anymore to be talking about python 2.

kstrauser [3 hidden]5 mins ago
The last time I touched a large Py2 project was in 2018 when I ported it to Py3. So, I have 18 years of Py2, probably 6 years of overlap, and 7 years of pure Py3. That means I still have a lot more Py2 than Py3 time.

Buuuttt, I'm so over the transition. It’s ancient now and I agree that we can stop fretting about it.

nightfly [3 hidden]5 mins ago
Python2 code didn't disappear when Python3 came out. At my work we're _still_ occasionally having to help people migrate code that was written for python2
damnitbuilds [3 hidden]5 mins ago
Also my experience, alas.

We are not completely Post Traumatic Python2 Stress yet, I am afraid.

Bad decisions can have looong-term repercussions.

nhumrich [3 hidden]5 mins ago
We're at a point where the current generation of programmers weren't even _alive_ when that happened.
pansa2 [3 hidden]5 mins ago
Yes, Python 3.0 was released 17 years ago. But the transition from Python 2.x was only completed with 2.7’s end-of-life, 5 years ago.
int_19h [3 hidden]5 mins ago
"It's still supported" is a strange metric for this. I mean, ActiveState still provides Python 2.7 builds with (paid) support.
eichin [3 hidden]5 mins ago
And Ubuntu ESM got used as an excuse/"life support" for python 2 via 16.04 until horrifyingly recently. (With a layer of "you can still get ESM for 14.04, we're not that far behind" :-)
Smithalicious [3 hidden]5 mins ago
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.

nhumrich [3 hidden]5 mins ago
For me, this is the best feature to land in python for 6 years. JS has had this and it allows my code to be completely safe from SQL injection, which is an absolutely incredible feature, given SQL injection has been the #1 vulnerability for a long time.
pauleveritt [3 hidden]5 mins ago
Thanks Nick for this response and all the time you've spent explaining. It's funny, I looked back at the comments on f-strings before they landed. They also got similar complaints about bloat. And yet, my uneducated guess: very popular.
Smithalicious [3 hidden]5 mins ago
But this doesn't prevent SQL injection, does it? It adds a grammar feature that you can then use to build SQL injection prevention following a novel idiom. Someone still needs to write the actual SQL building logic somewhere!

I don't think this is the right idiom for doing this. Frankly I don't think SQL query generation should look like string templating at all!

The sell seems to be "now you can write code that looks like an SQL injection vulnerability, without it actually being vulnerable!". I'd rather write code that isn't a vulnerability, and doesn't look like one, and doesn't have to bend the language grammar either.

stefan_ [3 hidden]5 mins ago
What? Maybe in 2005. This is just strictly worse than parameterized because now you are also wasting time "escaping" strings which perpetuates the whole mixing data & query thing nobody even wants anymore.

It's like even the one case identified nobody has even thought all the way through. Now your SQL library only accepts t-strings, I get an obscure error passing in a simple static query. Ah yes, put the useless t on it. That sorted, now the SQL library escapes all the parameters it wasn't previously doing, to then hand the final unique query to the actual underlying SQL library which would much rather have the parameterized one so it can cache parsing. Jesus.

sanderjd [3 hidden]5 mins ago
The "bloat" is that you can now put the letter "t" in front of a string, rather than "f" or "r"?
kccqzy [3 hidden]5 mins ago
I'm not the OP but I'm guessing with OP moving to Clojure that the bloat is basically any special syntax or special language feature that basically boils down to a function call or a let statement. A lot of functional programming languages have minimalistic expression syntax and they are just as expressive as OP needs them to be.
Smithalicious [3 hidden]5 mins ago
It's a pattern moreso than this specific feature. There's an important qualitative distinction for me between something that's on the level of a library (even if it's the stdlib) on the one hand and first class features (ie things that require parser level support) on the other.

Python has historically been very conservative about this but in recent years has had one controversial language extension after another, while parts of the language that actually need love are left to languish IMO.

I wanna be very clear that this is me changing my mind -- I was (still am) very on board with the highly controversial assignment expressions ("walrus operator") for instance.

I don't have much faith about what the Python language will look like if you project the current rate of changes forward 10, 15, 20 years. It really doesn't help that I consider this new thing an active antifeature.

sanderjd [3 hidden]5 mins ago
I feel like this is begging the question... This t-string feature is only controversial inasmuch as comments like yours here are criticizing it as being controversial...