Python's new t-strings
55 comments
·April 21, 2025sashank_1509
jsnell
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.
TekMol
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.
fweimer
The SQLite extension for Tcl offers something similar:
db1 eval {INSERT INTO t1 VALUES(5,$bigstring)}
https://sqlite.org/tclsqlite.html#the_eval_methodjbaiter
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
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.
baggiponte
Type checkers to the rescue ahaha I think db.get could also raise if the type does not match?
TekMol
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?
hyperbovine
OP is referring to swapping t with f.
codesnik
f'' vs t'' probably.
null
gnabgib
Big discussion (414 points, 10 days ago, 324 comments) https://news.ycombinator.com/item?id=43647716
runekaagaard
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"
masklinn
A t-string is a literal for a Template object which is a data hoilder, it doesn't actually do anything, so you would simply call
bar(t"zoop")
thund
Use a function?
bar(“zoop”)
It’s just syntax, like we used to have print “foo”
that later became print(“foo”)
Timwi
True. Then you could use
sql"..."
html"..."
for each of the given examples and achieve some runtime type safety.sgt
Will it be a performance boost if Django's template engine started using t-strings internally?
nezirus
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
That's the exact use case. Basically these are syntactic sugar for a very similar function signature.
pizza
Looks useful for embedding interpreters
enescakir
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
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
This was considered and rejected: https://peps.python.org/pep-0750/#arbitrary-string-literal-p...
dmurray
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
> 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
stavros
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.
mcintyre1994
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.
albert_e
"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
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
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.
oulipo
Nice, so it's a kind of "limited DSL" inside python that's easy to extend
avereveard
what's the tldr difference between this and .format ?
jitl
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.
porridgeraisin
[flagged]
hk__2
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.
earnestinger
It’s custom .format implementation. (You access the placeholder and value and produce the string)
7734128
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
> 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
But that would require any SQL library you're currently using to make the breaking change of no longer allowing strings.
baggiponte
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.
damnitbuilds
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
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
> 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.
nightfly
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
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++