# Template Strings in Python 3.14: Structured Interpolation
> Python 3.14 adds t"" template strings for structured interpolation, enabling safe rendering, inspection, and transformation before output.

Canonical: https://blog.abhimanyu-saharan.com/posts/template-strings-in-python-3-14-structured-interpolation
Published: 2025-05-20
Last updated: 2025-05-24
Authors: Abhimanyu Saharan
Categories: Python 3.14, Python

Python 3.14 introduces a powerful new feature: **template strings**, formalized in [PEP 750](https://peps.python.org/pep-0750/). 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:

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

```python
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:

```python
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:

```python
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"
```

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

```python
# 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:

```python
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"}
```

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

```python
# Dangerous if input is raw
f"<div>{user_input}</div>"
```

### Solution:

Process interpolated values before rendering:

```python
def safe_html(template: Template) -> str:
    def escape(val):
        return str(val).replace("<", "&lt;").replace(">", "&gt;")
    
    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>&lt;script&gt;alert(1)&lt;/script&gt;</div>
```

> NOTE
> 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`:

```python
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
```

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

```python
# 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:

```python
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:

```python
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

> TIP
> Use `t-strings` when you want to combine templates, inspect values, or transform output.

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

## FAQ

### 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
