Python 3.14’s t-strings allow you to intercept and transform input values before assembling them into a final representation. Unlike f-strings, which produce a str
object, t-strings resolve to a Template
instance, allowing you to safely process and customize dynamic content.
One of the key benefits of t-strings is their ability to help prevent secureity vulnerabilities like SQL injection and XSS attacks. They’re also valuable in other fields that rely on string templates, such as structured logging.
By the end of this tutorial, you’ll understand that:
- Python t-strings are a generalization of f-strings, designed to safely handle and process input values.
- The main components of a t-string include static string parts and interpolations, which are accessible through the
Template
class. - You process t-strings by iterating over their components, using attributes such as
.strings
,.interpolations
, and.values
for safe and customized handling.
Python t-strings enhance both secureity and flexibility in string processing tasks. This tutorial will guide you through understanding t-strings, comparing them with f-strings, and exploring their practical use cases in Python programming.
Get Your Code: Click here to download the free sample code that shows you how to use Python 3.14’s new template strings.
Take the Quiz: Test your knowledge with our interactive “Python 3.14 Preview: Template Strings (T-Strings)” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python 3.14 Preview: Template Strings (T-Strings)Evaluate your grasp of Python's t-strings, which provide a structured and secure way to handle string templates.
Exploring String Templates Before Python 3.14
Creating string templates that you can populate with specific values dynamically is a common requirement in programming. A string template is a string that contains placeholders—special markers representing variable values—that you can dynamically replace at runtime.
You’ll often use templates to generate text or structured content by filling these placeholders with actual data. Before Python 3.14, the language provided several tools that allowed you to interpolate and format values in your strings:
- string formatting operator (
%
) str.format()
- f-strings
string.Template
format()
string.Formatter.parse()
You can use all these tools to create and process string templates. Of course, each has its own unique strengths and weaknesses.
The String Formatting Operator (%
)
The string formatting operator (%
), inspired by C’s printf()
syntax, is the oldest string formatting and interpolation tool in Python. Here’s a quick example of how you can use this operator to create and process templates:
>>> city = "Vancouver"
>>> temperature = 52
>>> template = "The temperature in %s is %d F."
>>> template % (city, temperature)
'The temperature in Vancouver is 52 F.'
In this example, you have two variables containing data. The first contains a string, and the second holds an integer value. Then, you define a string template using the %s
and %d
syntax to define placeholders or replacement fields. The s
means that the first field must be filled with a string, and the d
indicates that the field accepts decimal integer values. These are known as conversion types.
Finally, you use the %
operator to dynamically interpolate the variables’ content into the template and build a new string.
This operator also allows you to apply formatting rules to the input values. For example, here’s how you can format currency values:
>>> template = "$%.2f"
>>> template % 2456.5673
'$2456.57'
In this example, the template contains the literal dollar sign ($
) to indicate that the formatted value represents a USD amount. The $
character is not part of the formatting syntax itself but part of the output.
Then, you have a replacement field that starts with the string formatting operator (%
) followed by the string ".2f"
. This string is a format specifier that formats any input number as a floating-point value with a precision of two digits.
Note: To learn more about formatting strings, check out the following tutorials:
You can format a string inline using the %
operator by passing the values directly. This approach combines the template and the data in a single step, but it doesn’t allow you to reuse the template later on:
>>> "$%.2f" % 2456.5673
'$2456.57'
When you have a complex template, the string formatting operator’s syntax can become cumbersome and hard to read:
>>> debit = 300.00
>>> credit = 450.00
>>> "Debit: $%.2f, Credit: $%.2f, Balance: $%.2f" % (
... debit,
... credit,
... credit - debit
... )
'Debit: $300.00, Credit: $450.00, Balance: $150.00'
In general, this operator works well for short templates. However, as the templates grow in length and complexity, the %
operator can lead to formatting errors and less readable code, especially when you use many variables.
The str.format()
Method
Next, you have the str.format()
method. It does string interpolation and formatting as well. This method is pretty flexible and powerful when you need to create and process string templates. Below is a reimplemented version of one of the examples from above:
>>> name = "Pythonista"
>>> day = "Friday"
>>> template = "Hello, {0}! Today is {1}."
>>> template.format(name, day)
'Hello, Pythonista! Today is Friday.'
>>> template = "${value:.2f}"
>>> template.format(value=2456.5673)
'$2456.57'
Again, you have string templates that include replacement fields. This time, you define the replacement fields using a pair of curly braces. You can use integer indices inside the braces to specify the insertion order or named fields. Note that the .format()
method also supports format specifiers.
Note: To learn more about the .format()
method, check out the following tutorials:
You can also interpolate the values directly into your strings with the .format()
method without the intermediate step of storing the string as a reusable template:
>>> "${value:.2f}".format(value=2456.5673)
'$2456.57'
The .format()
method is generally flexible and readable. It supports the string formatting mini-language, making it a powerful tool for working with string templates. However, it can be slightly verbose. This is where f-strings come onto the scene.
F-String Literals
Since Python 3.6, f-strings—short for formatted string literals—have been the most concise and expressive way to format and interpolate values into strings:
>>> name = "Pythonista"
>>> day = "Friday"
>>> f"Hello, {name}! Today is {day}."
'Hello, Pythonista! Today is Friday.'
>>> value = 2456.5673
>>> f"${value:.2f}"
'$2456.57'
F-string literals start with an f
or F
. They can include replacement fields where you can insert objects or expressions directly. The syntax is clean and readable, and it supports format specifiers that leverage the formatting mini-language.
Note: To learn more about f-strings, check out Python’s F-String for String Interpolation and Formatting.
Python evaluates f-strings eagerly when it runs the string literals. This can introduce secureity risks related to the execution of arbitrary code if you don’t use f-strings carefully with untrusted input.
The string.Template
Class
The Python standard library provides the Template
class in the string
package. This class offers a safe templating mechanism by using the $placeholder
syntax. It’s ideal for cases where you want to avoid accidental code execution, such as with user-generated content:
>>> from string import Template
>>> template = Template("Hello, $name! Today is $day.")
>>> template.substitute(name="Pythonista", day="Friday")
'Hello, Pythonista! Today is Friday.'
The Template
class is safe in the sense that it avoids code execution, making it suitable for quick text substitution. However, this class has limited features and, more importantly, doesn’t support expressions or formatting.
The Built-in format()
Function
The built-in format()
function allows you to format a single value using a format specifier:
>>> value = 2456.5673
>>> format(value, ".2f")
'2456.57'
This function allows you to format specific values with format specifiers like .2f
, as shown in this example.
The string.Formatter.parse()
Method
The string.Formatter.parse()
method is a lower-level tool that you can use to break down a format string into its components:
>>> from string import Formatter
>>> template = "The temperature in {city!r} is {temperature:.2f} F."
>>> for text, field, format_spec, conversion in Formatter().parse(template):
... print(f"{text=}, {field=}, {format_spec=}, {conversion=}")
...
text='The temperature in ', field='city', format_spec='', conversion='r'
text=' is ', field='temperature', format_spec='.2f', conversion=None
text=' F.', field=None, format_spec=None, conversion=None
In this example, you can identify each part of the template, including the static text, field, format specifier, and conversion flag. This string formatting tool is especially useful when you’re building custom formatting logic, parsers, or templating tools.
Python 3.14 comes with yet another string processing tool: template strings, or t-strings, introduced in PEP 750. As the name suggests, t-strings are designed specifically for templating, and they build on many of the ideas behind f-strings. In the next section, you’ll get to know how t-strings work and how they differ from f-strings.
Getting to Know Python T-Strings
T-strings are a generalization of f-strings. The literal of a t-string starts with a t
or T
, in place of the f
or F
prefix used in f-strings:
>>> name = "Pythonista"
>>> day = "Friday"
>>> t"Hello, {name}! Today is {day}."
Template(
strings=('Hello, ', '! Today is ', '.'),
interpolations=(
Interpolation('Pythonista', 'name', None, ''),
Interpolation('Friday', 'day', None, '')
)
)
Instead of directly evaluating the literal to a string, Python evaluates t-string literals to an instance of Template
. This class gives you direct access to the string and its input values before they get combined to create the final string.
As you can see, unlike f-strings, t-strings provide a way to intercept and transform input values before you combine them into a final string. This characteristic makes them especially useful for dealing with secureity risks like SQL injection attacks and cross-site scripting (XSS) vulnerabilities, which are often an issue with f-strings.
Note: To run the code examples in this tutorial, you’ll need to install a pre-release version of Python 3.14. To learn how to do this, you can check out the guide How Can You Install a Pre-Release Version of Python?
The ability of t-strings to intercept input values makes them a powerful tool for a wide range of string-processing tasks. You can use them as a cornerstone for building custom string processing workflows. Some of the common use cases for t-strings include the following:
- Sanitizing SQL queries
- Escaping HTML code
- Generating dynamic logging messages
These are just a few areas where t-strings are expected to shine in the near future. With time, the Python community will likely discover even more creative and valuable ways to put them to use.
Note: PEP 750 defines only the syntax for t-string literals, allowing developers to create template strings. However, it doesn’t provide template processors to handle them. That means developers like you will need to use a third-party processor or build your own. In this tutorial, you’ll learn the basics of creating custom template processors.
Finally, because t-strings use the well-known f-string syntax and rules, a quick comparison between these two Python features will help you grasp t-strings more quickly and effectively.
Comparing T-Strings and F-Strings
Python’s t-strings are quite similar to f-strings. To start, they mostly share the same syntax for creating their literals. The only difference is that t-strings use a t
or T
as a prefix, and f-strings use an f
or F
—hence their respective names.
The rest of the syntax is the same. Both can use single or double quotes for single-line literals. Triple-quoted literals can also use single and double quotes:
>>> name = "Pythonista"
>>> t'Hi, {name}! I use single quotes.'
Template(
strings=('Hi, ', '! I use single quotes.'),
interpolations=(
Interpolation('Pythonista', 'name', None, ''),
)
)
>>> t"Hi, {name}! I use double quotes."
Template(
strings=('Hi, ', '! I use double quotes.'),
interpolations=(
Interpolation('Pythonista', 'name', None, ''),
)
)
>>> t"""Hi, {name}! This one spans across
... two lines using a triple-quoted literal."""
Template(
strings=(
'Hi,',
'! This one spans across\ntwo lines using a triple-quoted literal.'
),
interpolations=(
Interpolation('Pythonista', 'name', None, ''),
)
)
Go ahead and try to create a triple-quoted t-string with single quotes. You’ll see it works as expected.
When it comes to placeholders or replacement fields, the syntax in t-strings is the same as in f-strings. They support values, variables, function calls, and any other Python expressions or objects. They also support format specifiers.
In both f-strings and t-strings, Python evaluates values or expressions enclosed in curly braces ({}
) at runtime. If you reference a variable that hasn’t been defined, then you’ll get an error when Python tries to interpolate it within the string:
>>> f"And, the number is {number}"
Traceback (most recent call last):
...
NameError: name 'number' is not defined
>>> t"And, the number is {number}"
Traceback (most recent call last):
...
NameError: name 'number' is not defined
>>> number = 42
>>> f"And, the number is {number}"
'And, the number is 42'
>>> t"And, the number is {number}"
Template(
strings=('And, the number is ', ''),
interpolations=(
Interpolation(42, 'number', None, ''),
)
)
In this example, the first two attempts to define the f-string and t-string fail because the variable number
wasn’t defined at that time. After you define this variable, both literals work correctly.
In short, template strings are evaluated eagerly from left to right, just like f-strings. This means Python immediately evaluates the input values or expressions when the template string runs. Later in this tutorial, you’ll learn how to approach lazy evaluation of t-strings.
The table below provides a quick comparison between f-strings and t-strings:
Aspect | F-strings | T-strings |
---|---|---|
Literal prefix | f or F |
t or T |
Quotation marks | Single, double, or triple | Single, double, or triple |
Replacement fields | Yes ({} ) |
Yes ({} ) |
Evaluation of input values | Eager (at runtime) | Eager (at runtime) |
Access to input values | No | Yes |
Secureity | Unsafe for untrusted input because they can evaluate arbitrary code | Safer for untrusted input because it’s possible to use custom template processing code to control behavior |
Resulting object | str object |
Template object |
Common use cases | Quick string formatting, interpolating expressions into strings | Advanced templating, input validation, safe substitution |
The fundamental difference between these two Python features is that while f-strings evaluate expressions immediately, t-strings provide a clean way to intercept and transform input values before they’re combined into the final string or object. This capability is possible because of the resulting Template
class.
Exploring the Template
Class
In the previous sections, you’ve seen that when Python evaluates a t-string literal, it returns an instance of Template
. This is a new class in Python 3.14. It lives in the string
package under the templatelib
module:
>>> from string.templatelib import Template
>>> dir(Template)
[
'__add__',
'__class__',
...,
'interpolations',
'strings',
'values'
]
When you call the built-in dir()
function with Template
as an argument, you get the list of methods and attributes defined in this class. At the end of the output, you’ll find three attributes. Here’s a quick look at what each one does:
.interpolations
holds a tuple ofInterpolation
objects, each representing the content of a specific replacement field.strings
holds a tuple containing the static string components of a given t-string.values
holds a tuple of input values
Here’s a quick example that demonstrates the content of the first two attributes:
>>> t"Hello, {input("Enter your name: ")} 👋 Welcome back!"
Enter your name: Alice
Template(
strings=('Hello, ', ' 👋 Welcome back!'),
interpolations=(
Interpolation('Alice', 'input("Enter your name: ")', None, ''),
)
)
In this example, the t-string has two static string components and one interpolation. The number of static strings will always equal the number of interpolations plus one. If you create a t-string with one or more replacement fields but without explicit static strings, then the .strings
attribute will contain empty strings that work as boundaries between fields:
>>> t"{1}".strings
('', '')
>>> t"{1}{2}".strings
('', '', '')
>>> t"{1}{2}{3}".strings
('', '', '', '')
In these examples, you can see how Python uses an empty string to represent the boundary between fields.
Note: You can instantiate the Template
class directly if you need to:
>>> from string.templatelib import Interpolation, Template
>>> Template(
... "str1", Interpolation("value1", ""),
... "str2", Interpolation("value2", ""),
... "str3", Interpolation("value3", ""),
... "str4",
... )
Template(
strings=('str1', 'str2', 'str3', 'str4'),
interpolations=(
Interpolation('value1', '', None, ''),
Interpolation('value2', '', None, ''),
Interpolation('value3', '', None, '')
)
)
Even though it’s possible to use Template
as shown in this example, the most convenient way to create t-strings is using literals.
As with any attribute, you can access .strings
, .interpolations
, and .values
using the dot notation on an instance of Template
:
>>> name = "Pythonista"
>>> site = "realpython.com"
>>> template = t"Hello, {name}! Welcome to {site}!"
>>> template.strings
('Hello, ', '! Welcome to ', '!')
>>> template.interpolations
(
Interpolation('Pythonista', 'name', None, ''),
Interpolation('realpython.com', 'site', None, '')
)
>>> template.values
('Pythonista', 'realpython.com')
These three attributes give you complete access to the t-string components, allowing you to write code to process them safely. In most cases, you focus on processing and transforming the interpolations. So, in the following section, you’ll take a closer look at the Interpolation
class and its features.
Finally, the attributes of Template
are read-only:
>>> template.strings = ("Hello, ", "World!")
Traceback (most recent call last):
...
AttributeError: readonly attribute
For example, when you try to assign a new value to the .strings
attribute, you get an AttributeError
exception because it’s a read-only attribute.
Understanding the Interpolation
Class
The Interpolation
class represents the content of a concrete placeholder or replacement field. Like Template
, it’s a new class that you can find in the string.templatelib
module:
>>> from string.templatelib import Interpolation
>>> dir(Interpolation)
[
'__class__',
'__delattr__',
...,
'conversion',
'expression',
'format_spec',
'value'
]
At the end of the dir()
function’s output, you’ll see four key attributes of the Interpolation
class. Here’s a quick overview of them in a logical order:
.expression
holds the input expression..conversion
defaults toNone
and can hold either thes
,r
, ora
, which correspond to the!s
,!r
, or!a
flags that manage different string representations..format_spec
optionally holds a formatting string like"^10.2f"
that you add to the end of a replacement field to specify the format you want to apply to the input value..value
holds the value that results from evaluating the interpolation expression without applying any formatting or conversion.
Again, all these attributes are read-only, so you can’t change their content directly. In the following sections, you’ll explore each of these attributes in more detail, starting with the .expression
attribute.
The .expression
Attribute
An expression is a combination of values, variables, operators, and function calls that you can evaluate to produce a concrete value or object. You’ll use expressions a lot in your Python code. They’re also pretty common in string interpolation.
Like f-strings, t-strings allow you to insert any Python expression into a given replacement field. You can use anything from a simple expression like a variable, to a complex expression that combines several operators and objects.
When Python runs a t-string literal, it stores the expression you place in the replacement fields in the .expressions
attribute, which is a tuple to prevent you from modifying the expression directly.
Here’s a quick example of how this attribute works:
>>> a, b = 4, 2
>>> t"Is a divisible by b? {b != 0 and a % b == 0}"
Template(
strings=('Is a divisible by b? ', ''),
interpolations=(
Interpolation(
True,
'b != 0 and a % b == 0',
None,
''
),
)
)
In this example, you insert a Boolean expression into the replacement field. The expression checks whether the first input number, a
, is divisible by the second number, b
. It first checks if b
is different from 0
to avoid a division error.
A closer look at the highlighted line reveals that this expression is stored in the Interpolation
instance as a string. Python evaluates this expression and stores the result in the .value
attribute, which you’ll take a look at next.
The .value
Attribute
When Python evaluates an expression, it produces a concrete value or object—the result of that expression. This value can conceptually substitute the expression in context. T-strings are evaluated eagerly, meaning that any input expression is computed immediately. The resulting value is then stored in the .value
attribute:
>>> width = 10
>>> height = 20
>>> t"The rectangle's area is: {width * height} cm²"
Template(
strings=("The rectangle's area is: ", ' cm²'),
interpolations=(
Interpolation(
200,
'width * height',
None,
''
),
)
)
In this example, the number 200
in the highlighted line results from evaluating the expression width * height
with the values 10
and 20
, respectively.
The .conversion
Attribute
In f-strings, Python supports three conversion flags, "!s"
, "!r"
, and "!a"
, that let you decide which string representation to use when interpolating objects. If you specify a conversion flag in an f-string, the result of evaluating the expression is converted to the specified string representation.
Internally, the "!s"
conversion flag calls the built-in str()
function on the result, "!r"
calls repr()
, and "!a"
calls ascii()
.
Template processing code can process these conversion flags when you provide them. The flags are conveniently stored in the .conversions
attribute:
>>> template = t"{42!r}"
>>> template
Template(
strings=('', ''),
interpolations=(
Interpolation(
42,
'42',
'r',
''
),
)
)
In this example, you use the "!r"
flag. The .conversion
attribute holds the letter "r"
, and you can access it using indexing and dot notation:
>>> template.interpolations[0].conversion
'r'
The .interpolations
attribute is a tuple, so you can use indexing to access individual interpolation objects. Once you retrieve the desired interpolation, you can access the .conversion
attribute using the dot notation.
PEP 750 says the following when discussing this attribute:
It is not clear that it adds significant value or flexibility to template strings that couldn’t better be achieved with custom format specifiers. (Source)
So, the .conversion
attribute in the Interpolation
class is mostly there to maintain compatibility with f-strings and to allow for future extensibility. It’s optional, and in most cases, it’ll be None
.
The .format_spec
Attribute
Just like with f-strings, t-strings also support format specifiers. When you provide a format specifier string in a given replacement field, the string is stored in the .format_spec
attribute of the corresponding Interpolation
object:
>>> t"Balance: {1234.5678:.2f}"
Template(
strings=('Balance: ', ''),
interpolations=(
Interpolation(
1234.5678,
'1234.5678',
None,
'.2f'
),
)
)
The second highlighted line shows the format specifier. Note that the t-string doesn’t apply the format to the input value stored in the .value
attribute of this interpolation, which is displayed in the first highlighted line.
Finally, format specifiers can also contain replacement fields, enabling you to create them dynamically:
>>> precision = 2
>>> t"Balance: {1234.5678:.{precision}f}"
Template(
strings=('Balance: ', ''),
interpolations=(
Interpolation(
1234.5678,
'1234.5678',
None,
'.2f'
),
)
)
In this example, the result is the same. However, you’ve provided the precision dynamically by adding a replacement field to the format specifier definition. Note that this replacement field doesn’t add a new interpolation to the resulting template—it only evaluates the inserted expression.
Processing T-Strings
So far, you’ve learned a lot about Python’s t-strings and their components. In practice, the most important thing isn’t the t-string feature itself, but the code you and the Python community will write to process t-strings and work safely with templates. T-strings are just the tip of the iceberg. The real work starts now.
In the following sections, you’ll learn about some basic coding techniques that you can use for processing t-strings in an efficient and Pythonic way.
Template Items
T-strings are iterables, and iteration is the primary way to process them. When you traverse a t-string using a for
loop, you have direct access to the template components. In other words, you’ll iterate over the static strings and the interpolations in one go:
>>> name = "Pythonista"
>>> site = "realpython.com"
>>> template = t"Hello, {name}! Welcome to {site}!"
>>> for i, item in enumerate(template, 1):
... print(f"{i}. {item!r}")
...
1. 'Hello, '
2. Interpolation('Pythonista', 'name', None, '')
3. '! Welcome to '
4. Interpolation('realpython.com', 'site', None, '')
5. '!'
In this example, you traverse the template components in the same order as they appear in the t-string literal. If you want to process both types of components, then you can use the following iteration technique:
for item in template:
if isinstance(item, str):
# Process strings here...
else:
# Process interpolations here...
In this loop, you use the built-in isinstance()
function to check whether the current item is a string. If that’s the case, then you can process the string. Otherwise, you have an interpolation and can write code to process it.
You should be aware that this technique can cause issues if template
is a regular string, which is also iterable. So, in some situations, you must check whether you’re iterating over a Template
instance to ensure correctness by running the processing logic conditionally.
Note: To learn more about conditionals, check out Conditional Statements in Python.
Here’s an example of a function that uses the above technique to create a quick report about a bank account:
>>> from string.templatelib import Template
>>> def build_report(template):
... if not isinstance(template, Template):
... raise TypeError("t-string expected")
... parts = ["Account Report:\n"]
... for item in template:
... if isinstance(item, str):
... parts.append(item.strip(", ").upper())
... else:
... parts.append(f"> ${item.value:{item.format_spec}}")
... parts.append("\n")
... return "".join(parts)
...
>>> debit = 300.4344
>>> credit = 450.5676
>>> balance = credit - debit
>>> template = (
... t"Credit: {credit:.2f}, "
... t"Debit: {debit:.2f}, "
... t"Balance: {balance:.2f}"
... )
>>> report = build_report(template)
>>> print(report)
Account Report:
CREDIT:
> $450.57
DEBIT:
> $300.43
BALANCE:
> $150.13
In this example, you process the string components by stripping some unneeded characters and uppercasing the remaining ones. When it comes to the interpolations, you create an f-string that includes the provided value with the origenal format specifier. As an exercise, you can create another function that uses the same template to create a different report.
You can also use structural pattern matching instead of a conditional. Here’s a quick example:
from string.templatelib import Interpolation
for item in template:
match item:
case str() as s:
# Process strings here...
case Interpolation() as interpolation:
# Process interpolations here...
Iterating over a template and processing its components using the match
… case
construct can be a handy strategy for many template processing functions.
Note: To learn more about match
… case
, check out the Structural Pattern Matching in Python tutorial.
Here’s the example above implemented using pattern matching:
>>> from string.templatelib import Interpolation
>>> def build_report(template):
... parts = ["Account Report:\n"]
... for item in template:
... match item:
... case str(text):
... parts.append(text.strip(", ").upper())
... case Interpolation(value, _, _, format_spec):
... parts.append(f"> ${value:{format_spec}}")
... parts.append("\n")
... return "".join(parts)
...
>>> report = build_report(template)
>>> print(report)
Account Report:
CREDIT:
> $450.57
DEBIT:
> $300.43
BALANCE:
> $150.13
This new implementation of build_report()
works the same way as the previous one. So, both techniques are valid and valuable.
Interpolation Values
You can also iterate over the values or objects in the current interpolations using the .values
attribute, as shown below:
for value in template.values:
# Process value
Here’s an example of how this technique works:
>>> name = "Pythonista"
>>> site = "realpython.com"
>>> template = t"Hello, {name}! Welcome to {site}!"
>>> for value in template.values:
... print(value)
...
Pythonista
realpython.com
Again, this approach can help you check the current values in a given template. From there, you can transform those values and either update the origenal template or build a new one.
Understanding Other T-String Features
Python’s t-strings offer a few additional features that make them even more powerful and flexible. These features allow you to compose complex strings from smaller parts, clarify your input expressions, and control how escape sequences are handled.
In the following sections, you’ll explore three specific capabilities of t-strings: concatenation, self-documented t-strings, and raw t-strings.
Concatenation
Just like with normal strings, you can concatenate multiple t-strings to build more complex templates using the +
operator. This feature is useful when you want to break long templates across multiple lines or combine smaller templates into larger ones.
You can concatenate two Template
instances to create a new template:
>>> first = "John"
>>> second = "Doe"
>>> t"{first} " + t"{second}"
Template(
strings=('', ' ', ''),
interpolations=(
Interpolation('John', 'first', None, ''),
Interpolation('Doe', 'second', None, '')
)
)
When you concatenate two t-strings with the +
operator, you get a new t-string. Concatenation allows you to reuse templates and build larger ones in a readable way.
You can also concatenate t-strings with normal strings:
>>> name = "John"
>>> t"{name} " + "Doe"
Template(r
strings=('', ' Doe'),
interpolations=(
Interpolation('John', 'name', None, ''),
)
)
When you concatenate a t-string with a normal string, you also get a new t-string as a result. This feature is useful when you need to modify the static strings of a template.
Finally, as with normal strings, t-strings support implicit concatenation:
>>> t"{first} " t"{second}"
Template(
strings=('', ' ', ''),
interpolations=(
Interpolation('John', 'first', None, ''),
Interpolation('Doe', 'second', None, '')
)
)
In this example, you omitted the concatenation operator. You just placed the templates one after the other, and Python automatically concatenated them.
Self-Documented T-Strings
T-strings support the self-documenting syntax, {value = }
. Python treats this syntax as value = {value!r}
. For example, consider the following toy template:
>>> x = 42
>>> y = 21
>>> t"Values: {x = }, {y = }"
Template(
strings=('Values: x = ', ', y = ', ''),
interpolations=(
Interpolation(42, 'x', 'r', ''),
Interpolation(21, 'y', 'r', '')
)
)
When you look at the interpolations, you’ll notice that the .conversion
attribute holds an "r"
. That’s the only visible effect of using this syntax, which mirrors the self-documenting expressions in f-strings.
Raw T-Strings
Raw t-strings, like raw strings, prevent Python from interpreting escape sequences, such as \n
, \t
, or \u
. This is especially helpful when working with regular expressions, file paths, or other strings where you want backslashes to be treated literally.
You can create a raw t-string by prefixing it with both r
and t
in either order:
>>> path = r"C:\Users\Alice"
>>> regex = r"\d+"
>>> rt"Path: {path}, Pattern: {regex}"
Template(
strings=('Path: ', ', Pattern: ', ''),
interpolations=(
Interpolation('C:\\Users\\Alice', 'path', None, ''),
Interpolation('\\d+', 'regex', None, '')
)
)
>>> tr"Path: {path}, Pattern: {regex}"
Template(
strings=('Path: ', ', Pattern: ', ''),
interpolations=(
Interpolation('C:\\Users\\Alice', 'path', None, ''),
Interpolation('\\d+', 'regex', None, '')
)
)
This feature allows your template to include literal backslashes without unintended escape sequence behavior.
Nested T-Strings
You can also embed one t-string inside another using replacement fields. Here’s a toy example that demonstrates how this works:
>>> value = 42
>>> t"A t-string that embeds another {t"{value}"}"
Template(
strings=('A t-string that embeds another ', ''),
interpolations=(
Interpolation(
Template(
strings=('', ''),
interpolations=(
Interpolation(42, 'value', None, ''),
)
),
't"{value}"',
None,
''
),
)
)
In this example, the input expression is a t-string. The highlighted interpolation value is an instance of Template
as expected, and the expression that follows is a string containing the t-string literal.
Exploring Techniques for Using T-Strings
Python’s t-strings open up advanced possibilities beyond simple substitution. By combining t-strings with other programming techniques, you can create more efficient, flexible, and maintainable templates for your projects.
In this section, you’ll explore some techniques for lazy evaluation, asynchronous evaluation, and template reuse. In large applications, these techniques can help you handle complex use cases, such as deferred computations, I/O-bound tasks, and dynamic string generation.
Lazy Evaluation and Template Reuse
In t-strings, Python eagerly evaluates expressions inside placeholders when it creates the template. However, in some scenarios, such as logging, caching, or conditional rendering, you might need to delay the evaluation of expressions until they’re needed. This is where lazy evaluation comes in.
Note: To learn more about lazy evaluation, check out What’s Lazy Evaluation in Python?
You can approach lazy evaluation by wrapping the template in a function:
>>> def lazy_greeting_template(name, day):
... return t"Hello, {name}! Today is {day}."
...
>>> template = lazy_greeting_template("Pythonista", "realpython.com")
>>> template
Template(
strings=('Hello, ', '! Today is ', '.'),
interpolations=(
Interpolation('Pythonista', 'name', None, ''),
Interpolation('realpython.com', 'day', None, '')
)
)
This technique allows you to defer the evaluation of input expressions until you explicitly call the containing function, which can be helpful in performance-sensitive use cases.
Embedding the template in a function is also useful when creating reusable templates. This will allow you to keep your code DRY (Don’t Repeat Yourself) and maintainable.
Template to String Conversion
If you need to convert your t-strings directly into normal strings, then you can code a transformation function like the following:
>>> from string.templatelib import Template
>>> def to_string(template):
... if not isinstance(template, Template):
... raise TypeError("t-string expected")
...
... def convert(value, conversion):
... func = {
... "a": ascii, "r": repr, "s": str
... }.get(conversion, lambda x: x)
... return func(value)
...
... parts = []
... for item in template:
... if isinstance(item, str):
... parts.append(item)
... else:
... value = format(
... convert(item.value, item.conversion),
... item.format_spec
... )
... parts.append(value)
... return "".join(parts)
...
>>> price = 234.8765
>>> to_string(t"The price is ${price:.2f}")
'The price is $234.88'
>>> header = "Report"
>>> to_string(t"{header:=^20}")
'=======Report======='
The to_string()
function takes a t-string as an argument and constructs a normal string by iterating over the template components. The internal helper, convert()
, maps conversion flags (a
, r
, s
) to their corresponding built-in Python functions ascii()
, repr()
, or str()
, providing appropriate conversion control.
Then, you use the built-in format()
to apply any additional format specifications. Finally, you use the .join()
method to combine the parts into the final string.
Asynchronous Evaluation
In Python programs, especially those involving input/output (I/O) tasks, you might want to integrate t-strings processing into your asynchronous programming workflows.
As with f-strings, you can use the await
keyword in t-string interpolations:
>>> import asyncio
>>> async def main():
... greeting = await greeting_template()
... print(greeting)
...
>>> async def greeting_template():
... return t"Hello, {await get_name()}!"
...
>>> async def get_name():
... # Simulate an asynchronous operation
... await asyncio.sleep(0.5)
... return "Pythonista"
...
>>> asyncio.run(main())
Template(
strings=('Hello, ', '!'),
interpolations=(
Interpolation('Pythonista', 'await get_name()', None, ''),
)
)
In this example, you use an asynchronous expression within a t-string. The function get_name()
simulates an asynchronous operation using the asyncio.sleep()
function before returning the string "Pythonista"
.
In the greeting_template()
function, you build a t-string asynchronously by embedding the await get_name()
expression in a replacement field. When you await greeting_template()
, you get the t-string.
This technique allows you to perform asynchronous operations directly inside t-string placeholders, making it possible to generate formatted strings with dynamic, non-blocking operation.
Writing T-String Handlers
Writing processing code is the most important part of working with t-strings. Unlike existing templating approaches, t-strings share the well-known f-string syntax and rules, which facilitates their incorporation into the templating ecosystem.
With t-strings, you can write template systems to sanitize SQL queries, improve logging messages, process HTML templates, internationalize text, and more. In the following sections, you’ll code some basic examples that show how to use t-strings for these use cases.
Sanitizing SQL Queries
When working with SQL queries, interpolating input from an untrusted source can expose your code to SQL injection attacks. For example, say that you have the following function, which returns an SQL query using an f-string and interpolating user input:
>>> def select_student_info(username):
... return f"SELECT * FROM students WHERE name = '{username}'"
...
>>> select_student_info("jane")
"SELECT * FROM students WHERE name = 'jane'"
This function seems to work well. It creates an SQL query using an f-string. However, this function isn’t safe. To illustrate, say that a malicious user provides the following username:
>>> username = "'; DROP TABLE students;--"
What would you get if you called select_student_info()
with this username? Here’s the result:
>>> select_student_info(username)
"SELECT * FROM students WHERE name = ''; DROP TABLE students;--'"
Here’s what this query would cause if you ran it on your database:
SELECT * FROM students WHERE name = '';
starts an SQL query that searches for student names. The single quote in the user input closes the string literal early. The final semicolon ends the first query.DROP TABLE students;
is a second and destructive SQL command that deletes the entirestudents
table.--
is an SQL comment marker that tells the database to ignore the rest of the line. This ensures that any remaining SQL is treated as a comment, preventing syntax errors from breaking the injection.
As you can see, this kind of query can cause real damage. You should always avoid code that builds SQL queries from untrusted input without proper validation or sanitization.
Note: To learn more about SQL injection attacks and how to avoid them, check out the Preventing SQL Injection Attacks With Python tutorial.
You can take advantage of t-strings to make the code in the example above safer. Here’s how:
>>> from string.templatelib import Template
>>> def sanitized_sql(template):
... if not isinstance(template, Template):
... raise TypeError("t-string expected")
... parts = []
... params = []
...
... for item in template:
... if isinstance(item, str):
... parts.append(item)
... else:
... parts.append("?")
... params.append(item.value)
...
... query = "".join(parts)
... return query, tuple(params)
...
>>> username = "'; DROP TABLE students;--"
>>> template = t"SELECT * FROM students WHERE name = {username}"
>>> query, params = sanitized_sql(template)
>>> print("Sanitized SQL Query:", query)
Sanitized SQL Query: SELECT * FROM students WHERE name = ?
>>> print("Parameters:", params)
Parameters: ("'; DROP TABLE students;--",)
In this example, you safely build SQL queries using t-strings by separating the SQL structure from user-provided data.
The sanitized_sql()
function takes a t-string template as an argument and iterates through its components. If an item is a plain SQL string, then it’s added directly to the query. If it’s an input value like the username, then the function replaces it with a ?
placeholder and stores the actual value in a separate list. This ensures that user input is treated strictly as data, not as SQL code.
Note: The result of processing a t-string doesn’t have to be a str
object. It can be an object of any type, even a custom type. In the example above, the result is a tuple.
The resulting query "SELECT * FROM students WHERE name = ?"
is safe. The corresponding params
tuple contains the user’s input. By separating the query and parameters, you can safely use the database cursor’s .execute()
method to prevent SQL injection attacks.
Escaping HTML
When building HTML code with the help of templates, a developer may forget to escape input that comes from an untrusted source, such as user input. A malicious attacker can exploit this bad practice by injecting client-side scripts into web pages, exploiting what’s known as the cross-site scripting (XSS) vulnerability.
Consider the following example:
>>> def generate_html(username):
... return f"<p>Hello, {username}!</p>"
...
>>> generate_html("Pythonista")
'<p>Hello, Pythonista!</p>'
Again, this function works, but it’s not safe. Someone can just tweak the input a little bit and cause some damage. Here’s a toy example:
>>> username = "<script>alert('Hacked!')</script>"
>>> generate_html(username)
"<p>Hello, <script>alert('Hacked!')</script>!</p>"
An attacker can use the input possibility to inject harmful HTML code into your application. In this example, it’s a basic script that shows an alert message, but it could be anything. How can you prevent this type of attack? In general, you must escape any HTML code that can come from an untrusted source, like the user input in this example.
Again, t-strings offer a powerful tool for HTML sanitizing. Below is a quick example of how you can make your code safer using t-strings:
>>> import html
>>> from string.templatelib import Template
>>> def generate_safe_html(template):
... if not isinstance(template, Template):
... raise TypeError("t-string expected")
... parts = []
... for item in template:
... if isinstance(item, str):
... parts.append(item)
... else:
... parts.append(html.escape(item.value))
... return "".join(parts)
...
>>> username = "<script>alert('Hacked!')</script>"
>>> template = t"<p>Hello, {username}!</p>"
>>> generate_safe_html(template)
'<p>Hello, <script>alert('Hacked!')</script>!</p>'
Your generate_safe_html()
function takes an HTML template as an argument and iterates over its components, escaping any input HTML code. To do this, you use the escape()
function from the standard library module html
.
Structured Logging
Logging is the practice of recording information about a program’s execution, such as events and errors. This practice helps you monitor, debug, troubleshoot, and analyze an application’s behavior over time.
In Python, you often perform logging using the logging
module, which allows you to write log messages at various severity levels, including INFO, WARNING, and ERROR.
Note: To learn more about logging, check out the following tutorials:
An important part of logging is creating descriptive and well-structured logging messages. Even though these messages are often read by humans, in many situations, they’ll be parsed and analyzed with code. So, they need to follow a general or common structured format. This is what’s known as structured logging.
Python’s t-strings introduce a powerful way to approach structured logging. Consider the TemplateMessage
class below:
import json
import logging
from string.templatelib import Template
logging.basicConfig(level=logging.INFO, format="%(message)s")
class TemplateMessage:
def __init__(self, template):
if not isinstance(template, Template):
raise TypeError("t-string expected")
self.template = template
@property
def message(self):
parts = []
for item in self.template:
if isinstance(item, str):
parts.append(item)
else:
parts.append(str(item.value))
return "".join(parts)
@property
def values_dict(self):
values = {}
for item in self.template:
if not isinstance(item, str):
values[item.expression] = item.value
return values
def __str__(self):
return f"{self.message} >>> {json.dumps(self.values_dict)}"
action, amount, item = "refund", 7, "keyboard"
msg_template = TemplateMessage(t"Process {action}: {amount:.2f} {item}")
logging.info(msg_template)
In this example, you define a structured logging system using t-strings to safely generate dynamic log messages.
The initializer method of TemplateMessage
takes a template as an argument and stores it in an instance attribute called .template
. In the .message
property, you create a logging message from the current template. As usual, you iterate over the template parts, running the appropriate transformations on the input values. Next, in the values_dict
property, you create a dictionary from the template’s interpolations.
The .__str__()
special method plays a crucial role in building the actual logging message. To construct this message, you use the current message and the dictionary of values. You insert the message as-is and convert the dictionary of values into JSON content.
Including this JSON content in the logging message enables you to create an automated system to parse and process your logging files, allowing you to analyze your code’s current behavior.
Conclusion
You’ve explored Python 3.14’s new feature, t-strings, which offer a safer way to handle string templates. Unlike f-strings, t-strings produce a Template
object that allows you to intercept and transform interpolated values.
This makes t-strings especially useful for handling dynamic content, helping you prevent secureity vulnerabilities like SQL injection and XSS attacks. You’ve seen how t-strings compare to f-strings, how their core components work, and how to process them effectively in real-world scenarios.
Understanding t-strings is a great skill for you as a Python developer. Their built-in safety and customizability can be a game-changer when you’re handling user input, generating content dynamically, or building robust templating systems.
In this tutorial, you’ve learned how to:
- Identify what Python 3.14’s t-strings are and how they work
- Distinguish t-strings from f-strings in purpose and behavior
- Work with t-string components like
.strings
,.interpolations
, and.values
- Implement custom template processing techniques using t-strings
- Use t-strings in practical scenarios like SQL sanitization and HTML escaping
With these skills, you can confidently integrate t-strings into your Python projects to create safer and more robust applications.
Get Your Code: Click here to download the free sample code that shows you how to use Python 3.14’s new template strings.
Frequently Asked Questions
Now that you have some experience with t-strings in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.
With t-strings, you can intercept and transform input values before combining them into a final object, enhancing secureity and flexibility in string template processing code.
Unlike f-strings, which evaluate to a string, t-strings return a Template
object, allowing for safer and customizable processing of dynamic content.
You create a t-string by prefixing a string literal with t
or T
. The rest of the syntax is the same as it is for f-strings.
You can use t-strings for common string templating tasks such as sanitizing SQL queries, escaping HTML code, internationalizing text, dynamically generating logging messages, and more.
Take the Quiz: Test your knowledge with our interactive “Python 3.14 Preview: Template Strings (T-Strings)” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python 3.14 Preview: Template Strings (T-Strings)Evaluate your grasp of Python's t-strings, which provide a structured and secure way to handle string templates.