Python recently made a big news: the PEP-750 t-string syntax has been officially adopted!
This means that Python will introduce a new string prefix in version 3.14 released in October this yeart
, called Template Strings, i.e. t-string.
This is a major upgrade in string processing capabilities following f-string, aiming to provide a safer and more flexible string interpolation processing mechanism.
The basic syntax of t-string is very similar to that of f-string, but the results are very different:
name = "World"
# f-string syntax
formatted = f"Hello {name}!"
print(type(formatted)) # Output: <class 'str'>
print(formatted) # Output: Hello World!
# t-string syntax
templated = t"Hello {name}!"
print(type(templated)) # Output: <class ''>
print() # Output: ('Hello ', '')
print([0].value) # Output: World
print("".join(
item if isinstance(item, str) else str()
for item in templated
)) # Output: Hello World!
As mentioned above, t-string is different from f-string, it does not immediately get a normal string, but returns a new typeTemplate
(From Python 3.14 new standard library module)。
This type cannot be output directly as a normal string, but it provides structured access to the string and its internal interpolation expressions, allowing developers to add interception and conversion before string combination insertion values.
In a word, the core feature of t-string isDelay rendering。
Why design t-string?
f-string is popular for its simplicity and ease of use, but it also has some limitations that cannot be ignored:
- Safety hazards: f-string can cause injection attacks when directly embedding user input into SQL queries, HTML content, or system commands
- Lack of conversion ability:f-string does not provide a mechanism for intercepting and converting insert values before string combination
- Insufficient flexibility: For complex string processing tasks, f-string has limited capabilities
Improve the security of string processing
Improper use of f-string may lead to security vulnerabilities:
# Unsafe example using f-string (SQL injection risk)
sql_query = f"SELECT * FROM users WHERE name = '{user_input}'"
# Unsafe examples using f-string (XSS risk)
html_content = f"<div>{user_input}</div>"
t-string allows developers to properly handle interpolation before string combination:
# Security example using t-string
evil = "<script>alert('evil')</script>"
template = t"<p>{evil}</p>"
# You can define processing functions to escape content
assert html(template) == "<p><script>alert('evil')</script></p>"
Enhanced string processing flexibility
The biggest advantage of t-string is that it provides a unified string processing mechanism, allowing developers to implement various custom rendering logic according to actual needs. This design avoids the complexity of creating a specialized syntax for each scenario while maintaining a simple and unified style of Python.
The following example shows how to output different formats using different renderers based on the same t-string template:
from import Template, Interpolation
data = {"name": "Python Cat", "age": 18}
template = t"The age of user {data['name']} is {data['age']}"
def standard_renderer(template: Template) -> str:
"""Standard text rendering"""
return "".join(
item if isinstance(item, str) else str()
for item in template
)
def json_renderer(template: Template) -> str:
"""JSON format rendering"""
import json, re
values = {}
for item in template:
if not isinstance(item, str):
# Use regular expression to extract key names from expressions
# Match name in data['name'] or data["name"] mode
match = (r"\['([^']+)'\]|\[\"([^\"]+)\"\]", )
If match:
# Get the first matched group
key = (1) if (1) else (2)
values[key] =
return (values, ensure_ascii=False)
def xml_renderer(template: Template) -> str:
"""XML format rendering"""
parts = ["<data>"]
for item in template:
if not isinstance(item, str) and hasattr(item, "expression"):
name = ("'")[1] if "'" in else
(f" <{name}>{}</{name}>")
("</data>")
return "\n".join(parts)
# Same template, different output formats
print(standard_renderer(template)) # Output: The age of the user Python cat is 18
print(json_renderer(template)) # Output: {"name": "Python Cat", "age": 18}
print(xml_renderer(template)) # Output: <data>\n <name>Python Cat</name>\n <age>18</age>\n</data>
This flexibility is not available to f-string and is very valuable for building various DSLs (domain-specific languages), template engines, or formatting systems.
The structure of the Template class
t-string after evaluationTemplate
A class has the following main properties and methods:
class Template:
strings: tuple[str, ...]
"""
A non-empty tuple of the string part in the template.
Contains N+1 elements, where N is the number of interpolated expressions in the template.
"""
interpolations: tuple[Interpolation, ...]
"""
Tuples of the interpolated portions in the template.
If there is no interpolation expression, this will be an empty tuple.
"""
def __new__(cls, *args: str | Interpolation):
"""
Create a new Template instance.
Parameters can be provided in any order.
"""
...
@property
def values(self) -> tuple[object, ...]:
"""
Returns a tuple composed of the `value` attribute of each Interpolation in the template.
If there is no interpolation expression, this will be an empty tuple.
"""
...
def __iter__(self) -> Iterator[str | Interpolation]:
"""
Iterate over string parts and interpolation expressions in the template.
These may appear in any order. No empty string is included.
"""
...
This structure enables developers to:
-
Access the original string fragment (
strings
) -
Access interpolation expression and its calculation results (
interpolations
) -
Get all interpolated values directly (
values
) -
Iterate all components of the template in order
Note:__iter__
Function comments say that the order of occurrence is not fixed, but its specific implementation in the PEP document is in sequence. I think the comments are incorrect.
Similarities and differences between t-string and f-string
Similarities
-
Basic syntax: Both use curly braces
{}
As a separator for interpolation expression - Expression evaluation: Both support placing any Python expression in curly braces
-
Format specifier: Both support format specifiers (such as
.2f
) and conversion specifiers (e.g.!r
) -
Quotation mark support: All valid quote marks are supported (
'
、"
、'''
、"""
) -
Case-insensitive prefix:
t
andT
All are valid, just likef
andF
The difference
-
Return type: f-string returns directly
str
type, and t-string returnsTemplate
type - Evaluation timing: f-string is evaluated immediately when defined, t-string provides delayed evaluation capability
- Structural access:t-string allows access to the structure of the original template (string part and interpolation part)
- Processing Model: f-string is the "instant completion" model, t-string is the "preprocessing + conversion" model
Application scenarios of t-string
1. Secure HTML templates
Use t-string to create HTML templates that automatically escape user input:
def html(template: Template) -> str:
parts = []
for item in template:
if isinstance(item, str):
(item)
else: #Interpolation
(html_escape())
return "".join(parts)
user_input = "<script>alert('XSS')</script>"
safe_html = html(t"<div>{user_input}</div>")
# Output: <div><script>alert('XSS')</script></div>
2. Secure SQL query construction
t-string can build anti-injection SQL queries:
def safe_sql(template: Template) -> str:
parts = []
params = []
for item in template:
if isinstance(item, str):
(item)
else:
("?")
()
return "".join(parts), params
user_id = "user' OR 1=1--"
query, params = safe_sql(t"SELECT * FROM users WHERE id = {user_id}")
# query: "SELECT * FROM users WHERE id = ?"
# params: ["user' OR 1=1--"]
3. Structured logs
Use t-string to achieve elegant structured logging:
import json
import logging
from import Template, Interpolation
class TemplateMessage:
def __init__(self, template: Template) -> None:
= template
@property
def message(self) -> str:
# Format as readable message
return f() # Use custom f() function
@property
def values(self) -> dict:
# Extract structured data
return {
:
for item in
}
def __str__(self) -> str:
return f"{} >>> {()}"
action, amount, item = "traded", 42, "shrubs"
(TemplateMessage(t"User {action}: {amount:.2f} {item}"))
# Output: User traded: 42.00 shrubs >>> {"action": "traded", "amount": 42, "item": "shrubs"}
4. Safe child process calls
PEP-787 has been specifically extended to the scenario of t-string in child process calls, so thatsubprocess
The module can natively support t-string:
# Unsafe f-string usage
(f"echo {message_from_user}", shell=True) # Command injection risk
# Safe t-string usage
(t"echo {message_from_user}") # Automatically perform appropriate command escape
This method not only preserves the readability of string commands, but also avoids security risks.
Advanced usage of t-string
1. Custom multi-function template renderer
The real power of t-string is that it can customize renderer templates:
from import Template, Interpolation
import html
def smart_renderer(template: Template, context="text") -> str:
"""Context-aware renderer
Automatically decide how to handle each interpolation according to the context parameter:
- "text": Normal text mode, directly converted to string
- "html": HTML pattern, automatically escape HTML special characters, prevent XSS
- "sql": SQL mode, automatically escapes SQL special characters to prevent injection
"""
parts = []
for item in template:
if isinstance(item, str):
(item)
else: #Interpolation
value =
expression =
# Intelligent processing based on value type and context
if context == "html":
# HTML pattern: Automatically escape HTML special characters
((str(value))))
elif context == "sql":
# SQL Mode: Prevent SQL Injection
if isinstance(value, str):
# Escape 1 single quote into 2
escaped_value = ("'", "''")
(f"'{escaped_value}'")
elif value is None:
("NULL")
else:
(str(value))
else:
(str(value))
return "".join(parts)
# Automatic adaptation rendering of the same template in different contexts
user_input = "<script>alert('evil')</script>"
template = t"User input: {user_input}"
print(smart_renderer(template, context="html")) # Output: User input: <script>alert('evil')</script>
# SQL injection protection example
user_id = "1'; DROP TABLE users; --"
sql_template = t"SELECT * FROM users WHERE id = {user_id}"
print(smart_renderer(sql_template, context="sql")) # Output: SELECT * FROM users WHERE id = '1''; DROP TABLE users; --'
# f-string For SQL injection, the value must be processed first and then f-string is put into
escaped_id = user_id.replace("'", "''")
sql_safe_id = f"'{escaped_id}'"
print(f"SQL query(f-string): SELECT * FROM users WHERE id = {sql_safe_id}")
2. Structured nested template processing
There are essential differences between t-string and f-string when used in nesting:
#Nesting of f-string: internal expressions are evaluated immediately, information is lost
value = "world"
inner_f = f"inner {value}"
outer_f = f"outer {inner_f}"
print(outer_f) # Output: outer inner world
print(type(outer_f)) # <class 'str'> - Just a normal string
# t-string nesting: retain complete structural information
inner_t = t"inner {value}"
outer_t = t"outer {inner_t}"
print(type(outer_t)) # <class ''>
print(type(outer_t.interpolations[0].value)) # is also a Template object!
# Can access and process nested structures at any depth
user = {"name": "Alice", "age": 30}
message = t"User{user['name']} information: {t'Age:{user['age']}'}"
inner_template = [1].value
print(inner_template.strings) # Output: ('Age:', '')
print(inner_template.interpolations[0].value) # Output: 30
This structured processing power makes t-string especially suitable for building complex template systems that can delay or customize all parts of the rendering process on demand.
3. Delay evaluation and asynchronous processing
The structural nature of t-string makes it support delayed evaluation and asynchronous processing. Here is an example of asynchronous template rendering:
import asyncio
# Simulate asynchronous data acquisition
async def fetch_data(key: str) -> str:
await (0.1) # simulate network latency
return f "get {key} data"
async def render_async(template):
tasks = {}
# Start all asynchronous queries in parallel
for item in :
tasks[] = asyncio.create_task(
fetch_data()
)
# Wait for all queries to complete
for expr, task in ():
tasks[expr] = await task
# Assembly results
result = []
for item in template:
if isinstance(item, str):
(item)
else:
(tasks[])
return "".join(result)
async def main():
template = t"User: {user}, Age: {age}"
result = await render_async(template)
print(result)
# (main())
Key advantages of this model:
- Structural retention: You can obtain complete expression information
- Parallel acquisition: Handle multiple asynchronous tasks simultaneously
- Delay combination: Serve all data before splicing
Summarize
Python's t-string syntax is an important extension of string processing capabilities. It provides a more flexible and safer string interpolation processing mechanism while maintaining similarity to f-string syntax. By structuring the string template intoTemplate
Objects, developers can intercept and convert interpolation before string combinations, thus avoiding common security issues and supporting more advanced use cases.
It's like a separation pattern of data and views, f-string is a directly rendered view, while t-string retains the data model, allowing you to perform various transformation rules and validations before final rendering.
The design philosophy of t-string reflects the balance of functionality and security. Although it is more complex than f-string, this complexity brings more advanced composability and greater security.
It follows Python's "explicit over implicit" principle, making every step of string processing clear by explicitly separating the template structure and rendering process.
t-string is not a syntax to replace f-string, and the simplicity and ease of use still have important value.
So, after Python 3.14, how to choose two string interpolation methods?
In a word, use f-string is more direct and efficient when simply formatting a string; and when processing untrusted input, custom rendering logic, building complex template systems, or performing asynchronous processing, you should choose a more powerful t-string.
References
- PEP 750 – Template Strings
- PEP 787 – Safer subprocess usage using t-strings
- Template strings accepted for Python 3.14
Python Trend Weekly Season 3 Summary, with e-book download:/posts/2025-04-20-sweekly
Python Trendy Weekly Season 2 ends (31~60):/posts/2025-04-20-iweekly
Python Trend Weekly Season 2 is over, and I share a few summary:/posts/2024-07-14-iweekly
Python Trendy Weekly Season 2 (31~60) - Pure Link Edition:/posts/2025-04-19-sweekly
Python Fashion Weekly Season 1 Highlights Collection (1~30):/posts/2023-12-11-weekly
Ten Thousand Words Condensation Edition, 800 Links to Python Trend Weekly Season 1! :/post/78c3d645-86fa-4bd8-8eac-46fb192a339e
Follow Python Cat on WeChat:/python_cat.jpg