theme | paginate | marp |
---|---|---|
default |
true |
true |
-
Overview of new
t-strings
feature in Python 3.14+ -
Deck links to code snippets in
snippets/
directory
-
t"Hello {name}"
→ returns aTemplate
object -
Separates literal parts and evaluated expressions
-
Enables inspection, transformation, sanitization before rendering
-
Interception & Transformation Sanitize or escape interpolations to prevent injection
-
Separation of Concerns Cache static literal text; recompute only dynamic values
-
Flexible Processing Render to HTML, AST, JSON, DSL outputs
-
Eager but Inspectable Eager f-string evaluation + introspection metadata
-
t-strings produce a Template object that retains
-
the literal slices (Template.strings)
-
the raw values (Template.values)
Use uv
to run interactive mode
in a ephemeral virtual environment
with Python beta version - 3.14.0b1:
$ uv run --python 3.14.0b1 -- python
from string.templatelib import Template, Interpolation
from html import escape
def html(template: Template) -> str:
parts = []
for part in template:
if isinstance(part, str):
parts.append(part)
else:
value = escape(str(part.value))
parts.append(value)
return "".join(parts)
evil = "<script>alert('xss')</script>"
template = t"<p>{evil}</p>"
assert html(template) == "<p><script>alert('xss')</script></p>"
import json, logging
from string.templatelib import Template, Interpolation
def format_log(template: Template) -> tuple[str, dict]:
msg = []
data = {}
for part in template:
if isinstance(part, str):
msg.append(part)
else:
data[part.expression] = part.value
msg.append(str(part.value))
return "".join(msg), data
logger = logging.getLogger()
action, amount, item = "traded", 42, "shrubs"
template = t"User {action}: {amount:.2f} {item}"
message, payload = format_log(template)
# message → "User traded: 42.00 shrubs"
# payload → {"action":"traded","amount":42,"item":"shrubs"}
from string.templatelib import Template, Interpolation
def make_greeting(name: str) -> Template:
return t"Hello, {name}!"
def convert(value: object, conversion: Literal["a", "r", "s"] | None)
-> object:
"""Convert the value to a string using the specified conversion."""
# Python has no convert() built-in function, so we have to implement it.
if conversion == "a":
return ascii(value)
if conversion == "r":
return repr(value)
if conversion == "s":
return str(value)
return value
def f(template: Template) -> str:
"""Implement f-string behavior using the PEP 750 t-string behavior."""
parts = []
for item in template:
match item:
case str() as s:
parts.append(s)
case Interpolation(value, _, conversion, format_spec):
value = convert(value, conversion)
value = format(value, format_spec)
parts.append(value)
return "".join(parts)
# Reuse with different names
for person in ["Alice", "Bob", "Carol"]:
tmpl = make_greeting(person)
# Render via f-string behavior
print(f(tmpl)) # “Hello, Alice!”, then “Hello, Bob!”, etc.
Overridden eq and hash for Template and Interpolation
- Identity left to user code to avoid caching confusion
Making Template and Interpolation Into Protocols
- Concrete classes are simpler; protocols add complexity
An Additional Decoded Type
- Over-engineered; raw vs cooked text handled via templates
-
More examples: https://github.com/davepeck/pep750-examples/tree/main/pep
-
HN debate about the PEP: https://news.ycombinator.com/item?id=43748512
to complete presentation with demo
since this is on a not released yet python version (3.14) the first option to use would be godbolt.org (compiler explorer) but for now, is not possible
other simple alternatives are:
devcontainer.json
replit.nix
)both alternatives are remote micro-VM or container back-ends / remote sandbox model
alternatives in the spectrum of browser-native sandboxes compiled to WebAssembly like:
are not ready since former doesn't have a template nowadays ready for 3.14, need to download cpython package and use wasm tools to build python.wasm file and inject into its environment, and the latter is just releasing compatibility with 3.13 and they are recently exploring 3.14, so not ready any time soon.