Template Strings in Python 3.14: Structured Interpolation
Python 3.14 introduces a powerful new feature: template strings, formalized in PEP 750. While f-strings
(f""
) offer convenient inline interpolation, they immediately evaluate to plain strings, making it impossible to extract metadata, control rendering behavior later, or inspect what expressions were interpolated.
Template strings (t""
) solve this by producing a structured object (Template
) rather than a string. This gives developers programmatic access to:
- The raw expression (
{username}
) - Its evaluated value (
'abhimanyu'
) - Its formatting options (
:.2f
,!r
, etc.)
The result: a foundation for building safer, inspectable, and reusable string handling, especially useful for logging, templating systems, web output, and structured pipelines.
Why Not Just Use f-Strings?¶
Many use cases (HTML, shell commands, logs) appear doable with f-strings
. So why switch?
Because t-strings
are:¶
f-strings give you a result. t-strings give you control.
Getting Started¶
Before diving into real-world use cases, here’s how a basic t-string
works:
from string.templatelib import Template
user = "abhimanyu"
template = t"Hello, {user}"
print(template.strings)
# Output: ('Hello, ', '')
print(template.interpolations[0].expression)
# Output: 'user'
print(template.interpolations[0].value)
# Output: 'abhimanyu'
Instead of evaluating directly to "Hello, abhimanyu"
, the template gives you structured access to:
- The string fragments (
"Hello, "
) - The interpolated variable (
user
) - Its current value (
'abhimanyu'
)
Example 1: Sanitize User Input in Shell Commands¶
Problem:¶
When dynamically creating shell commands (e.g., in automation scripts), inserting user input directly using f-strings
can lead to shell injection or break quoting:
command = f'ls "{path}"' # f-string renders immediately—no way to validate `path` later
Solution:¶
Use a t-string
to delay rendering and sanitize input values before final construction:
from string.templatelib import Template, Interpolation
def build_command(template: Template) -> str:
def sanitize(val):
return str(val).replace('"', '\\"') # naive quote escape
return "".join(
sanitize(item.value) if isinstance(item, Interpolation) else item
for item in template
)
command = "ls"
path = "/var/www"
print(build_command(t'{command} "{path}"'))
Output:
ls "/var/www"
With template strings, the values are still separate at the point of rendering, allowing for safe pre-processing.
Example 2: Structured Logging with Embedded Context¶
Problem:¶
Traditional log lines mix human-readable and machine-readable output, making it hard to extract fields for analysis:
# f-string: Cannot extract key-value fields cleanly
logline = f"User {user} did {action}"
Solution:¶
Template strings let you access all interpolated values directly and use them to generate structured logs:
import json
from string.templatelib import Template, Interpolation
class DebugLog:
def __init__(self, template: Template):
self.template = template
def __str__(self):
message = "".join(
str(item.value) if isinstance(item, Interpolation) else item
for item in self.template
)
fields = {
item.expression: item.value
for item in self.template
if isinstance(item, Interpolation)
}
return f"{message} | {json.dumps(fields)}"
action = "deploy"
env = "staging"
version = "v2.3.1"
log = DebugLog(t"Action: {action}, Environment: {env}, Version: {version}")
print(log)
Output:
Action: deploy, Environment: staging, Version: v2.3.1 | {"action": "deploy", "env": "staging", "version": "v2.3.1"}
This makes log parsing, alerting, and dashboarding much simpler, no extra parsing or duplication required.
Example 3: Rendering Safe HTML Content¶
Problem:¶
Displaying user input in HTML (e.g., admin dashboards, reports) risks breaking the layout or worse, injecting malicious content.
# Dangerous if input is raw
f"<div>{user_input}</div>"
Solution:¶
Process interpolated values before rendering:
def safe_html(template: Template) -> str:
def escape(val):
return str(val).replace("<", "<").replace(">", ">")
return "".join(
escape(item.value) if isinstance(item, Interpolation) else item
for item in template
)
user_input = "<script>alert(1)</script>"
print(safe_html(t"<div>{user_input}</div>"))
Output:
<div><script>alert(1)</script></div>
You now have full control over how and when sanitization happens, without altering the input or changing its source.
Example 4: Custom Renderer That Emulates f-Strings¶
Problem:¶
f-strings hide formatting behavior internally. There’s no way to override how conversions like !r
, !s
, or :.2f
behave.
Solution:¶
Build a custom renderer using metadata from each Interpolation
:
from typing import Literal
from string.templatelib import Interpolation, Template
def convert(value, conversion: Literal["a", "r", "s"] | None) -> str:
return {
"a": ascii,
"r": repr,
"s": str,
None: lambda x: x
}[conversion](value)
def render(template: Template) -> str:
parts = []
for item in template:
if isinstance(item, str):
parts.append(item)
elif isinstance(item, Interpolation):
val = convert(item.value, item.conversion)
parts.append(format(val, item.format_spec))
return "".join(parts)
name = "admin"
score = 97.438
print(render(t"User: {name!r}, Score: {score:.1f}"))
Output:
User: 'admin', Score: 97.4
This lets you reimplement
f-string
behavior, or extend it, to suit your formatting logic or domain-specific rules.
Example 5: Combining Templates Dynamically¶
Problem:¶
You may want to build a full template by composing smaller pieces, especially in configuration generators, reusable UI components, or dynamic log builders.
With f-strings
, this is impossible since they render immediately:
# f-string: this won't combine as reusable components
f"Stage: " + f"{env}"
Solution:¶
Template strings allow composition, Template + Template
or Template + str
returns a new Template
object:
from string.templatelib import Interpolation
env = "prod" # Must be defined *before* the t-string
t1 = t"Stage: "
t2 = t"{env}"
combined = t1 + t2
# Render result
print("".join(str(item.value if isinstance(item, Interpolation) else item) for item in combined))
Output:
Stage: prod
⚠️ Important: Interpolations Are Eagerly Evaluated¶
Just like f-strings
, t-strings
evaluate expressions at the moment the string is created, not when you render it:
env = "staging"
t = t"{env}"
env = "prod" # Changing later has no effect
print(t.interpolations[0].value)
Output:
staging
This means:
- Always define the variables before using them inside a
t""
literal - Template strings are not lazy templates; they’re structured snapshots of expressions and their evaluated values
Use
t-strings
when you want to combine templates, inspect values, or transform output.
Don’t expect them to re-evaluate expressions at render time, they capture state once, not on demand.
Final Thoughts¶
Template strings in Python 3.14 are not a replacement for f-strings
, they're an evolution for more advanced, controlled use cases.
Where f-strings
prioritize simplicity and immediate output, t-strings
prioritize structure, safety, and flexibility. They let you inspect expressions, modify rendering behavior, sanitize values on the fly, and reuse templates as objects rather than static text.
If you're working on:
- Logging frameworks that extract field values
- Templating engines where user input must be validated
- DSLs or automation tools that build commands or configs
- Any system where how a string is built matters as much as what it becomes
then t-strings
are the right tool.
Use f-strings
when all you need is a string.
Use t-strings
when you need control before the string is finalized.
FAQs
What are template strings (t"") in Python 3.14, and how do they differ from f-strings?
Template strings (t""
) return a structured Template
object instead of a plain string. Unlike f-strings, which immediately produce a string, t-strings give access to interpolation metadata like variable names, evaluated values, and formatting instructions. This enables introspection, transformation, and safer rendering.
When should I use template strings instead of f-strings?
Use template strings when:
- You need to sanitize or inspect interpolated values
- You're building structured logs, shell commands, or HTML safely
- You want to defer rendering or customize output behavior
- You’re composing strings from smaller parts dynamically
Use f-strings when you only need immediate string output.
Are template strings lazily evaluated?
No. Like f-strings, template strings eagerly evaluate their expressions at the time the string is created. This means all referenced variables must already be defined, and changes to variables after t-string creation won’t affect the template’s stored values.
Can I build custom renderers with template strings?
Yes. You can access individual interpolations (e.g., their values, conversion flags, and formatting specifiers) and build custom rendering logic. This is useful for replicating or extending f-string behavior, such as overriding how !r
or :.2f
is interpreted.
What are practical use cases for template strings in Python 3.14?
- Secure shell command generation (with input sanitization)
- Structured logging (exposing fields as data, not just strings)
- HTML templating with safe escaping
- Custom DSLs or config generators
- Composable string templates for reuse across systems