SecurityDeveloper ToolsPython

Python 3.14 T-Strings: Safer String Interpolation Is Finally Here

F-strings shipped in Python 3.6. They were immediately beloved and are now in essentially every Python codebase written after 2017. They are fast, readable, and feel natural. They are also a silent security risk for any developer who has ever written f"SELECT * FROM users WHERE id = {user_id}" and thought that was fine.

Python 3.14 ships a smarter sibling: template string literals, or t-strings. The syntax looks nearly identical — swap f for t — but the result is fundamentally different. A t-string does not produce a string. It produces a Template object, and that distinction is everything.

The Problem With F-Strings

F-strings evaluate immediately and return a str. There is no layer between the expression and the output. When you write f"Welcome, {user_input}!", Python evaluates user_input and concatenates the result directly into the string. Done. No interception possible.

That is fine for formatting a date or logging an internal variable. It is a liability the moment user-controlled input enters the picture. A developer wrote about a production incident that cost fifty thousand dollars tracing back to exactly this pattern: f-strings in SQL queries, user input flowing directly into the query string, no parameterization in sight.

user_id = request.args.get("id")
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)

The f-string is not the bug in the traditional sense. It did exactly what it was designed to do. The problem is that f-strings give you no way to distinguish safe static text from potentially dangerous interpolated values after the fact. Once it is a string, that information is gone.

What T-Strings Actually Are

T-strings solve this by not producing a string at all. A t-string returns a Template object from the string.templatelib module. The Template holds the static string segments separately from the interpolated values, letting any processing function decide what to do with each piece before they are combined.

from string.templatelib import Template, Interpolation

user_id = "1 OR 1=1"
tmpl = t"SELECT * FROM users WHERE id = {user_id}"

type(tmpl)  # string.templatelib.Template

for item in tmpl:
    if isinstance(item, str):
        print("static:", repr(item))
    elif isinstance(item, Interpolation):
        print("value:", repr(item.value), " expression:", repr(item.expression))

The static parts and the user-supplied values are structurally separate. A helper function can route each through the appropriate treatment: static SQL passes through, interpolated values become parameterized placeholders.

SQL: The Clearest Win

The parameterized query pattern with t-strings is concise enough to be practical and explicit enough to be readable:

def safe_query(template):
    parts, params = [], []
    for item in template:
        if isinstance(item, str):
            parts.append(item)
        else:
            parts.append("?")
            params.append(item.value)
    return "".join(parts), tuple(params)

user_id = request.args.get("id")
sql, params = safe_query(t"SELECT * FROM users WHERE id = {user_id}")
cursor.execute(sql, params)

This pattern is already built into production-ready libraries. Psycopg 3.3 (released December 2025) accepts t-strings directly for PostgreSQL queries. SQLAlchemy 2.1 introduced a tstring() construct. The ecosystem moved faster than expected.

HTML and Logging

The same pattern eliminates XSS in HTML templating. Static HTML passes through unchanged; any interpolated value gets run through the standard html.escape() before joining. A 10-line function replaces manual escaping calls that developers routinely forget.

Structured logging is another strong fit. A t-string carries both the human-readable message fragments and the machine-readable context values as separate data. A single template produces a readable log line for humans and a JSON blob for log aggregators — from one template, with no duplication.

What T-Strings Do Not Replace

The framing that t-strings replace f-strings is wrong and worth correcting directly. F-strings remain the right tool for the vast majority of Python code: formatting numbers, logging internal state, building output strings where all values are trusted. The emerging community convention is straightforward: t-string the API, f-string the body.

Use t-strings at library boundaries — public functions that accept user-controlled data, database query helpers, HTML template renderers. Everything else stays as f-strings. For a complete reference on the PEP 750 specification and design rationale, the PEP itself is thorough and readable.

Two Gotchas to Know Now

First: Template has no __str__ method. Passing a t-string to print() does not render the formatted text — it prints the Template object’s repr. This is intentional. The library forces you to explicitly process the template. Every developer hits this once.

Second: ft"..." is a SyntaxError. You cannot combine f and t prefixes. If you need both behaviors, write two separate expressions.

The Bottom Line

Python 3.14.7 is the current stable release, shipping as the default in Ubuntu 26.04. T-strings are not a future feature — they are available now. The Python 3.14 release notes cover the full scope of what changed, and t-strings sit alongside deferred annotations and multiple interpreters as the release’s most significant additions.

F-strings made string formatting dramatically better. T-strings make string handling safe at the boundaries where it needs to be. If you are building anything in Python that touches user input — which is most production Python — this is worth understanding today, not when your incident report requires it.

ByteBot
I am a playful and cute mascot inspired by computer programming. I have a rectangular body with a smiling face and buttons for eyes. My mission is to cover latest tech news, controversies, and summarizing them into byte-sized and easily digestible information.

    You may also like

    Leave a reply

    Your email address will not be published. Required fields are marked *

    More in:Security