Python's new t-strings
Template strings, also known as t-strings, have been officially accepted as a feature in Python 3.14, which will ship in late 2025. 🎉
I’m excited; t-strings open the door to safer more flexible string processing in Python.
What’s the big idea with t-strings?
Since they were introduced in Python 3.6, f-strings have become a very popular way to format strings. They are concise, readable, and powerful.
In fact, they’re so delightful that many developers use f-strings for everything… even when they shouldn’t!
Alas, f-strings are often dangerously (mis)used to format strings that contain user input. I’ve seen f-strings used for SQL (f"SELECT * FROM users WHERE name = '{user_name}'"
) and for HTML (f"<div>{user_name}</div>"
). These are not safe! If user_name
contains a malicious value, it can lead to SQL injection or cross-site scripting.
Template strings are a generalization of Python’s f-strings. Whereas f-strings immediately become a string, t-strings evaluate to a new type, string.templatelib.Template
:
from string.templatelib import Template
name = "World"
template: Template = t"Hello {name}!"
Importantly, Template
instances are not strings. The Template
type does not provide its own __str__()
implementation, which is to say that calling str(my_template)
does not return a useful value. Templates must be processed before they can be used; that processing code can be written by the developer or provided by a library and can safely escape the dynamic content.
We can imagine a library that provides an html()
function that takes a Template
and returns a safely escaped string:
evil = "<script>alert('bad')</script>"
template = t"<p>{evil}</p>"
safe = html(template)
assert safe == "<p><script>alert('bad')</script></p>"
Of course, t-strings are useful for more than just safety; they also allow for more flexible string processing. For example, that html()
function could return a new type, HTMLElement
. It could also accept all sorts of useful substitutions in the HTML itself:
attributes = {"src": "roquefort.jpg", "alt": "Yum"}
template = t"<img {attributes} />"
element = html(template)
assert str(element) == "<img src='roquefort.jpg' alt='Yum' />"
If you’ve worked with JavaScript, t-strings may feel familiar. They are the pythonic parallel to JavaScript’s tagged templates.
How do I work with t-strings?
To support processing, Template
s give developers access to the string and its interpolated values before they are combined into a final string.
The .strings
and .values
properties of a Template
return tuples:
name = "World"
template = t"Hello {name}!"
assert template.strings == ("Hello ", "!")
assert template.values == (name,)
There is always one more (possibly empty) string than value. That is, t"".strings == ("",)
and t"{name}".strings == ("", "")
.
As a shortcut, it’s also possible to iterate over a Template
:
name = "World"
template = t"Hello {name}!"
contents = list(template)
assert contents[0] == "Hello "
assert contents[1].value == name
assert contents[2] == "!"
Developers writing complex processing code can also access the gory details of each interpolation:
name = "World"
template = t"Hello {name!s:>8}!"
assert template.interpolations[0].value == name
assert template.interpolations[0].expression == "name"
assert template.interpolations[0].conversion == "s"
assert template.interpolations[0].format_spec == ">8"
In addition to supporting the literal (t"foo"
) form, Template
s can also be instantiated directly:
from string.templatelib import Template, Interpolation
template = Template(
"Hello ",
Interpolation(value="World", expression="name"),
"!"
)
Strings and interpolations can be provided to the Template
constructor in any order.
A simple t-string example
Let’s say we wanted to write code to convert all substituted words into pig latin. All it takes is a simple function:
def pig_latin(template: Template) -> str:
"""Convert a Template to pig latin."""
result = []
for item in template:
if isinstance(item, str):
result.append(item)
else:
word = item.value
if word and word[0] in "aeiou":
result.append(word + "yay")
else:
result.append(word[1:] + word[0] + "ay")
return "".join(result)
name = "world"
template = t"Hello {name}!"
assert pig_latin(template) == "Hello orldway!"
This is a goofy example; if you’d like to see some less silly examples, check out the PEP 750 examples repository.
What’s next once t-strings ship?
T-strings are a powerful new feature that will make Python string processing safer and more flexible. I hope to see them used in all sorts of libraries and frameworks, especially those that deal with user input.
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.
It’s been fun to get to know and work with Jim, Paul, Koudai, Lysandros, and Guido on this project and to interact with many more members of the Python community online without whose input PEP 750 simply wouldn’t have come together. I can’t wait to see what developers build with t-strings once they ship!