Les t-strings : PEP 750

Python 3.14 introduit les template strings (t-strings), un nouveau type de littéral préfixé par t. Contrairement aux f-strings qui produisent directement une str, les t-strings produisent un objet Template contenant les parties statiques et les interpolations non encore évaluées en chaîne. Cela permet un traitement intermédiaire avant la conversion finale.

Syntaxe et objet Template

La syntaxe est identique aux f-strings, mais le préfixe t remplace f. Le résultat est un objet Template avec deux attributs : strings (les parties littérales) et interpolations (les valeurs insérées, avec leur expression source et leur format optionnel).

python
from templatelib import Template, Interpolation

nom = "Alice"
age = 30

# f-string : produit directement une str
f_result = f"{nom} a {age} ans"
print(type(f_result))  # <class 'str'>

# t-string : produit un objet Template
t_result = t"{nom} a {age} ans"
print(type(t_result))  # <class 'templatelib.Template'>

# Inspection du contenu
print(t_result.strings)          # ('', ' a ', ' ans')
print(t_result.interpolations)   # deux objets Interpolation
for interp in t_result.interpolations:
    print(f"  valeur={interp.value!r}, expr={interp.expression!r}")
# valeur='Alice', expr='nom'
# valeur=30, expr='age'

Cas pratique : prévention d'injection SQL

L'intérêt majeur des t-strings est la sécurité. On peut écrire un processeur qui échappe automatiquement les valeurs interpolées avant de les insérer dans une requête, éliminant les injections SQL sans recourir à des placeholders manuels.

python
from templatelib import Template
import sqlite3


def sql(template: Template) -> tuple[str, list]:
    """Convertit un t-string en requête paramétrée."""
    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


# Utilisation : impossible d'injecter du SQL
nom_utilisateur = "Alice'; DROP TABLE users; --"
query, params = sql(t"SELECT * FROM users WHERE name = {nom_utilisateur}")
print(query)   # SELECT * FROM users WHERE name = ?
print(params)  # ["Alice'; DROP TABLE users; --"]

# La valeur dangereuse est passée comme paramètre,
# jamais interpolée dans la requête.
# conn = sqlite3.connect(':memory:')
# conn.execute(query, params)

Sources