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

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

python
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)

Sources