You’ve been writing f-strings since Python 3.6. They’re fast, readable, and feel like the right tool for everything. But there is a class of problems they were never designed to solve — and if you’ve ever used an f-string to build a SQL query or an HTML snippet, you’ve walked straight into it. Python 3.14 ships a new string type called t-strings (PEP 750), and psycopg 3.3 just became the first major library to put them to real production use.
What’s Wrong With F-Strings?
Nothing, in most cases. But f-strings evaluate immediately. The moment Python sees f"SELECT * FROM users WHERE name = '{user_input}'", it produces a finished string. The database driver that receives it has no idea which parts were literals and which were user-supplied values — it’s all one blob of text.
That’s why every database tutorial tells you to use parameterized queries with ? or %s placeholders instead of concatenating values into the query string. F-strings don’t solve the underlying problem; they just make it easier to accidentally bypass the protection you already had.
The same logic applies to HTML. If you use an f-string to embed user input into a template and forget to escape it, you’ve introduced an XSS vulnerability. The f-string has no mechanism to help you — it just combines the parts and hands you a string.
T-Strings: What They Actually Are
T-strings use a t prefix instead of f, and they do not return a string. That last part is the key. A t-string returns a Template object from the new string.templatelib module. The Template holds the literal parts and the interpolated values separately, letting a library decide what to do with them before any output is produced.
name = "Alice"
count = 3
greeting = t"Hello, {name}! You have {count} messages."
# greeting.strings → ('Hello, ', '! You have ', ' messages.')
# greeting.interpolations → (Interpolation('Alice', ...), Interpolation(3, ...))
The Template is not a string. You cannot pass it to print() or concatenate it with +. You pass it to a function or library that knows how to process it — and that function gets to see every interpolated value before deciding how to render it. That’s the safety hook f-strings don’t have.
The concept is not new. JavaScript has had tagged template literals since ES6 (2015). Python is catching up — a decade later — but the implementation is clean and the stdlib module makes it straightforward for library authors to build on.
Psycopg 3.3 Makes It Real
Psycopg, the standard PostgreSQL adapter for Python, shipped t-string support in version 3.3 (December 2025). Here’s what it looks like compared to the old approach:
import psycopg
# The old way — parameterized, but verbose
cur.execute("SELECT * FROM users WHERE name = %s", [user_input])
# The t-string way — same safety, better ergonomics
cur.execute(t"SELECT * FROM users WHERE name = {user_input}")
When psycopg receives the Template, it extracts user_input as a parameter and generates SELECT * FROM users WHERE name = $1, binding the value separately. The user input never touches the query string. SQL injection via this path is structurally impossible.
It handles dynamic table and column names too, via format specs:
# :i → psycopg treats this as an SQL identifier (quoted with "")
conn.execute(t"DELETE FROM {table_name:i} WHERE id = {record_id}")
# Generates: DELETE FROM "users" WHERE id = $1
This is strictly better than the manual parameterized query approach. You get the same protection without the syntactic overhead of managing placeholder lists.
HTML Escaping Works the Same Way
The pattern generalizes. Write a function that takes a Template, escapes all interpolated values, and returns safe markup:
from markupsafe import Markup, escape
from string.templatelib import Template
def safe_html(t: Template) -> Markup:
parts = [item if isinstance(item, str) else str(escape(item.value)) for item in t]
return Markup("".join(parts))
user_input = "alert('xss')"
result = safe_html(t"Hello, {user_input}!
")
# → Hello, <script>alert('xss')</script>!
Flask and FastAPI have experimental t-string-aware APIs in progress. When those land, this pattern becomes the default — you write a clean template, the framework handles escaping, and XSS becomes the framework’s problem rather than something you manually remember to do.
F-String or T-String: How to Decide
This is not a “use t-strings for everything” argument. F-strings are still the right tool most of the time. Here’s a practical split:
Use f-strings for: internal variables, debugging output, log messages with trusted data, display formatting, any interpolation where all values come from your own code and the result is not interpreted by another system.
Use t-strings for: any value that comes from a user, a network request, a file, or any external source; any output that will be interpreted by a database, browser, or shell; and anywhere you want to defer or customize interpolation behavior in a library context.
The boundary is simple: if you can trace every interpolated value back to your own code and the output is just text for display, use an f-string. If anything external is touching those values or the output gets executed somewhere, reach for a t-string and a library that handles the escaping.
Getting Started
Python 3.14 ships with t-string support enabled by default — no flags, no build options. Update your Python version and the syntax is available immediately:
python3.14 --version
# Python 3.14.6
name = "World"
t_obj = t"Hello {name}"
print(type(t_obj)) #
print(t_obj.strings) # ('Hello ', '')
print(t_obj.interpolations[0].value) # World
For database work, upgrade psycopg to 3.3 or later and start replacing verbose parameterized queries with t-strings. The PEP 750 specification and the Real Python t-strings guide are both worth reading once you’ve seen the basics in action.
T-strings are not a replacement for f-strings. They’re the right tool for the specific, common problem of safely interpolating external data into structured output. Now that psycopg ships support, there’s no good reason to keep building SQL queries by hand with placeholder lists — or worse, with f-strings.













