@@ -34,13 +34,28 @@ printing space-separated values. There are several ways to format output.
3434 >>> f'Results of the {year} {event}'
3535 'Results of the 2016 Referendum'
3636
37+ * When greater control is needed, :ref: `template string literals <tut-t-strings >`
38+ can be useful. T-strings -- which begin with ``t `` or ``T `` -- share the
39+ same syntax as f-strings but, unlike f-strings, produce a
40+ :class: `~string.templatelib.Template ` instance rather than a simple ``str ``.
41+ Templates give you access to the static and interpolated (in curly braces)
42+ parts of the string *before * they are combined into a final string.
43+
44+ ::
45+
46+ >>> name = "World"
47+ >>> template = t"Hello {name}!"
48+ >>> template.strings
49+ ('Hello ', '!')
50+ >>> template.values
51+ ('World',)
52+
3753* The :meth: `str.format ` method of strings requires more manual
3854 effort. You'll still use ``{ `` and ``} `` to mark where a variable
3955 will be substituted and can provide detailed formatting directives,
4056 but you'll also need to provide the information to be formatted. In the following code
4157 block there are two examples of how to format variables:
4258
43-
4459 ::
4560
4661 >>> yes_votes = 42_572_654
@@ -95,10 +110,11 @@ Some examples::
95110 >>> repr((x, y, ('spam', 'eggs')))
96111 "(32.5, 40000, ('spam', 'eggs'))"
97112
98- The :mod: `string ` module contains a :class: `~string.Template ` class that offers
99- yet another way to substitute values into strings, using placeholders like
100- ``$x `` and replacing them with values from a dictionary, but offers much less
101- control of the formatting.
113+ The :mod: `string ` module also contains support for so-called
114+ :ref: `$-strings <template-strings-pep292 >` that offer yet another way to
115+ substitute values into strings, using placeholders like ``$x `` and replacing
116+ them with values from a dictionary. This syntax is easy to use, although
117+ it offers much less control of the formatting.
102118
103119.. index ::
104120 single: formatted string literal
@@ -160,6 +176,186 @@ See :ref:`self-documenting expressions <bpo-36817-whatsnew>` for more informatio
160176on the ``= `` specifier. For a reference on these format specifications, see
161177the reference guide for the :ref: `formatspec `.
162178
179+
180+ .. _tut-t-strings :
181+
182+ Template String Literals
183+ -------------------------
184+
185+ :ref: `Template string literals <t-strings >` (also called t-strings for short)
186+ are an extension of :ref: `f-strings <tut-f-strings >` that let you access the
187+ static and interpolated parts of a string *before * they are combined into a
188+ final string. This provides for greater control over how the string is
189+ formatted.
190+
191+ The most common way to create a :class: `~string.templatelib.Template ` instance
192+ is to use the :ref: `t-string literal syntax <t-strings >`. This syntax is
193+ identical to that of :ref: `f-strings ` except that it uses a ``t `` instead of
194+ an ``f ``:
195+
196+ >>> name = " World"
197+ >>> template = t" Hello {name} !"
198+ >>> template.strings
199+ ('Hello ', '!')
200+ >>> template.values
201+ ('World',)
202+
203+ :class: `~!string.templatelib.Template ` instances are iterable, yielding each
204+ string and :class: `~string.templatelib.Interpolation ` in order:
205+
206+ .. testsetup ::
207+
208+ name = "World"
209+ template = t"Hello {name}!"
210+
211+ .. doctest ::
212+
213+ >>> list (template)
214+ ['Hello ', Interpolation('World', 'name', None, ''), '!']
215+
216+ Interpolations represent expressions inside a t-string. They contain the
217+ evaluated value of the expression (``'World' `` in this example), the text of
218+ the original expression (``'name' ``), and optional conversion and format
219+ specification attributes.
220+
221+ Templates can be processed in a variety of ways. For instance, here's code that
222+ converts static strings to lowercase and interpolated values to uppercase:
223+
224+ >>> from string.templatelib import Template
225+ >>>
226+ >>> def lower_upper (template : Template) -> str :
227+ ... return ' ' .join(
228+ ... part.lower() if isinstance (part, str ) else part.value.upper()
229+ ... for part in template
230+ ... )
231+ ...
232+ >>> name = " World"
233+ >>> template = t" Hello {name} !"
234+ >>> lower_upper(template)
235+ 'hello WORLD!'
236+
237+ Template strings are particularly useful for sanitizing user input. Imagine
238+ we're building a web application that has user profile pages. Perhaps the
239+ ``User `` class is defined like this:
240+
241+ >>> from dataclasses import dataclass
242+ >>>
243+ >>> @ dataclass
244+ ... class User :
245+ ... name: str
246+ ...
247+
248+ Imagine using f-strings in to generate HTML for the ``User ``:
249+
250+ .. testsetup ::
251+
252+ class User:
253+ name: str
254+ def __init__(self, name: str):
255+ self.name = name
256+
257+
258+ .. doctest ::
259+
260+ >>> # Warning: this is dangerous code. Don't do this!
261+ >>> def user_html (user : User) -> str :
262+ ... return f " <div><h1> { user.name} </h1></div> "
263+ ...
264+
265+ This code is dangerous because our website lets users type in their own names.
266+ If a user types in a name like ``"<script>alert('evil');</script>" ``, the
267+ browser will execute that script when someone else visits their profile page.
268+ This is called a *cross-site scripting (XSS) vulnerability *, and it is a form
269+ of *injection vulnerability *. Injection vulnerabilities occur when user input
270+ is included in a program without proper sanitization, allowing malicious code
271+ to be executed. The same sorts of vulnerabilities can occur when user input is
272+ included in SQL queries, command lines, or other contexts where the input is
273+ interpreted as code.
274+
275+ To prevent this, instead of using f-strings, we can use t-strings. Let's
276+ update our ``user_html() `` function to return a :class: `~string.templatelib.Template `:
277+
278+ >>> from string.templatelib import Template
279+ >>>
280+ >>> def user_html (user : User) -> Template:
281+ ... return t" <div><h1>{user.name} </h1></div>"
282+
283+ Now let's implement a function that sanitizes *any * HTML
284+ :class: `~!string.templatelib.Template `:
285+
286+ >>> from html import escape
287+ >>> from string.templatelib import Template
288+ >>>
289+ >>> def sanitize_html_template (template : Template) -> str :
290+ ... return ' ' .join(
291+ ... part if isinstance (part, str ) else escape(part.value)
292+ ... for part in template
293+ ... )
294+ ...
295+
296+ This function iterates over the parts of the
297+ :class: `~!string.templatelib.Template `, escaping any interpolated values using
298+ the :func: `html.escape ` function, which converts special characters like `< `,
299+ `> `, and `& ` into their HTML-safe equivalents.
300+
301+ Now we can tie it all together:
302+
303+ .. testsetup ::
304+
305+ from dataclasses import dataclass
306+ from string.templatelib import Template
307+ from html import escape
308+ @dataclass
309+ class User:
310+ name: str
311+ def sanitize_html_template(template: Template) -> str:
312+ return ''.join(
313+ part if isinstance(part, str) else escape(part.value)
314+ for part in template
315+ )
316+ def user_html(user: User) -> Template:
317+ return t"<div><h1>{user.name}</h1></div>"
318+
319+ .. doctest ::
320+
321+ >>> evil_user = User(name = " <script>alert('evil');</script>" )
322+ >>> template = user_html(evil_user)
323+ >>> safe = sanitize_html_template(template)
324+ >>> print (safe)
325+ <div><h1><script>alert('evil');</script></h1></div>
326+
327+ We are no longer vulnerable to XSS attacks because we are escaping the
328+ interpolated values before they are included in the rendered HTML.
329+
330+ Of course, there's no need for code that processes
331+ :class: `~!string.templatelib.Template ` instances to be limited to returning a
332+ simple string. For instance, we could imagine defining a more complex ``html() ``
333+ function that returns a structured representation of the HTML:
334+
335+ >>> from dataclasses import dataclass
336+ >>> from string.templatelib import Template
337+ >>> from html.parser import HTMLParser
338+ >>>
339+ >>> @ dataclass
340+ ... class Element :
341+ ... tag: str
342+ ... attributes: dict[str , str ]
343+ ... children: list[str | Element]
344+ ...
345+ >>> def parse_html (template : Template) -> Element:
346+ ... """
347+ ... Uses python' s built-in HTMLParser to parse the template,
348+ ... handle any interpolated values, and return a tree of
349+ ... Element instances.
350+ ... """
351+ ... pass
352+ ...
353+
354+ A full implementation of this function would be quite complex and is not
355+ provided here. That said, the fact that it is possible to implement a method
356+ like ``parse_html() `` showcases the flexibility and power of t-strings.
357+
358+
163359.. _tut-string-format :
164360
165361The String format() Method
0 commit comments