|
|
Log in / Subscribe / Register

Template strings accepted for Python 3.14

The Python Steering Council accepted PEP 750 ("Template Strings") on April 10. LWN covered the discussion around the proposal, including the substantial revisions to the idea that were needed for it to be accepted. Template strings (t-strings) are a new kind of string that produces structured data instead of a raw string, allowing library authors to build their own custom template-handling logic. Since the approval happened before the cutoff for new features (May 6), support for template strings will be included in Python 3.14, scheduled for October 2025.



to post comments

A Welcome Syntax

Posted Apr 22, 2025 0:57 UTC (Tue) by ryanduve (subscriber, #127786) [Link] (22 responses)

I like this `t"string"` syntax more than other syntax changes introduced after the release of Python 3. The async/await/match keywords, the walrus operator and type hint syntax feel like they get in the way more than they help. The single-character string prefix seems unobtrusive. I hope future changes are more like this one.

A Welcome Syntax

Posted Apr 22, 2025 11:51 UTC (Tue) by intelfx (subscriber, #130118) [Link] (21 responses)

What would you propose instrad of async/await? This is the overwhelmingly common syntax that covers a very specific functionality niche, so I don’t see how it can “get in the way” on principle. If you need asynchronous execution, then this is the best way we know how to express asynchronous execution in code; if you don’t, then… you don’t use it?

A Welcome Syntax

Posted Apr 22, 2025 12:19 UTC (Tue) by farnz (subscriber, #17727) [Link] (20 responses)

I would propose making await "more functiony" in a Python world; writing await foo is shades of Python 2's print foo, which became print(foo) in Python 3. So, make await foo become await(foo).

Once you've done that, there's less need for the async keyword (since Python is dynamically typed, so you don't need the type signature to tell you whether this thing is a function or a coroutine), so I'd also be tempted to remove the async keyword in Python; await(…) becomes syntax for "this thing takes part in coroutine scheduling at this point", and have the decision about whether a function is async or not be made based on whether or not it contains a await(…) in the body.

A Welcome Syntax

Posted Apr 22, 2025 15:22 UTC (Tue) by kleptog (subscriber, #1183) [Link] (17 responses)

That doesn't make much sense. You can't implement await as a function, so making it look like a function call would be weird. Await is basically a another name for yield, and nobody is talking turning yield into a function call.

You're right that strictly speaking the await is optional. But I think they wanted to avoid a repeat of the "magic" where using yield in a function body magically turns it into a generator. The whole explicit over implicit and all that.

A Welcome Syntax

Posted Apr 22, 2025 15:28 UTC (Tue) by farnz (subscriber, #17727) [Link] (16 responses)

I'd also change yield to be syntactically the same as a function call; the issue I have with Python's current await foo syntax is the implicit order of evaluation, and the parentheses make order of evaluation explicit. I'd be happy for them to be {} instead of (), or some other formatting, to make it clear that it's not a function; what I'm not happy with is the current thing where await and yield let my coworkers write code that works, but is unclear because they've shoved await in wherever they think they need it, rather than thinking about what they want it to say.

A Welcome Syntax

Posted Apr 22, 2025 18:56 UTC (Tue) by NYKevin (subscriber, #129325) [Link] (15 responses)

await foo is an expression, like not foo. If your colleagues are unable to understand that expressions in Python are eagerly evaluated, leftmost innermost first, I do not see how parentheses around foo would change that.

print() is very different. It was never an expression in 2.x - it was a statement, which is pointlessly restrictive in Python due to the use of syntactic whitespace (i.e. you couldn't put it in a lambda, or in various other places where almost any other code can go). It grew parentheses because, at the end of the day, it isn't a sufficiently "primitive" operation to make sense as a keyword or operator.

A Welcome Syntax

Posted Apr 23, 2025 8:49 UTC (Wed) by farnz (subscriber, #17727) [Link] (14 responses)

My colleagues who write Python aren't software developers by training - they're writing Python because that's the easiest language for them to automate things with. Adding parentheses makes it clear that the thing inside the parentheses is evaluated first, since that's the norm in mathematics, too. As it is, they simply don't know when await is evaluated - it is something they insert pretty much at random until things work, and needs tidying up afterwards.

A Welcome Syntax

Posted Apr 23, 2025 9:00 UTC (Wed) by mb (subscriber, #50428) [Link] (13 responses)

>As it is, they simply don't know when await is evaluated

This can be changed.
Problem solved.

A Welcome Syntax

Posted Apr 23, 2025 9:02 UTC (Wed) by farnz (subscriber, #17727) [Link] (9 responses)

I've been trying to get it through to people for over two years now - the trouble is that they don't write code very often, and after 6 months of not coding, they forget most of the details.

It is getting better now that we have access to LLMs trained on FOSS code - they tend to ask an LLM for a starting point, and the LLM writes better code than they do.

A Welcome Syntax

Posted Apr 23, 2025 12:24 UTC (Wed) by kleptog (subscriber, #1183) [Link] (8 responses)

> I've been trying to get it through to people for over two years now - the trouble is that they don't write code very often, and after 6 months of not coding, they forget most of the details.

In that case I wonder why they're even using async/await at all? It's not exactly a trivial feature. Is there some company library that requires its use?

I'm also struggling to think of a situation where it's unclear where to put the await. It's almost always applied to a function call and I don't see the benefit of writing:

res = await(func(foo, ...))

over

res = await func(foo, ...)

Just seems like excessive parentheses. If you're calling an async function, stick the await right before the function name. That's all that's required. I can't believe that's hard to remember if you know the rest of Python. If you're using something like VSCode it will show you where you went wrong.

A Welcome Syntax

Posted Apr 23, 2025 12:44 UTC (Wed) by farnz (subscriber, #17727) [Link] (7 responses)

They use await because they're working with a whole pile of external libraries (since virtually all they write is glue code for hardware test harnesses) that require them to use await. And the trouble is that they don't know Python - or tcl, or bash, or VBA, or any other programming language - which means that they're coming up with ad-hoc theories of how programming works.

For example, they expect that res = func(foo) + func2(bar); res = await res will work, as will res = await func(foo) + func2(bar), as shorthand for res = await func(foo) + await func2(bar), because that fits their ad-hoc theories, and it doesn't work (for reasons that are very obvious to any programmer). I had some success getting them to write this as res = (await func(foo)) + (await func2(bar)), but that's fallen by the wayside over time as people remove the outer parentheses, and then get confused again when they come to copy-and-paste this into a new test harness.

If they were programming regularly, they'd learn that "await func" is indivisible. But they're not - they're usually working in the lab with physical hardware, writing equations and things to convince themselves that their measurements are telling them what they expect to see, and then eventually coming back to automate this. And we're not big enough to afford someone employed full time to turn their test harnesses into code based on written descriptions, so we suffer instead.

A Welcome Syntax

Posted Apr 24, 2025 10:29 UTC (Thu) by taladar (subscriber, #68407) [Link] (6 responses)

Maybe a language that is stricter would be better suited for people who are not very familiar with programming? Python expects the programmer to do a lot of the work in keeping the program correct that is done by the compiler in a strict language like Rust.

A Welcome Syntax

Posted Apr 24, 2025 10:42 UTC (Thu) by farnz (subscriber, #17727) [Link] (5 responses)

We're kinda stuck here, because we don't have the resources to support our own bindings to the vendor-supplied hardware libraries. The vendor supports C89 using callback hell, or async Python; we've asked them to support a "simple" interface using non-async Python (which is plenty for our use case), but they don't want to, citing a belief that "big" customers won't take them seriously if they offer a simple interface as well as a complicated one.

And it's simpler for us to keep retraining the hardware people on Python every 6 months than to maintain a wrapper around the (constantly changing) vendor libraries to make it easy for them. The annoyance is that this is purely syntactical - they see await foo() as two separate things, when they should see it as one for their purposes, and they get confused because in their mental model of "how this works", keyword function() implies returning from this function to the caller, (as return and yield do), and that's not what they want to do here. Changing it to "look" like a function call (even though it isn't) would fit their mental model, since that's what they want to do - run the thing until it returns an answer.

A Welcome Syntax

Posted Apr 24, 2025 11:13 UTC (Thu) by mb (subscriber, #50428) [Link] (4 responses)

I really don't understand what the problem is that you are talking about.
If you want parenthesis after await, then just add them. You can do that today.

$ cat t.py
import asyncio

async def x():
    print('A')
    await asyncio.sleep(1)
    print('B')
    await(asyncio.sleep(1))
    print('C')

asyncio.run(x())

$ python3 t.py
A
B
C

A Welcome Syntax

Posted Apr 24, 2025 11:28 UTC (Thu) by farnz (subscriber, #17727) [Link]

Ideally, I would get a syntax error if I wrote await asyncio.sleep(5), because the mental model my Python-using colleagues have is that keyword expression means "return the value of expression to the caller of this function somehow", whereas keyword(expression) means "have keyword call expression and give me back a value".

And because they don't want to return from this function (they want to wait for expression to evaluate, which involves hardware accesses), they try to put the minimum number of awaits in that seems to work for them, scoping them as broadly as possible and dragging other people in when they can't guess their way to working code.

A Welcome Syntax

Posted Apr 24, 2025 18:28 UTC (Thu) by Wol (subscriber, #4433) [Link] (2 responses)

> I really don't understand what the problem is that you are talking about.
> If you want parenthesis after await, then just add them. You can do that today.

The problem is you ass-u-me-ing that the people doing the program know to do it.

And training is not the answer when (as seems to be the case here) they are doing it maybe twice in a blue moon.

I think you need a spell doing 1st-line support for people who either (a) don't program as part of their work, or (b) for whom it is a necessary evil. Most of them are unteachable ...

Cheers,
Wol

A Welcome Syntax

Posted Apr 24, 2025 18:37 UTC (Thu) by mb (subscriber, #50428) [Link] (1 responses)

>The problem is you ass-u-me-ing that the people doing the program know to do it.

No.
The complaint was that the people want to use parenthesis, because that better fits to how they think.
Well, I'm fine with that.
And that Python should allow that.

But that's actually possible with current Python.
Therefore, I really don't know where the problem is.

If you want to use parenthesis, use them.
If you don't want to use parenthesis, don't use them.

And if you don't know the language, learn it.
This is orthogonal to whether async is spelled with or without parenthesis.

A Welcome Syntax

Posted Apr 25, 2025 8:53 UTC (Fri) by farnz (subscriber, #17727) [Link]

No.
The complaint is that Python permits you to use a space instead, which results in people getting confused by the code they copy-and-paste from elsewhere, since they don't understand why they're "returning" something here, and come up with all sorts of weird mental models for how await changes the context after it - e.g. that await foo() + bar() is semantically the same as await foo() + await bar().

Python should not use the same syntactic convention for "things that return a value from this function to its caller (such as yield and return)" and "things that evaluate something into a different type of value (such as await)".

Adopting a Rust-like "postfix await" would also work for this - because it means that it's a different syntax for a different behaviour - but I would expect that to be a much bigger sell to Python people than parens.

A Welcome Syntax

Posted Apr 23, 2025 10:48 UTC (Wed) by Wol (subscriber, #4433) [Link] (2 responses)

> >As it is, they simply don't know when await is evaluated

> This can be changed.

You may be a genius, but the average person is, well, average ...

> Problem solved.

And as has been mentioned elsewhere, just because they may be a genius at *their* job, does not mean that they are a genius at programming. More importantly, because they *are* a genius at their job, it's a safe bet they're mediocre at best at programming.

I don't know how old you are, but as I get older I run into more and more people who CAN NOT learn. That doesn't mean they don't want to, it means it doesn't stick. Even worse, people who used to know, but have lost the knowledge and can't get it back ...

Youngsters always think there's a magic bullet somewhere. My bugbear is the current crop who think that technology is the perfect solution for people who can *no* *longer* *cope* with technology. (TVs, PVRs et al being a case in point. Streaming is NOT a replacement for VHS and DVDs. Great for youngsters, but I'm rapidly moving into the generation that just can't cope with it any more.)

Cheers,
Wol

A Welcome Syntax

Posted Apr 23, 2025 11:41 UTC (Wed) by mb (subscriber, #50428) [Link] (1 responses)

You don't need to be a genius to understand how await is evaluated.

A Welcome Syntax

Posted Apr 23, 2025 13:39 UTC (Wed) by Wol (subscriber, #4433) [Link]

> You don't need to be a genius to understand how await is evaluated.

Just look at farnz' example. It's clearly beyond a fair few genii ...

Cheers,
Wol

A Welcome Syntax

Posted Apr 22, 2025 19:52 UTC (Tue) by intelfx (subscriber, #130118) [Link] (1 responses)

The `await foo` thing has already been addressed by other people who made much better points than I ever could, so I'll just comment on this:

> Once you've done that, there's less need for the async keyword (since Python is dynamically typed, so you don't need the type signature to tell you whether this thing is a function or a coroutine), so I'd also be tempted to remove the async keyword in Python; await(…) becomes syntax for "this thing takes part in coroutine scheduling at this point", and have the decision about whether a function is async or not be made based on whether or not it contains a await(…) in the body.

This is pure spooky action at a distance.

The async-ness of the function drastically changes the way it has to be used, with no room for any kind of substitution principle (i.e., yes, in a dynamically typed language, you can change the return type in some ways if the new type supports all operations the old one did, but this does not work with generators because there is zero overlap between normal types and generator types).

So no, I cannot agree that such a change could be in any way desirable.

A Welcome Syntax

Posted Apr 23, 2025 11:01 UTC (Wed) by wjb (subscriber, #71810) [Link]

I've seen similar issues in Dart a few years back

There is a problem when you accidentally call an aysnc function (one with side effects you need) without an await. You can also end up in a real mess when a function that has existed a while is changed to async because of reasons and you fail to start by putting an await everywhere its used.

I have thought that a reasonable way of dealing with this might be to require that all async function calls be preceded by await or async keyword, so the expected behaviour is absolutely obvious and any change to the asyncness(?) of a function forces you to look at every point where it's used.

Could’ve been long ago

Posted Apr 22, 2025 7:11 UTC (Tue) by xi0n (guest, #138144) [Link] (2 responses)

So, this is basically format_args!() from Rust, producing an inspectable object containing the format string and its arguments. Neat, and the applications are quite obvious.

Funnily enough, this isn’t really tied to f-strings, other than the fact that the existence of f”” literals mandates this to be a special literal, too. One could easily imagine str.formatargs function, that takes same params as str.format but returns a Template, to have been added even to Python 2.x.

Could’ve been long ago

Posted Apr 22, 2025 15:43 UTC (Tue) by NYKevin (subscriber, #129325) [Link]

This nomintally existed in 2.x. It was (and nominally still is) spelled string.Template, but unfortunately, you had to implement 90% of it yourself, because it just provides a top-level entry point for overriding the whole interpolation: https://docs.python.org/3/library/string.html#template-st...

Supposedly it is used for i18n, but I have no familiarity with that use case. If t-strings end up being wildly popular, they might plausibly end up absorbing and replacing this use case.

Could’ve been long ago

Posted Apr 23, 2025 7:29 UTC (Wed) by iabervon (subscriber, #722) [Link]

As compared to str.format(), there's been string.Formatter to allow for producing something other than a str and there's been f-strings to allow for writing the arguments inside the format string literal. This is essentially combining those differences (with improvements to the API from experience with string.Formatter and for the fact that Interpolation can actually be given the text of the expression).

While it is mostly not functionally different from format_args!(), it's actually a huge ergonomic benefit to be able to write the arguments interleaved with the string literal text. For example, this allows for writing code that doesn't have SQL injection vulnerabilities that is actually more obvious and readable than code that does have SQL injection vulnerabilities. You've had to tell people why they must not write "SELECT * FROM users WHERE name='"+name+"' AND password='"+password'";", or f"SELECT * FROM users WHERE name='{name}' AND password='{password}';", and have to instead write execute("SELECT * FROM users WHERE name={} AND password={};", name, password), which is harder to read but actually secure. But now they can use t"SELECT * FROM users WHERE name={name} AND password={password};" and the code you can read in order without any extra quotes is actually correct.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds