Python 3.14 introduces a powerful new feature: template strings, formalized in PEP 750. While () 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.
f-strings
f""
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.
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:
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:
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?
import jsonfrom string.templatelib import Template, Interpolationclass 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)
# Dangerous if input is rawf"<div>{user_input}</div>"
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>"))
from typing import Literalfrom string.templatelib import Interpolation, Templatedef 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.438print(render(t"User: {name!r}, Score: {score:.1f}"))
# f-string: this won't combine as reusable componentsf"Stage: " + f"{env}"
from string.templatelib import Interpolationenv = "prod" # Must be defined *before* the t-stringt1 = t"Stage: "t2 = t"{env}"combined = t1 + t2# Render resultprint("".join(str(item.value if isinstance(item, Interpolation) else item) for item in combined))
env = "staging"t = t"{env}"env = "prod" # Changing later has no effectprint(t.interpolations[0].value)
GitHub cut hosted runner prices but planned fees for self-hosted Actions, triggering backlash. The change was paused. Here’s what happened and what to expect.
Learn the key differences between `removesuffix()` and `rstrip()` in Python. Avoid common pitfalls and choose the right method for precise string manipulation.
Explore the top 10 Python trends in 2025, from faster runtimes and type safety to AI, web, data, DevOps, and quantum. Stay ahead in every domain with Python.
Python 3.14 simplifies exception handling with PEP 758, letting you drop parentheses when catching multiple exceptions, cleaner, consistent, and backward-safe.