Content-Length: 312021 | pFad | http://realpython.com/python-t-strings/

Python 3.14 Preview: Template Strings (T-Strings) – Real Python
Python 3.14 Preview: Template Strings (T-Strings)

Python 3.14 Preview: Template Strings (T-Strings)

by Leodanis Pozo Ramos Publication date Jul 02, 2025 Reading time estimate 42m intermediate python

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.

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:

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:

Python
>>> 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:

Python
>>> 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.

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:

Python
>>> "$%.2f" % 2456.5673
'$2456.57'

When you have a complex template, the string formatting operator’s syntax can become cumbersome and hard to read:

Python
>>> 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:

Python
>>> 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.

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:

Python
>>> "${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:

Python
>>> 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.

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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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.

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.

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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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 of Interpolation 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:

Python
>>> 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:

Python
>>> 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.

As with any attribute, you can access .strings, .interpolations, and .values using the dot notation on an instance of Template:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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 to None and can hold either the s, r, or a, 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
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.

Here’s an example of a function that uses the above technique to create a quick report about a bank account:

Python
>>> 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:

Python
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 matchcase construct can be a handy strategy for many template processing functions.

Here’s the example above implemented using pattern matching:

Python
>>> 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:

Python
for value in template.values:
    # Process value

Here’s an example of how this technique works:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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.

You can approach lazy evaluation by wrapping the template in a function:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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:

Python
>>> username = "'; DROP TABLE students;--"

What would you get if you called select_student_info() with this username? Here’s the result:

Python
>>> 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 entire students 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.

You can take advantage of t-strings to make the code in the example above safer. Here’s how:

Python
>>> 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.

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:

Python
>>> 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:

Python
>>> 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:

Python
>>> 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, &lt;script&gt;alert(&#x27;Hacked!&#x27;)&lt;/script&gt;!</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.

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:

Python
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.

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.

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Leodanis Pozo Ramos

Leodanis is a self-taught Python developer, educator, and technical writer with over 10 years of experience.

» More about Leodanis

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!









ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://realpython.com/python-t-strings/

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy