t-strings: PEP 750
Python 3.14 introduces template strings (t-strings), a new literal type prefixed with t. Unlike f-strings which directly produce a str, t-strings produce a Template object containing the static parts and the not-yet-stringified interpolations. This allows intermediate processing before final conversion.
Syntax and Template Object
The syntax is identical to f-strings, but with the t prefix instead of f. The result is a Template object with two attributes: strings (the literal parts) and interpolations (the inserted values, with their source expression and optional format spec).
from templatelib import Template, Interpolation
name = "Alice"
age = 30
# f-string: produces a str directly
f_result = f"{name} is {age} years old"
print(type(f_result)) # <class 'str'>
# t-string: produces a Template object
t_result = t"{name} is {age} years old"
print(type(t_result)) # <class 'templatelib.Template'>
# Inspecting the contents
print(t_result.strings) # ('', ' is ', ' years old')
print(t_result.interpolations) # two Interpolation objects
for interp in t_result.interpolations:
print(f" value={interp.value!r}, expr={interp.expression!r}")
# value='Alice', expr='name'
# value=30, expr='age'
Practical Use Case: SQL Injection Prevention
The main benefit of t-strings is security. You can write a processor that automatically escapes interpolated values before inserting them into a query, eliminating SQL injection without manual placeholders.
from templatelib import Template
import sqlite3
def sql(template: Template) -> tuple[str, list]:
"""Convert a t-string into a parameterized query."""
parts = []
params = []
for i, s in enumerate(template.strings):
parts.append(s)
if i < len(template.interpolations):
parts.append("?")
params.append(template.interpolations[i].value)
return "".join(parts), params
# Usage: SQL injection is impossible
username = "Alice'; DROP TABLE users; --"
query, params = sql(t"SELECT * FROM users WHERE name = {username}")
print(query) # SELECT * FROM users WHERE name = ?
print(params) # ["Alice'; DROP TABLE users; --"]
# The dangerous value is passed as a parameter,
# never interpolated into the query.
# conn = sqlite3.connect(':memory:')
# conn.execute(query, params)
