CINXE.COM
PEP 750 – Template Strings | peps.python.org
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="color-scheme" content="light dark"> <title>PEP 750 – Template Strings | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0750/"> <link rel="stylesheet" href="../_static/style.css" type="text/css"> <link rel="stylesheet" href="../_static/mq.css" type="text/css"> <link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light"> <link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark"> <link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss"> <meta property="og:title" content='PEP 750 – Template Strings | peps.python.org'> <meta property="og:description" content="This PEP introduces template strings for custom string processing."> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0750/"> <meta property="og:site_name" content="Python Enhancement Proposals (PEPs)"> <meta property="og:image" content="https://peps.python.org/_static/og-image.png"> <meta property="og:image:alt" content="Python PEPs"> <meta property="og:image:width" content="200"> <meta property="og:image:height" content="200"> <meta name="description" content="This PEP introduces template strings for custom string processing."> <meta name="theme-color" content="#3776ab"> </head> <body> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all"> <title>Following system colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="9"></circle> <path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path> </svg> </symbol> <symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all"> <title>Selected dark colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path> </svg> </symbol> <symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all"> <title>Selected light colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> </symbol> </svg> <script> document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto" </script> <section id="pep-page-section"> <header> <h1>Python Enhancement Proposals</h1> <ul class="breadcrumbs"> <li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> » </li> <li><a href="../pep-0000/">PEP Index</a> » </li> <li>PEP 750</li> </ul> <button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())"> <svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg> <span class="visually-hidden">Toggle light / dark / auto colour theme</span> </button> </header> <article> <section id="pep-content"> <h1 class="page-title">PEP 750 – Template Strings</h1> <dl class="rfc2822 field-list simple"> <dt class="field-odd">Author<span class="colon">:</span></dt> <dd class="field-odd">Jim Baker <jim.baker at python.org>, Guido van Rossum <guido at python.org>, Paul Everitt <pauleveritt at me.com>, Koudai Aono <koxudaxi at gmail.com>, Lysandros Nikolaou <lisandrosnik at gmail.com>, Dave Peck <davepeck at davepeck.org></dd> <dt class="field-even">Discussions-To<span class="colon">:</span></dt> <dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/pep-750-tag-strings-for-writing-domain-specific-languages/60408">Discourse thread</a></dd> <dt class="field-odd">Status<span class="colon">:</span></dt> <dd class="field-odd"><abbr title="Proposal under active discussion and revision">Draft</abbr></dd> <dt class="field-even">Type<span class="colon">:</span></dt> <dd class="field-even"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd> <dt class="field-odd">Created<span class="colon">:</span></dt> <dd class="field-odd">08-Jul-2024</dd> <dt class="field-even">Python-Version<span class="colon">:</span></dt> <dd class="field-even">3.14</dd> <dt class="field-odd">Post-History<span class="colon">:</span></dt> <dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/60408" title="Discourse thread">09-Aug-2024</a>, <a class="reference external" href="https://discuss.python.org/t/60408/201" title="Discourse message">17-Oct-2024</a>, <a class="reference external" href="https://discuss.python.org/t/60408/226" title="Discourse message">21-Oct-2024</a></dd> </dl> <hr class="docutils" /> <section id="contents"> <details><summary>Table of Contents</summary><ul class="simple"> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#relationship-with-other-peps">Relationship With Other PEPs</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#template-string-literals">Template String Literals</a></li> <li><a class="reference internal" href="#the-template-type">The <code class="docutils literal notranslate"><span class="pre">Template</span></code> Type</a></li> <li><a class="reference internal" href="#the-interpolation-type">The <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> Type</a></li> <li><a class="reference internal" href="#processing-template-strings">Processing Template Strings</a></li> <li><a class="reference internal" href="#template-string-concatenation">Template String Concatenation</a></li> <li><a class="reference internal" href="#template-and-interpolation-equality">Template and Interpolation Equality</a></li> <li><a class="reference internal" href="#no-support-for-ordering">No Support for Ordering</a></li> <li><a class="reference internal" href="#support-for-the-debug-specifier">Support for the debug specifier (<code class="docutils literal notranslate"><span class="pre">=</span></code>)</a></li> <li><a class="reference internal" href="#raw-template-strings">Raw Template Strings</a></li> <li><a class="reference internal" href="#interpolation-expression-evaluation">Interpolation Expression Evaluation</a></li> <li><a class="reference internal" href="#exceptions">Exceptions</a></li> <li><a class="reference internal" href="#interleaving-of-template-args">Interleaving of <code class="docutils literal notranslate"><span class="pre">Template.args</span></code></a></li> </ul> </li> <li><a class="reference internal" href="#examples">Examples</a><ul> <li><a class="reference internal" href="#example-implementing-f-strings-with-t-strings">Example: Implementing f-strings with t-strings</a></li> <li><a class="reference internal" href="#example-structured-logging">Example: Structured Logging</a><ul> <li><a class="reference internal" href="#approach-1-custom-log-messages">Approach 1: Custom Log Messages</a></li> <li><a class="reference internal" href="#approach-2-custom-formatters">Approach 2: Custom Formatters</a></li> </ul> </li> <li><a class="reference internal" href="#example-html-templating">Example: HTML Templating</a></li> </ul> </li> <li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li> <li><a class="reference internal" href="#security-implications">Security Implications</a></li> <li><a class="reference internal" href="#how-to-teach-this">How To Teach This</a></li> <li><a class="reference internal" href="#common-patterns-seen-in-processing-templates">Common Patterns Seen in Processing Templates</a><ul> <li><a class="reference internal" href="#structural-pattern-matching">Structural Pattern Matching</a></li> <li><a class="reference internal" href="#memoizing">Memoizing</a></li> <li><a class="reference internal" href="#parsing-to-intermediate-representations">Parsing to Intermediate Representations</a></li> <li><a class="reference internal" href="#context-sensitive-processing-of-interpolations">Context-sensitive Processing of Interpolations</a></li> <li><a class="reference internal" href="#nested-template-strings">Nested Template Strings</a></li> <li><a class="reference internal" href="#approaches-to-lazy-evaluation">Approaches to Lazy Evaluation</a></li> <li><a class="reference internal" href="#approaches-to-asynchronous-evaluation">Approaches to Asynchronous Evaluation</a></li> <li><a class="reference internal" href="#approaches-to-template-reuse">Approaches to Template Reuse</a></li> </ul> </li> <li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li> <li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul> <li><a class="reference internal" href="#arbitrary-string-literal-prefixes">Arbitrary String Literal Prefixes</a></li> <li><a class="reference internal" href="#delayed-evaluation-of-interpolations">Delayed Evaluation of Interpolations</a></li> <li><a class="reference internal" href="#making-template-and-interpolation-into-protocols">Making <code class="docutils literal notranslate"><span class="pre">Template</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> Into Protocols</a></li> <li><a class="reference internal" href="#an-additional-decoded-type">An Additional <code class="docutils literal notranslate"><span class="pre">Decoded</span></code> Type</a></li> <li><a class="reference internal" href="#other-homes-for-template-and-interpolation">Other Homes for <code class="docutils literal notranslate"><span class="pre">Template</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code></a></li> <li><a class="reference internal" href="#enable-full-reconstruction-of-original-template-literal">Enable Full Reconstruction of Original Template Literal</a></li> <li><a class="reference internal" href="#disallowing-string-concatenation">Disallowing String Concatenation</a></li> <li><a class="reference internal" href="#arbitrary-conversion-values">Arbitrary Conversion Values</a></li> <li><a class="reference internal" href="#removing-conv-from-interpolation">Removing <code class="docutils literal notranslate"><span class="pre">conv</span></code> From <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code></a></li> <li><a class="reference internal" href="#alternate-interpolation-symbols">Alternate Interpolation Symbols</a></li> <li><a class="reference internal" href="#a-lazy-conversion-specifier">A Lazy Conversion Specifier</a></li> <li><a class="reference internal" href="#alternate-layouts-for-template-args">Alternate Layouts for <code class="docutils literal notranslate"><span class="pre">Template.args</span></code></a></li> <li><a class="reference internal" href="#mechanism-to-describe-the-kind-of-template">Mechanism to Describe the “Kind” of Template</a></li> </ul> </li> <li><a class="reference internal" href="#acknowledgements">Acknowledgements</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> </details></section> <section id="abstract"> <h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2> <p>This PEP introduces template strings for custom string processing.</p> <p>Template strings are a generalization of f-strings, using a <code class="docutils literal notranslate"><span class="pre">t</span></code> in place of the <code class="docutils literal notranslate"><span class="pre">f</span></code> prefix. Instead of evaluating to <code class="docutils literal notranslate"><span class="pre">str</span></code>, t-strings evaluate to a new type, <code class="docutils literal notranslate"><span class="pre">Template</span></code>:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">template</span><span class="p">:</span> <span class="n">Template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{name}</span><span class="s2">"</span> </pre></div> </div> <p>Templates provide developers with access to the string and its interpolated values <em>before</em> they are combined. This brings native flexible string processing to the Python language and enables safety checks, web templating, domain-specific languages, and more.</p> </section> <section id="relationship-with-other-peps"> <h2><a class="toc-backref" href="#relationship-with-other-peps" role="doc-backlink">Relationship With Other PEPs</a></h2> <p>Python introduced f-strings in Python 3.6 with <a class="pep reference internal" href="../pep-0498/" title="PEP 498 – Literal String Interpolation">PEP 498</a>. The grammar was then formalized in <a class="pep reference internal" href="../pep-0701/" title="PEP 701 – Syntactic formalization of f-strings">PEP 701</a> which also lifted some restrictions. This PEP is based on PEP 701.</p> <p>At nearly the same time PEP 498 arrived, <a class="pep reference internal" href="../pep-0501/" title="PEP 501 – General purpose template literal strings">PEP 501</a> was written to provide “i-strings” – that is, “interpolation template strings”. The PEP was deferred pending further experience with f-strings. Work on this PEP was resumed by a different author in March 2023, introducing “t-strings” as template literal strings, and built atop PEP 701.</p> <p>The authors of this PEP consider it to be a generalization and simplification of the updated work in PEP 501. (That PEP has also recently been updated to reflect the new ideas in this PEP.)</p> </section> <section id="motivation"> <h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2> <p>Python f-strings are easy to use and very popular. Over time, however, developers have encountered limitations that make them <a class="reference external" href="https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.html.format_html">unsuitable for certain use cases</a>. In particular, f-strings provide no way to intercept and transform interpolated values before they are combined into a final string.</p> <p>As a result, incautious use of f-strings can lead to security vulnerabilities. For example, a user executing a SQL query with <a class="reference external" href="https://docs.python.org/3/library/sqlite3.html#module-sqlite3" title="(in Python v3.13)"><code class="docutils literal notranslate"><span class="pre">sqlite3</span></code></a> may be tempted to use an f-string to embed values into their SQL expression, which could lead to a <a class="reference external" href="https://en.wikipedia.org/wiki/SQL_injection">SQL injection attack</a>. Or, a developer building HTML may include unescaped user input in the string, leading to a <a class="reference external" href="https://en.wikipedia.org/wiki/Cross-site_scripting">cross-site scripting (XSS)</a> vulnerability.</p> <p>More broadly, the inability to transform interpolated values before they are combined into a final string limits the utility of f-strings in more complex string processing tasks.</p> <p>Template strings address these problems by providing developers with access to the string and its interpolated values.</p> <p>For example, imagine we want to generate some HTML. Using template strings, we can define an <code class="docutils literal notranslate"><span class="pre">html()</span></code> function that allows us to automatically sanitize content:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">evil</span> <span class="o">=</span> <span class="s2">"<script>alert('evil')</script>"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"<p></span><span class="si">{evil}</span><span class="s2"></p>"</span> <span class="k">assert</span> <span class="n">html</span><span class="p">(</span><span class="n">template</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"<p>&lt;script&gt;alert('evil')&lt;/script&gt;</p>"</span> </pre></div> </div> <p>Likewise, our hypothetical <code class="docutils literal notranslate"><span class="pre">html()</span></code> function can make it easy for developers to add attributes to HTML elements using a dictionary:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">attributes</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"src"</span><span class="p">:</span> <span class="s2">"shrubbery.jpg"</span><span class="p">,</span> <span class="s2">"alt"</span><span class="p">:</span> <span class="s2">"looks nice"</span><span class="p">}</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"<img </span><span class="si">{attributes}</span><span class="s2"> />"</span> <span class="k">assert</span> <span class="n">html</span><span class="p">(</span><span class="n">template</span><span class="p">)</span> <span class="o">==</span> <span class="s1">'<img src="shrubbery.jpg" alt="looks nice" />'</span> </pre></div> </div> <p>Neither of these examples is possible with f-strings. By providing a mechanism to intercept and transform interpolated values, template strings enable a wide range of string processing use cases.</p> </section> <section id="specification"> <h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2> <section id="template-string-literals"> <h3><a class="toc-backref" href="#template-string-literals" role="doc-backlink">Template String Literals</a></h3> <p>This PEP introduces a new string prefix, <code class="docutils literal notranslate"><span class="pre">t</span></code>, to define template string literals. These literals resolve to a new type, <code class="docutils literal notranslate"><span class="pre">Template</span></code>, found in a new top-level standard library module, <code class="docutils literal notranslate"><span class="pre">templatelib</span></code>.</p> <p>The following code creates a <code class="docutils literal notranslate"><span class="pre">Template</span></code> instance:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">templatelib</span> <span class="kn">import</span> <span class="n">Template</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"This is a template string."</span> <span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">template</span><span class="p">,</span> <span class="n">Template</span><span class="p">)</span> </pre></div> </div> <p>Template string literals support the full syntax of <a class="pep reference internal" href="../pep-0701/" title="PEP 701 – Syntactic formalization of f-strings">PEP 701</a>. This includes the ability to nest template strings within interpolations, as well as the ability to use all valid quote marks (<code class="docutils literal notranslate"><span class="pre">'</span></code>, <code class="docutils literal notranslate"><span class="pre">"</span></code>, <code class="docutils literal notranslate"><span class="pre">'''</span></code>, and <code class="docutils literal notranslate"><span class="pre">"""</span></code>). Like other string prefixes, the <code class="docutils literal notranslate"><span class="pre">t</span></code> prefix must immediately precede the quote. Like f-strings, both lowercase <code class="docutils literal notranslate"><span class="pre">t</span></code> and uppercase <code class="docutils literal notranslate"><span class="pre">T</span></code> prefixes are supported. Like f-strings, t-strings may not be combined with the <code class="docutils literal notranslate"><span class="pre">b</span></code> or <code class="docutils literal notranslate"><span class="pre">u</span></code> prefixes. Additionally, f-strings and t-strings cannot be combined, so the <code class="docutils literal notranslate"><span class="pre">ft</span></code> prefix is invalid as well. t-strings <em>may</em> be combined with the <code class="docutils literal notranslate"><span class="pre">r</span></code> prefix; see the <a class="reference internal" href="#raw-template-strings">Raw Template Strings</a> section below for more information.</p> </section> <section id="the-template-type"> <h3><a class="toc-backref" href="#the-template-type" role="doc-backlink">The <code class="docutils literal notranslate"><span class="pre">Template</span></code> Type</a></h3> <p>Template strings evaluate to an instance of a new type, <code class="docutils literal notranslate"><span class="pre">templatelib.Template</span></code>:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Template</span><span class="p">:</span> <span class="n">args</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="n">Interpolation</span><span class="p">]</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="n">Interpolation</span><span class="p">):</span> <span class="o">...</span> </pre></div> </div> <p>The <code class="docutils literal notranslate"><span class="pre">args</span></code> attribute provides access to the string parts and any interpolations in the literal:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{name}</span><span class="s2">"</span> <span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nb">str</span><span class="p">)</span> <span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">Interpolation</span><span class="p">)</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"Hello "</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">value</span> <span class="o">==</span> <span class="s2">"World"</span> </pre></div> </div> <p>See <a class="reference internal" href="#interleaving-of-template-args">Interleaving of Template.args</a> below for more information on how the <code class="docutils literal notranslate"><span class="pre">args</span></code> attribute is structured.</p> <p>The <code class="docutils literal notranslate"><span class="pre">Template</span></code> type is immutable. <code class="docutils literal notranslate"><span class="pre">Template.args</span></code> cannot be reassigned or mutated.</p> </section> <section id="the-interpolation-type"> <h3><a class="toc-backref" href="#the-interpolation-type" role="doc-backlink">The <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> Type</a></h3> <p>The <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> type represents an expression inside a template string. Like <code class="docutils literal notranslate"><span class="pre">Template</span></code>, it is a new concrete type found in the <code class="docutils literal notranslate"><span class="pre">templatelib</span></code> module:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Interpolation</span><span class="p">:</span> <span class="n">value</span><span class="p">:</span> <span class="nb">object</span> <span class="n">expr</span><span class="p">:</span> <span class="nb">str</span> <span class="n">conv</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">"a"</span><span class="p">,</span> <span class="s2">"r"</span><span class="p">,</span> <span class="s2">"s"</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="n">format_spec</span><span class="p">:</span> <span class="nb">str</span> <span class="n">__match_args__</span> <span class="o">=</span> <span class="p">(</span><span class="s2">"value"</span><span class="p">,</span> <span class="s2">"expr"</span><span class="p">,</span> <span class="s2">"conv"</span><span class="p">,</span> <span class="s2">"format_spec"</span><span class="p">)</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span> <span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">,</span> <span class="n">expr</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">conv</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">"a"</span><span class="p">,</span> <span class="s2">"r"</span><span class="p">,</span> <span class="s2">"s"</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">format_spec</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">""</span><span class="p">,</span> <span class="p">):</span> <span class="o">...</span> </pre></div> </div> <p>Like <code class="docutils literal notranslate"><span class="pre">Template</span></code>, <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> is shallow immutable. Its attributes cannot be reassigned.</p> <p>The <code class="docutils literal notranslate"><span class="pre">value</span></code> attribute is the evaluated result of the interpolation:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{name}</span><span class="s2">"</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">value</span> <span class="o">==</span> <span class="s2">"World"</span> </pre></div> </div> <p>The <code class="docutils literal notranslate"><span class="pre">expr</span></code> attribute is the <em>original text</em> of the interpolation:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{name}</span><span class="s2">"</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">expr</span> <span class="o">==</span> <span class="s2">"name"</span> </pre></div> </div> <p>We expect that the <code class="docutils literal notranslate"><span class="pre">expr</span></code> attribute will not be used in most template processing code. It is provided for completeness and for use in debugging and introspection. See both the <a class="reference internal" href="#common-patterns-seen-in-processing-templates">Common Patterns Seen in Processing Templates</a> section and the <a class="reference internal" href="#examples">Examples</a> section for more information on how to process template strings.</p> <p>The <code class="docutils literal notranslate"><span class="pre">conv</span></code> attribute is the <a class="reference external" href="https://docs.python.org/3/library/string.html#formatstrings" title="(in Python v3.13)"><span class="xref std std-ref">optional conversion</span></a> to be used, one of <code class="docutils literal notranslate"><span class="pre">r</span></code>, <code class="docutils literal notranslate"><span class="pre">s</span></code>, and <code class="docutils literal notranslate"><span class="pre">a</span></code>, corresponding to <code class="docutils literal notranslate"><span class="pre">repr()</span></code>, <code class="docutils literal notranslate"><span class="pre">str()</span></code>, and <code class="docutils literal notranslate"><span class="pre">ascii()</span></code> conversions. As with f-strings, no other conversions are supported:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{name!r}</span><span class="s2">"</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">conv</span> <span class="o">==</span> <span class="s2">"r"</span> </pre></div> </div> <p>If no conversion is provided, <code class="docutils literal notranslate"><span class="pre">conv</span></code> is <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p> <p>The <code class="docutils literal notranslate"><span class="pre">format_spec</span></code> attribute is the <a class="reference external" href="https://docs.python.org/3/library/string.html#formatspec" title="(in Python v3.13)"><span class="xref std std-ref">format specification</span></a>. As with f-strings, this is an arbitrary string that defines how to present the value:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">value</span> <span class="o">=</span> <span class="mi">42</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Value: </span><span class="si">{value:.2f}</span><span class="s2">"</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">format_spec</span> <span class="o">==</span> <span class="s2">".2f"</span> </pre></div> </div> <p>Format specifications in f-strings can themselves contain interpolations. This is permitted in template strings as well; <code class="docutils literal notranslate"><span class="pre">format_spec</span></code> is set to the eagerly evaluated result:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">value</span> <span class="o">=</span> <span class="mi">42</span> <span class="n">precision</span> <span class="o">=</span> <span class="mi">2</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Value: {value:.</span><span class="si">{precision}</span><span class="s2">f}"</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">format_spec</span> <span class="o">==</span> <span class="s2">".2f"</span> </pre></div> </div> <p>If no format specification is provided, <code class="docutils literal notranslate"><span class="pre">format_spec</span></code> defaults to an empty string (<code class="docutils literal notranslate"><span class="pre">""</span></code>). This matches the <code class="docutils literal notranslate"><span class="pre">format_spec</span></code> parameter of Python’s <a class="reference external" href="https://docs.python.org/3/library/functions.html#format" title="(in Python v3.13)"><code class="docutils literal notranslate"><span class="pre">format()</span></code></a> built-in.</p> <p>Unlike f-strings, it is up to code that processes the template to determine how to interpret the <code class="docutils literal notranslate"><span class="pre">conv</span></code> and <code class="docutils literal notranslate"><span class="pre">format_spec</span></code> attributes. Such code is not required to use these attributes, but when present they should be respected, and to the extent possible match the behavior of f-strings. It would be surprising if, for example, a template string that uses <code class="docutils literal notranslate"><span class="pre">{value:.2f}</span></code> did not round the value to two decimal places when processed.</p> </section> <section id="processing-template-strings"> <h3><a class="toc-backref" href="#processing-template-strings" role="doc-backlink">Processing Template Strings</a></h3> <p>Developers can write arbitrary code to process template strings. For example, the following function renders static parts of the template in lowercase and interpolations in uppercase:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">templatelib</span> <span class="kn">import</span> <span class="n">Template</span><span class="p">,</span> <span class="n">Interpolation</span> <span class="k">def</span> <span class="nf">lower_upper</span><span class="p">(</span><span class="n">template</span><span class="p">:</span> <span class="n">Template</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="w"> </span><span class="sd">"""Render static parts lowercased and interpolations uppercased."""</span> <span class="n">parts</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">arg</span> <span class="ow">in</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">:</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="n">Interpolation</span><span class="p">):</span> <span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">arg</span><span class="o">.</span><span class="n">value</span><span class="p">)</span><span class="o">.</span><span class="n">upper</span><span class="p">())</span> <span class="k">else</span><span class="p">:</span> <span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">arg</span><span class="o">.</span><span class="n">lower</span><span class="p">())</span> <span class="k">return</span> <span class="s2">""</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span> <span class="n">name</span> <span class="o">=</span> <span class="s2">"world"</span> <span class="k">assert</span> <span class="n">lower_upper</span><span class="p">(</span><span class="n">t</span><span class="s2">"HELLO </span><span class="si">{name}</span><span class="s2">"</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"hello WORLD"</span> </pre></div> </div> <p>There is no requirement that template strings are processed in any particular way. Code that processes templates has no obligation to return a string. Template strings are a flexible, general-purpose feature.</p> <p>See the <a class="reference internal" href="#common-patterns-seen-in-processing-templates">Common Patterns Seen in Processing Templates</a> section for more information on how to process template strings. See the <a class="reference internal" href="#examples">Examples</a> section for detailed working examples.</p> </section> <section id="template-string-concatenation"> <h3><a class="toc-backref" href="#template-string-concatenation" role="doc-backlink">Template String Concatenation</a></h3> <p>Template strings support explicit concatenation using <code class="docutils literal notranslate"><span class="pre">+</span></code>. Concatenation is supported for two <code class="docutils literal notranslate"><span class="pre">Template</span></code> instances as well as for a <code class="docutils literal notranslate"><span class="pre">Template</span></code> instance and a <code class="docutils literal notranslate"><span class="pre">str</span></code>:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">template1</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello "</span> <span class="n">template2</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"</span><span class="si">{name}</span><span class="s2">"</span> <span class="k">assert</span> <span class="n">template1</span> <span class="o">+</span> <span class="n">template2</span> <span class="o">==</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{name}</span><span class="s2">"</span> <span class="k">assert</span> <span class="n">template1</span> <span class="o">+</span> <span class="s2">"!"</span> <span class="o">==</span> <span class="n">t</span><span class="s2">"Hello !"</span> <span class="k">assert</span> <span class="s2">"Hello "</span> <span class="o">+</span> <span class="n">template2</span> <span class="o">==</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{name}</span><span class="s2">"</span> </pre></div> </div> <p>Concatenation of templates is “viral”: the concatenation of a <code class="docutils literal notranslate"><span class="pre">Template</span></code> and a <code class="docutils literal notranslate"><span class="pre">str</span></code> always results in a <code class="docutils literal notranslate"><span class="pre">Template</span></code> instance.</p> <p>Python’s implicit concatenation syntax is also supported. The following code will work as expected:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello "</span> <span class="s2">"World"</span> <span class="k">assert</span> <span class="n">template</span> <span class="o">==</span> <span class="n">t</span><span class="s2">"Hello World"</span> <span class="n">template2</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello "</span> <span class="n">t</span><span class="s2">"World"</span> <span class="k">assert</span> <span class="n">template2</span> <span class="o">==</span> <span class="n">t</span><span class="s2">"Hello World"</span> </pre></div> </div> <p>The <code class="docutils literal notranslate"><span class="pre">Template</span></code> type implements the <code class="docutils literal notranslate"><span class="pre">__add__()</span></code> and <code class="docutils literal notranslate"><span class="pre">__radd__()</span></code> methods roughly as follows:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Template</span><span class="p">:</span> <span class="k">def</span> <span class="fm">__add__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-></span> <span class="n">Template</span><span class="p">:</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span> <span class="k">return</span> <span class="n">Template</span><span class="p">(</span><span class="o">*</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">other</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">Template</span><span class="p">):</span> <span class="k">return</span> <span class="bp">NotImplemented</span> <span class="k">return</span> <span class="n">Template</span><span class="p">(</span><span class="o">*</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">other</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="o">*</span><span class="n">other</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">:])</span> <span class="k">def</span> <span class="fm">__radd__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-></span> <span class="n">Template</span><span class="p">:</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span> <span class="k">return</span> <span class="bp">NotImplemented</span> <span class="k">return</span> <span class="n">Template</span><span class="p">(</span><span class="n">other</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="o">*</span><span class="bp">self</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">:])</span> </pre></div> </div> <p>Special care is taken to ensure that the interleaving of <code class="docutils literal notranslate"><span class="pre">str</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> instances is maintained when concatenating. (See the <a class="reference internal" href="#interleaving-of-template-args">Interleaving of Template.args</a> section for more information.)</p> </section> <section id="template-and-interpolation-equality"> <h3><a class="toc-backref" href="#template-and-interpolation-equality" role="doc-backlink">Template and Interpolation Equality</a></h3> <p>Two instances of <code class="docutils literal notranslate"><span class="pre">Template</span></code> are defined to be equal if their <code class="docutils literal notranslate"><span class="pre">args</span></code> attributes contain the same strings and interpolations in the same order:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">assert</span> <span class="n">t</span><span class="s2">"I love </span><span class="si">{stilton}</span><span class="s2">"</span> <span class="o">==</span> <span class="n">t</span><span class="s2">"I love </span><span class="si">{stilton}</span><span class="s2">"</span> <span class="k">assert</span> <span class="n">t</span><span class="s2">"I love </span><span class="si">{stilton}</span><span class="s2">"</span> <span class="o">!=</span> <span class="n">t</span><span class="s2">"I love </span><span class="si">{roquefort}</span><span class="s2">"</span> <span class="k">assert</span> <span class="n">t</span><span class="s2">"I "</span> <span class="o">+</span> <span class="n">t</span><span class="s2">"love </span><span class="si">{stilton}</span><span class="s2">"</span> <span class="o">==</span> <span class="n">t</span><span class="s2">"I love </span><span class="si">{stilton}</span><span class="s2">"</span> </pre></div> </div> <p>The implementation of <code class="docutils literal notranslate"><span class="pre">Template.__eq__()</span></code> is roughly as follows:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Template</span><span class="p">:</span> <span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-></span> <span class="nb">bool</span><span class="p">:</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">Template</span><span class="p">):</span> <span class="k">return</span> <span class="bp">NotImplemented</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">args</span> <span class="o">==</span> <span class="n">other</span><span class="o">.</span><span class="n">args</span> </pre></div> </div> <p>Two instances of <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> are defined to be equal if their <code class="docutils literal notranslate"><span class="pre">value</span></code>, <code class="docutils literal notranslate"><span class="pre">expr</span></code>, <code class="docutils literal notranslate"><span class="pre">conv</span></code>, and <code class="docutils literal notranslate"><span class="pre">format_spec</span></code> attributes are equal:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Interpolation</span><span class="p">:</span> <span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">:</span> <span class="nb">object</span><span class="p">)</span> <span class="o">-></span> <span class="nb">bool</span><span class="p">:</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">Interpolation</span><span class="p">):</span> <span class="k">return</span> <span class="bp">NotImplemented</span> <span class="k">return</span> <span class="p">(</span> <span class="bp">self</span><span class="o">.</span><span class="n">value</span> <span class="o">==</span> <span class="n">other</span><span class="o">.</span><span class="n">value</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">expr</span> <span class="o">==</span> <span class="n">other</span><span class="o">.</span><span class="n">expr</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">conv</span> <span class="o">==</span> <span class="n">other</span><span class="o">.</span><span class="n">conv</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">format_spec</span> <span class="o">==</span> <span class="n">other</span><span class="o">.</span><span class="n">format_spec</span> <span class="p">)</span> </pre></div> </div> </section> <section id="no-support-for-ordering"> <h3><a class="toc-backref" href="#no-support-for-ordering" role="doc-backlink">No Support for Ordering</a></h3> <p>The <code class="docutils literal notranslate"><span class="pre">Template</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> types do not support ordering. This is unlike all other string literal types in Python, which support lexicographic ordering. Because interpolations can contain arbitrary values, there is no natural ordering for them. As a result, neither the <code class="docutils literal notranslate"><span class="pre">Template</span></code> nor the <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> type implements the standard comparison methods.</p> </section> <section id="support-for-the-debug-specifier"> <h3><a class="toc-backref" href="#support-for-the-debug-specifier" role="doc-backlink">Support for the debug specifier (<code class="docutils literal notranslate"><span class="pre">=</span></code>)</a></h3> <p>The debug specifier, <code class="docutils literal notranslate"><span class="pre">=</span></code>, is supported in template strings and behaves similarly to how it behaves in f-strings, though due to limitations of the implementation there is a slight difference.</p> <p>In particular, <code class="docutils literal notranslate"><span class="pre">t'{expr=}'</span></code> is treated as <code class="docutils literal notranslate"><span class="pre">t'expr={expr}'</span></code>:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello {name=}"</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"Hello name="</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">value</span> <span class="o">==</span> <span class="s2">"World"</span> </pre></div> </div> </section> <section id="raw-template-strings"> <h3><a class="toc-backref" href="#raw-template-strings" role="doc-backlink">Raw Template Strings</a></h3> <p>Raw template strings are supported using the <code class="docutils literal notranslate"><span class="pre">rt</span></code> (or <code class="docutils literal notranslate"><span class="pre">tr</span></code>) prefix:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">trade</span> <span class="o">=</span> <span class="s1">'shrubberies'</span> <span class="n">t</span> <span class="o">=</span> <span class="n">rt</span><span class="s1">'Did you say "</span><span class="si">{trade}</span><span class="s1">"?</span><span class="se">\n</span><span class="s1">'</span> <span class="k">assert</span> <span class="n">t</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="sa">r</span><span class="s1">'Did you say "'</span> <span class="k">assert</span> <span class="n">t</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">==</span> <span class="sa">r</span><span class="s1">'"?\n'</span> </pre></div> </div> <p>In this example, the <code class="docutils literal notranslate"><span class="pre">\n</span></code> is treated as two separate characters (a backslash followed by ‘n’) rather than a newline character. This is consistent with Python’s raw string behavior.</p> <p>As with regular template strings, interpolations in raw template strings are processed normally, allowing for the combination of raw string behavior and dynamic content.</p> </section> <section id="interpolation-expression-evaluation"> <h3><a class="toc-backref" href="#interpolation-expression-evaluation" role="doc-backlink">Interpolation Expression Evaluation</a></h3> <p>Expression evaluation for interpolations is the same as in <a class="pep reference internal" href="../pep-0498/#expression-evaluation" title="PEP 498 – Literal String Interpolation § Expression evaluation">PEP 498</a>:</p> <blockquote> <div>The expressions that are extracted from the string are evaluated in the context where the template string appeared. This means the expression has full access to its lexical scope, including local and global variables. Any valid Python expression can be used, including function and method calls.</div></blockquote> <p>Template strings are evaluated eagerly from left to right, just like f-strings. This means that interpolations are evaluated immediately when the template string is processed, not deferred or wrapped in lambdas.</p> </section> <section id="exceptions"> <h3><a class="toc-backref" href="#exceptions" role="doc-backlink">Exceptions</a></h3> <p>Exceptions raised in t-string literals are the same as those raised in f-string literals.</p> </section> <section id="interleaving-of-template-args"> <h3><a class="toc-backref" href="#interleaving-of-template-args" role="doc-backlink">Interleaving of <code class="docutils literal notranslate"><span class="pre">Template.args</span></code></a></h3> <p>In the <code class="docutils literal notranslate"><span class="pre">Template</span></code> type, the <code class="docutils literal notranslate"><span class="pre">args</span></code> attribute is a sequence that will always alternate between string literals and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> instances. Specifically:</p> <ul class="simple"> <li>Even-indexed elements (0, 2, 4, …) are always of type <code class="docutils literal notranslate"><span class="pre">str</span></code>, representing the literal parts of the template.</li> <li>Odd-indexed elements (1, 3, 5, …) are always <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> instances, representing the interpolated expressions.</li> </ul> <p>For example, the following assertions hold:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{name}</span><span class="s2">"</span> <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"Hello "</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">value</span> <span class="o">==</span> <span class="s2">"World"</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">==</span> <span class="s2">""</span> </pre></div> </div> <p>These rules imply that the <code class="docutils literal notranslate"><span class="pre">args</span></code> attribute will always have an odd length. As a consequence, empty strings are added to the sequence when the template begins or ends with an interpolation, or when two interpolations are adjacent:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="s2">"a"</span><span class="p">,</span> <span class="s2">"b"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"</span><span class="si">{a}{b}</span><span class="s2">"</span> <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">)</span> <span class="o">==</span> <span class="mi">5</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s2">""</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">value</span> <span class="o">==</span> <span class="s2">"a"</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">==</span> <span class="s2">""</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="o">.</span><span class="n">value</span> <span class="o">==</span> <span class="s2">"b"</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">==</span> <span class="s2">""</span> </pre></div> </div> <p>Most template processing code will not care about this detail and will use either structural pattern matching or <code class="docutils literal notranslate"><span class="pre">isinstance()</span></code> checks to distinguish between the two types of elements in the sequence.</p> <p>The detail exists because it allows for performance optimizations in template processing code. For example, a template processor could cache the static parts of the template and only reprocess the dynamic parts when the template is evaluated with different values. Access to the static parts can be done with <code class="docutils literal notranslate"><span class="pre">template.args[::2]</span></code>.</p> <p>Interleaving is an invariant maintained by the <code class="docutils literal notranslate"><span class="pre">Template</span></code> class. Developers can take advantage of it but they are not required to themselves maintain it. Specifically, <code class="docutils literal notranslate"><span class="pre">Template.__init__()</span></code> can be called with <code class="docutils literal notranslate"><span class="pre">str</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> instances in <em>any</em> order; the constructor will “interleave” them as necessary before assigning them to <code class="docutils literal notranslate"><span class="pre">args</span></code>.</p> </section> </section> <section id="examples"> <h2><a class="toc-backref" href="#examples" role="doc-backlink">Examples</a></h2> <p>All examples in this section of the PEP have fully tested reference implementations available in the public <a class="reference external" href="https://github.com/davepeck/pep750-examples">pep750-examples</a> git repository.</p> <section id="example-implementing-f-strings-with-t-strings"> <h3><a class="toc-backref" href="#example-implementing-f-strings-with-t-strings" role="doc-backlink">Example: Implementing f-strings with t-strings</a></h3> <p>It is easy to “implement” f-strings using t-strings. That is, we can write a function <code class="docutils literal notranslate"><span class="pre">f(template:</span> <span class="pre">Template)</span> <span class="pre">-></span> <span class="pre">str</span></code> that processes a <code class="docutils literal notranslate"><span class="pre">Template</span></code> in much the same way as an f-string literal, returning the same result:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">value</span> <span class="o">=</span> <span class="mi">42</span> <span class="n">templated</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{name!r}</span><span class="s2">, value: </span><span class="si">{value:.2f}</span><span class="s2">"</span> <span class="n">formatted</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"Hello </span><span class="si">{</span><span class="n">name</span><span class="si">!r}</span><span class="s2">, value: </span><span class="si">{</span><span class="n">value</span><span class="si">:</span><span class="s2">.2f</span><span class="si">}</span><span class="s2">"</span> <span class="k">assert</span> <span class="n">f</span><span class="p">(</span><span class="n">templated</span><span class="p">)</span> <span class="o">==</span> <span class="n">formatted</span> </pre></div> </div> <p>The <code class="docutils literal notranslate"><span class="pre">f()</span></code> function supports both conversion specifiers like <code class="docutils literal notranslate"><span class="pre">!r</span></code> and format specifiers like <code class="docutils literal notranslate"><span class="pre">:.2f</span></code>. The full code is fairly simple:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">templatelib</span> <span class="kn">import</span> <span class="n">Template</span><span class="p">,</span> <span class="n">Interpolation</span> <span class="k">def</span> <span class="nf">convert</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nb">object</span><span class="p">,</span> <span class="n">conv</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">"a"</span><span class="p">,</span> <span class="s2">"r"</span><span class="p">,</span> <span class="s2">"s"</span><span class="p">]</span> <span class="o">|</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-></span> <span class="nb">object</span><span class="p">:</span> <span class="k">if</span> <span class="n">conv</span> <span class="o">==</span> <span class="s2">"a"</span><span class="p">:</span> <span class="k">return</span> <span class="n">ascii</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">elif</span> <span class="n">conv</span> <span class="o">==</span> <span class="s2">"r"</span><span class="p">:</span> <span class="k">return</span> <span class="nb">repr</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">elif</span> <span class="n">conv</span> <span class="o">==</span> <span class="s2">"s"</span><span class="p">:</span> <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">return</span> <span class="n">value</span> <span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">template</span><span class="p">:</span> <span class="n">Template</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="n">parts</span> <span class="o">=</span> <span class="p">[]</span> <span class="k">for</span> <span class="n">arg</span> <span class="ow">in</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">:</span> <span class="k">match</span> <span class="n">arg</span><span class="p">:</span> <span class="k">case</span> <span class="nb">str</span><span class="p">()</span> <span class="k">as</span> <span class="n">s</span><span class="p">:</span> <span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">case</span><span class="w"> </span><span class="n">Interpolation</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="k">_</span><span class="p">,</span> <span class="n">conv</span><span class="p">,</span> <span class="n">format_spec</span><span class="p">):</span> <span class="n">value</span> <span class="o">=</span> <span class="n">convert</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">conv</span><span class="p">)</span> <span class="n">value</span> <span class="o">=</span> <span class="nb">format</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">format_spec</span><span class="p">)</span> <span class="n">parts</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">return</span> <span class="s2">""</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span> </pre></div> </div> <div class="admonition note"> <p class="admonition-title">Note</p> <p>Example code</p> <p>See <a class="reference external" href="https://github.com/davepeck/pep750-examples/blob/main/pep/fstring.py">fstring.py</a> and <a class="reference external" href="https://github.com/davepeck/pep750-examples/blob/main/pep/test_fstring.py">test_fstring.py</a>.</p> </div> </section> <section id="example-structured-logging"> <h3><a class="toc-backref" href="#example-structured-logging" role="doc-backlink">Example: Structured Logging</a></h3> <p>Structured logging allows developers to log data in both a human-readable format <em>and</em> a structured format (like JSON) using only a single logging call. This is useful for log aggregation systems that process the structured format while still allowing developers to easily read their logs.</p> <p>We present two different approaches to implementing structured logging with template strings.</p> <section id="approach-1-custom-log-messages"> <h4><a class="toc-backref" href="#approach-1-custom-log-messages" role="doc-backlink">Approach 1: Custom Log Messages</a></h4> <p>The <a class="reference external" href="https://docs.python.org/3/howto/logging-cookbook.html#logging-cookbook" title="(in Python v3.13)"><span class="xref std std-ref">Python Logging Cookbook</span></a> has a short section on <a class="reference external" href="https://docs.python.org/3/howto/logging-cookbook.html#implementing-structured-logging">how to implement structured logging</a>.</p> <p>The logging cookbook suggests creating a new “message” class, <code class="docutils literal notranslate"><span class="pre">StructuredMessage</span></code>, that is constructed with a simple text message and a separate dictionary of values:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">message</span> <span class="o">=</span> <span class="n">StructuredMessage</span><span class="p">(</span><span class="s2">"user action"</span><span class="p">,</span> <span class="p">{</span> <span class="s2">"action"</span><span class="p">:</span> <span class="s2">"traded"</span><span class="p">,</span> <span class="s2">"amount"</span><span class="p">:</span> <span class="mi">42</span><span class="p">,</span> <span class="s2">"item"</span><span class="p">:</span> <span class="s2">"shrubs"</span> <span class="p">})</span> <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="n">message</span><span class="p">)</span> <span class="c1"># Outputs:</span> <span class="c1"># user action >>> {"action": "traded", "amount": 42, "item": "shrubs"}</span> </pre></div> </div> <p>The <code class="docutils literal notranslate"><span class="pre">StructuredMessage.__str__()</span></code> method formats both the human-readable message <em>and</em> the values, combining them into a final string. (See the <a class="reference external" href="https://docs.python.org/3/howto/logging-cookbook.html#implementing-structured-logging">logging cookbook</a> for its full example.)</p> <p>We can implement an improved version of <code class="docutils literal notranslate"><span class="pre">StructuredMessage</span></code> using template strings:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">json</span> <span class="kn">from</span> <span class="nn">templatelib</span> <span class="kn">import</span> <span class="n">Interpolation</span><span class="p">,</span> <span class="n">Template</span> <span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Mapping</span> <span class="k">class</span> <span class="nc">TemplateMessage</span><span class="p">:</span> <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">template</span><span class="p">:</span> <span class="n">Template</span><span class="p">)</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">template</span> <span class="o">=</span> <span class="n">template</span> <span class="nd">@property</span> <span class="k">def</span> <span class="nf">message</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="c1"># Use the f() function from the previous example</span> <span class="k">return</span> <span class="n">f</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">template</span><span class="p">)</span> <span class="nd">@property</span> <span class="k">def</span> <span class="nf">values</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="n">Mapping</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">object</span><span class="p">]:</span> <span class="k">return</span> <span class="p">{</span> <span class="n">arg</span><span class="o">.</span><span class="n">expr</span><span class="p">:</span> <span class="n">arg</span><span class="o">.</span><span class="n">value</span> <span class="k">for</span> <span class="n">arg</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">template</span><span class="o">.</span><span class="n">args</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="n">Interpolation</span><span class="p">)</span> <span class="p">}</span> <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="k">return</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">message</span><span class="si">}</span><span class="s2"> >>> </span><span class="si">{</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">values</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span> <span class="n">_</span> <span class="o">=</span> <span class="n">TemplateMessage</span> <span class="c1"># optional, to improve readability</span> <span class="n">action</span><span class="p">,</span> <span class="n">amount</span><span class="p">,</span> <span class="n">item</span> <span class="o">=</span> <span class="s2">"traded"</span><span class="p">,</span> <span class="mi">42</span><span class="p">,</span> <span class="s2">"shrubs"</span> <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="n">_</span><span class="p">(</span><span class="n">t</span><span class="s2">"User </span><span class="si">{action}</span><span class="s2">: </span><span class="si">{amount:.2f}</span><span class="s2"> </span><span class="si">{item}</span><span class="s2">"</span><span class="p">))</span> <span class="c1"># Outputs:</span> <span class="c1"># User traded: 42.00 shrubs >>> {"action": "traded", "amount": 42, "item": "shrubs"}</span> </pre></div> </div> <p>Template strings give us a more elegant way to define the custom message class. With template strings it is no longer necessary for developers to make sure that their format string and values dictionary are kept in sync; a single template string literal is all that is needed. The <code class="docutils literal notranslate"><span class="pre">TemplateMessage</span></code> implementation can automatically extract structured keys and values from the <code class="docutils literal notranslate"><span class="pre">Interpolation.expr</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation.value</span></code> attributes, respectively.</p> </section> <section id="approach-2-custom-formatters"> <h4><a class="toc-backref" href="#approach-2-custom-formatters" role="doc-backlink">Approach 2: Custom Formatters</a></h4> <p>Custom messages are a reasonable approach to structured logging but can be a little awkward. To use them, developers must wrap every log message they write in a custom class. This can be easy to forget.</p> <p>An alternative approach is to define custom <code class="docutils literal notranslate"><span class="pre">logging.Formatter</span></code> classes. This approach is more flexible and allows for more control over the final output. In particular, it’s possible to take a single template string and output it in multiple formats (human-readable and JSON) to separate log streams.</p> <p>We define two simple formatters, a <code class="docutils literal notranslate"><span class="pre">MessageFormatter</span></code> for human-readable output and a <code class="docutils literal notranslate"><span class="pre">ValuesFormatter</span></code> for JSON output:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">json</span> <span class="kn">from</span> <span class="nn">logging</span> <span class="kn">import</span> <span class="n">Formatter</span><span class="p">,</span> <span class="n">LogRecord</span> <span class="kn">from</span> <span class="nn">templatelib</span> <span class="kn">import</span> <span class="n">Interpolation</span><span class="p">,</span> <span class="n">Template</span> <span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">Mapping</span> <span class="k">class</span> <span class="nc">MessageFormatter</span><span class="p">(</span><span class="n">Formatter</span><span class="p">):</span> <span class="k">def</span> <span class="nf">message</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">template</span><span class="p">:</span> <span class="n">Template</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="c1"># Use the f() function from the previous example</span> <span class="k">return</span> <span class="n">f</span><span class="p">(</span><span class="n">template</span><span class="p">)</span> <span class="k">def</span> <span class="nf">format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">:</span> <span class="n">LogRecord</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="n">msg</span> <span class="o">=</span> <span class="n">record</span><span class="o">.</span><span class="n">msg</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">Template</span><span class="p">):</span> <span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">record</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">message</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span> <span class="k">class</span> <span class="nc">ValuesFormatter</span><span class="p">(</span><span class="n">Formatter</span><span class="p">):</span> <span class="k">def</span> <span class="nf">values</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">template</span><span class="p">:</span> <span class="n">Template</span><span class="p">)</span> <span class="o">-></span> <span class="n">Mapping</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">]:</span> <span class="k">return</span> <span class="p">{</span> <span class="n">arg</span><span class="o">.</span><span class="n">expr</span><span class="p">:</span> <span class="n">arg</span><span class="o">.</span><span class="n">value</span> <span class="k">for</span> <span class="n">arg</span> <span class="ow">in</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="n">Interpolation</span><span class="p">)</span> <span class="p">}</span> <span class="k">def</span> <span class="nf">format</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">record</span><span class="p">:</span> <span class="n">LogRecord</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="n">msg</span> <span class="o">=</span> <span class="n">record</span><span class="o">.</span><span class="n">msg</span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">Template</span><span class="p">):</span> <span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">record</span><span class="p">)</span> <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">values</span><span class="p">(</span><span class="n">msg</span><span class="p">))</span> </pre></div> </div> <p>We can then use these formatters when configuring our logger:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">logging</span> <span class="kn">import</span> <span class="nn">sys</span> <span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span> <span class="n">message_handler</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="p">)</span> <span class="n">message_handler</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">MessageFormatter</span><span class="p">())</span> <span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">message_handler</span><span class="p">)</span> <span class="n">values_handler</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">StreamHandler</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span> <span class="n">values_handler</span><span class="o">.</span><span class="n">setFormatter</span><span class="p">(</span><span class="n">ValuesFormatter</span><span class="p">())</span> <span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">values_handler</span><span class="p">)</span> <span class="n">action</span><span class="p">,</span> <span class="n">amount</span><span class="p">,</span> <span class="n">item</span> <span class="o">=</span> <span class="s2">"traded"</span><span class="p">,</span> <span class="mi">42</span><span class="p">,</span> <span class="s2">"shrubs"</span> <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="n">t</span><span class="s2">"User </span><span class="si">{action}</span><span class="s2">: </span><span class="si">{amount:.2f}</span><span class="s2"> </span><span class="si">{item}</span><span class="s2">"</span><span class="p">)</span> <span class="c1"># Outputs to sys.stdout:</span> <span class="c1"># User traded: 42.00 shrubs</span> <span class="c1"># At the same time, outputs to sys.stderr:</span> <span class="c1"># {"action": "traded", "amount": 42, "item": "shrubs"}</span> </pre></div> </div> <p>This approach has a couple advantages over the custom message approach to structured logging:</p> <ul class="simple"> <li>Developers can log a t-string directly without wrapping it in a custom class.</li> <li>Human-readable and structured output can be sent to separate log streams. This is useful for log aggregation systems that process structured data independently from human-readable data.</li> </ul> <div class="admonition note"> <p class="admonition-title">Note</p> <p>Example code</p> <p>See <a class="reference external" href="https://github.com/davepeck/pep750-examples/blob/main/pep/logging.py">logging.py</a> and <a class="reference external" href="https://github.com/davepeck/pep750-examples/blob/main/pep/test_logging.py">test_logging.py</a>.</p> </div> </section> </section> <section id="example-html-templating"> <h3><a class="toc-backref" href="#example-html-templating" role="doc-backlink">Example: HTML Templating</a></h3> <p>This PEP contains several short HTML templating examples. It turns out that the “hypothetical” <code class="docutils literal notranslate"><span class="pre">html()</span></code> function mentioned in the <a class="reference internal" href="#motivation">Motivation</a> section (and a few other places in this PEP) exists and is available in the <a class="reference external" href="https://github.com/davepeck/pep750-examples/">pep750-examples repository</a>. If you’re thinking about parsing a complex grammar with template strings, we hope you’ll find it useful.</p> </section> </section> <section id="backwards-compatibility"> <h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2> <p>Like f-strings, use of template strings will be a syntactic backwards incompatibility with previous versions.</p> </section> <section id="security-implications"> <h2><a class="toc-backref" href="#security-implications" role="doc-backlink">Security Implications</a></h2> <p>The security implications of working with template strings, with respect to interpolations, are as follows:</p> <ol class="arabic simple"> <li>Scope lookup is the same as f-strings (lexical scope). This model has been shown to work well in practice.</li> <li>Code that processes <code class="docutils literal notranslate"><span class="pre">Template</span></code> instances can ensure that any interpolations are processed in a safe fashion, including respecting the context in which they appear.</li> </ol> </section> <section id="how-to-teach-this"> <h2><a class="toc-backref" href="#how-to-teach-this" role="doc-backlink">How To Teach This</a></h2> <p>Template strings have several audiences:</p> <ul class="simple"> <li>Developers using template strings and processing functions</li> <li>Authors of template processing code</li> <li>Framework authors who build interesting machinery with template strings</li> </ul> <p>We hope that teaching developers will be straightforward. At a glance, template strings look just like f-strings. Their syntax is familiar and the scoping rules remain the same.</p> <p>The first thing developers must learn is that template string literals don’t evaluate to strings; instead, they evaluate to a new type, <code class="docutils literal notranslate"><span class="pre">Template</span></code>. This is a simple type intended to be used by template processing code. It’s not until developers call a processing function that they get the result they want: typically, a string, although processing code can of course return any arbitrary type.</p> <p>Because developers will learn that t-strings are nearly always used in tandem with processing functions, they don’t necessarily need to understand the details of the <code class="docutils literal notranslate"><span class="pre">Template</span></code> type. As with descriptors and decorators, we expect many more developers will use t-strings than write t-string processing functions.</p> <p>Over time, a small number of more advanced developers <em>will</em> wish to author their own template processing code. Writing processing code often requires thinking in terms of formal grammars. Developers will need to learn how to parse the <code class="docutils literal notranslate"><span class="pre">args</span></code> attribute of a <code class="docutils literal notranslate"><span class="pre">Template</span></code> instance and how to process interpolations in a context-sensitive fashion. More sophisticated grammars will likely require parsing to intermediate representations like an AST. Great template processing code will handle format specifiers and conversions when appropriate. Writing production-grade template processing code – for instance, to support HTML templates – can be a large undertaking.</p> <p>We expect that template strings will provide framework authors with a powerful new tool in their toolbox. While the functionality of template strings overlaps with existing tools like template engines, t-strings move that logic into the language itself. Bringing the full power and generality of Python to bear on string processing tasks opens new possibilities for framework authors.</p> </section> <section id="common-patterns-seen-in-processing-templates"> <h2><a class="toc-backref" href="#common-patterns-seen-in-processing-templates" role="doc-backlink">Common Patterns Seen in Processing Templates</a></h2> <section id="structural-pattern-matching"> <h3><a class="toc-backref" href="#structural-pattern-matching" role="doc-backlink">Structural Pattern Matching</a></h3> <p>Iterating over the <code class="docutils literal notranslate"><span class="pre">Template.args</span></code> with structural pattern matching is the expected best practice for many template function implementations:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">templatelib</span> <span class="kn">import</span> <span class="n">Template</span><span class="p">,</span> <span class="n">Interpolation</span> <span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="n">template</span><span class="p">:</span> <span class="n">Template</span><span class="p">)</span> <span class="o">-></span> <span class="n">Any</span><span class="p">:</span> <span class="k">for</span> <span class="n">arg</span> <span class="ow">in</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">:</span> <span class="k">match</span> <span class="n">arg</span><span class="p">:</span> <span class="k">case</span> <span class="nb">str</span><span class="p">()</span> <span class="k">as</span> <span class="n">s</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># handle each string part</span> <span class="k">case</span> <span class="n">Interpolation</span><span class="p">()</span> <span class="k">as</span> <span class="n">interpolation</span><span class="p">:</span> <span class="o">...</span> <span class="c1"># handle each interpolation</span> </pre></div> </div> <p>Processing code may also commonly sub-match on attributes of the <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> type:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">match</span> <span class="n">arg</span><span class="p">:</span> <span class="k">case</span> <span class="n">Interpolation</span><span class="p">(</span><span class="nb">int</span><span class="p">()):</span> <span class="o">...</span> <span class="c1"># handle interpolations with integer values</span> <span class="k">case</span> <span class="n">Interpolation</span><span class="p">(</span><span class="n">value</span><span class="o">=</span><span class="nb">str</span><span class="p">()</span> <span class="k">as</span> <span class="n">s</span><span class="p">):</span> <span class="o">...</span> <span class="c1"># handle interpolations with string values</span> <span class="c1"># etc.</span> </pre></div> </div> </section> <section id="memoizing"> <h3><a class="toc-backref" href="#memoizing" role="doc-backlink">Memoizing</a></h3> <p>Template functions can efficiently process both static and dynamic parts of templates. The structure of <code class="docutils literal notranslate"><span class="pre">Template</span></code> objects allows for effective memoization:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">source</span> <span class="o">=</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[::</span><span class="mi">2</span><span class="p">]</span> <span class="c1"># Static string parts</span> <span class="n">values</span> <span class="o">=</span> <span class="p">[</span><span class="n">i</span><span class="o">.</span><span class="n">value</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">::</span><span class="mi">2</span><span class="p">]]</span> <span class="c1"># Dynamic interpolated values</span> </pre></div> </div> <p>This separation enables caching of processed static parts, while dynamic parts can be inserted as needed. Authors of template processing code can use the static <code class="docutils literal notranslate"><span class="pre">source</span></code> as cache keys, leading to significant performance improvements when similar templates are used repeatedly.</p> </section> <section id="parsing-to-intermediate-representations"> <h3><a class="toc-backref" href="#parsing-to-intermediate-representations" role="doc-backlink">Parsing to Intermediate Representations</a></h3> <p>Code that processes templates can parse the template string into intermediate representations, like an AST. We expect that many template processing libraries will use this approach.</p> <p>For instance, rather than returning a <code class="docutils literal notranslate"><span class="pre">str</span></code>, our theoretical <code class="docutils literal notranslate"><span class="pre">html()</span></code> function (see the <a class="reference internal" href="#motivation">Motivation</a> section) could return an HTML <code class="docutils literal notranslate"><span class="pre">Element</span></code> defined in the same package:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> <span class="k">class</span> <span class="nc">Element</span><span class="p">:</span> <span class="n">tag</span><span class="p">:</span> <span class="nb">str</span> <span class="n">attributes</span><span class="p">:</span> <span class="n">Mapping</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span> <span class="o">|</span> <span class="nb">bool</span><span class="p">]</span> <span class="n">children</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">str</span> <span class="o">|</span> <span class="n">Element</span><span class="p">]</span> <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="o">...</span> <span class="k">def</span> <span class="nf">html</span><span class="p">(</span><span class="n">template</span><span class="p">:</span> <span class="n">Template</span><span class="p">)</span> <span class="o">-></span> <span class="n">Element</span><span class="p">:</span> <span class="o">...</span> </pre></div> </div> <p>Calling <code class="docutils literal notranslate"><span class="pre">str(element)</span></code> would then render the HTML but, in the meantime, the <code class="docutils literal notranslate"><span class="pre">Element</span></code> could be manipulated in a variety of ways.</p> </section> <section id="context-sensitive-processing-of-interpolations"> <h3><a class="toc-backref" href="#context-sensitive-processing-of-interpolations" role="doc-backlink">Context-sensitive Processing of Interpolations</a></h3> <p>Continuing with our hypothetical <code class="docutils literal notranslate"><span class="pre">html()</span></code> function, it could be made context-sensitive. Interpolations could be processed differently depending on where they appear in the template.</p> <p>For example, our <code class="docutils literal notranslate"><span class="pre">html()</span></code> function could support multiple kinds of interpolations:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">attributes</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="s2">"main"</span><span class="p">}</span> <span class="n">attribute_value</span> <span class="o">=</span> <span class="s2">"shrubbery"</span> <span class="n">content</span> <span class="o">=</span> <span class="s2">"hello"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"<div </span><span class="si">{attributes}</span><span class="s2"> data-value=</span><span class="si">{attribute_value}</span><span class="s2">></span><span class="si">{content}</span><span class="s2"></div>"</span> <span class="n">element</span> <span class="o">=</span> <span class="n">html</span><span class="p">(</span><span class="n">template</span><span class="p">)</span> <span class="k">assert</span> <span class="nb">str</span><span class="p">(</span><span class="n">element</span><span class="p">)</span> <span class="o">==</span> <span class="s1">'<div id="main" data-value="shrubbery">hello</div>'</span> </pre></div> </div> <p>Because the <code class="docutils literal notranslate"><span class="pre">{attributes}</span></code> interpolation occurs in the context of an HTML tag, and because there is no corresponding attribute name, it is treated as a dictionary of attributes. The <code class="docutils literal notranslate"><span class="pre">{attribute_value}</span></code> interpolation is treated as a simple string value and is quoted before inclusion in the final string. The <code class="docutils literal notranslate"><span class="pre">{content}</span></code> interpolation is treated as potentially unsafe content and is escaped before inclusion in the final string.</p> </section> <section id="nested-template-strings"> <h3><a class="toc-backref" href="#nested-template-strings" role="doc-backlink">Nested Template Strings</a></h3> <p>Going a step further with our <code class="docutils literal notranslate"><span class="pre">html()</span></code> function, we could support nested template strings. This would allow for more complex HTML structures to be built up from simpler templates:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">content</span> <span class="o">=</span> <span class="n">html</span><span class="p">(</span><span class="n">t</span><span class="s2">"<p>Hello </span><span class="si">{name}</span><span class="s2"></p>"</span><span class="p">)</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"<div></span><span class="si">{content}</span><span class="s2"></div>"</span> <span class="n">element</span> <span class="o">=</span> <span class="n">html</span><span class="p">(</span><span class="n">template</span><span class="p">)</span> <span class="k">assert</span> <span class="nb">str</span><span class="p">(</span><span class="n">element</span><span class="p">)</span> <span class="o">==</span> <span class="s1">'<div><p>Hello World</p></div>'</span> </pre></div> </div> <p>Because the <code class="docutils literal notranslate"><span class="pre">{content}</span></code> interpolation is an <code class="docutils literal notranslate"><span class="pre">Element</span></code> instance, it does not need to be escaped before inclusion in the final string.</p> <p>One could imagine a nice simplification: if the <code class="docutils literal notranslate"><span class="pre">html()</span></code> function is passed a <code class="docutils literal notranslate"><span class="pre">Template</span></code> instance, it could automatically convert it to an <code class="docutils literal notranslate"><span class="pre">Element</span></code> by recursively calling itself on the nested template.</p> <p>We expect that nesting and composition of templates will be a common pattern in template processing code and, where appropriate, used in preference to simple string concatenation.</p> </section> <section id="approaches-to-lazy-evaluation"> <h3><a class="toc-backref" href="#approaches-to-lazy-evaluation" role="doc-backlink">Approaches to Lazy Evaluation</a></h3> <p>Like f-strings, interpolations in t-string literals are eagerly evaluated. However, there are cases where lazy evaluation may be desirable.</p> <p>If a single interpolation is expensive to evaluate, it can be explicitly wrapped in a <code class="docutils literal notranslate"><span class="pre">lambda</span></code> in the template string literal:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">name</span> <span class="o">=</span> <span class="s2">"World"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello {(lambda: name)}"</span> <span class="k">assert</span> <span class="nb">callable</span><span class="p">(</span><span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">value</span><span class="p">)</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">value</span><span class="p">()</span> <span class="o">==</span> <span class="s2">"World"</span> </pre></div> </div> <p>This assumes, of course, that template processing code anticipates and handles callable interpolation values. (One could imagine also supporting iterators, awaitables, etc.) This is not a requirement of the PEP, but it is a common pattern in template processing code.</p> <p>In general, we hope that the community will develop best practices for lazy evaluation of interpolations in template strings and that, when it makes sense, common libraries will provide support for callable or awaitable values in their template processing code.</p> </section> <section id="approaches-to-asynchronous-evaluation"> <h3><a class="toc-backref" href="#approaches-to-asynchronous-evaluation" role="doc-backlink">Approaches to Asynchronous Evaluation</a></h3> <p>Closely related to lazy evaluation is asynchronous evaluation.</p> <p>As with f-strings, the <code class="docutils literal notranslate"><span class="pre">await</span></code> keyword is allowed in interpolations:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">async</span> <span class="k">def</span> <span class="nf">example</span><span class="p">():</span> <span class="k">async</span> <span class="k">def</span> <span class="nf">get_name</span><span class="p">()</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">return</span> <span class="s2">"Sleepy"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello {await get_name()}"</span> <span class="c1"># Use the f() function from the f-string example, above</span> <span class="k">assert</span> <span class="n">f</span><span class="p">(</span><span class="n">template</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"Hello Sleepy"</span> </pre></div> </div> <p>More sophisticated template processing code can take advantage of this to perform asynchronous operations in interpolations. For example, a “smart” processing function could anticipate that an interpolation is an awaitable and await it before processing the template string:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">async</span> <span class="k">def</span> <span class="nf">example</span><span class="p">():</span> <span class="k">async</span> <span class="k">def</span> <span class="nf">get_name</span><span class="p">()</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span> <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">return</span> <span class="s2">"Sleepy"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{get_name}</span><span class="s2">"</span> <span class="k">assert</span> <span class="k">await</span> <span class="n">aformat</span><span class="p">(</span><span class="n">template</span><span class="p">)</span> <span class="o">==</span> <span class="s2">"Hello Sleepy"</span> </pre></div> </div> <p>This assumes that the template processing code in <code class="docutils literal notranslate"><span class="pre">aformat()</span></code> is asynchronous and is able to <code class="docutils literal notranslate"><span class="pre">await</span></code> an interpolation’s value.</p> <div class="admonition note"> <p class="admonition-title">Note</p> <p>Example code</p> <p>See <a class="reference external" href="https://github.com/davepeck/pep750-examples/blob/main/pep/aformat.py">aformat.py</a> and <a class="reference external" href="https://github.com/davepeck/pep750-examples/blob/main/pep/test_aformat.py">test_aformat.py</a>.</p> </div> </section> <section id="approaches-to-template-reuse"> <h3><a class="toc-backref" href="#approaches-to-template-reuse" role="doc-backlink">Approaches to Template Reuse</a></h3> <p>If developers wish to reuse template strings multiple times with different values, they can write a function to return a <code class="docutils literal notranslate"><span class="pre">Template</span></code> instance:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">reusable</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">question</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="n">Template</span><span class="p">:</span> <span class="k">return</span> <span class="n">t</span><span class="s2">"Hello </span><span class="si">{name}</span><span class="s2">, </span><span class="si">{question}</span><span class="s2">?"</span> <span class="n">template</span> <span class="o">=</span> <span class="n">reusable</span><span class="p">(</span><span class="s2">"friend"</span><span class="p">,</span> <span class="s2">"how are you"</span><span class="p">)</span> <span class="n">template</span> <span class="o">=</span> <span class="n">reusable</span><span class="p">(</span><span class="s2">"King Arthur"</span><span class="p">,</span> <span class="s2">"what is your quest"</span><span class="p">)</span> </pre></div> </div> <p>This is, of course, no different from how f-strings can be reused.</p> </section> </section> <section id="reference-implementation"> <h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2> <p>At the time of this PEP’s announcement, a fully-working implementation is <a class="reference external" href="https://github.com/lysnikolaou/cpython/tree/tag-strings-rebased">available</a>.</p> <p>There is also a public repository of <a class="reference external" href="https://github.com/davepeck/pep750-examples">examples and tests</a> built around the reference implementation. If you’re interested in playing with template strings, this repository is a great place to start.</p> </section> <section id="rejected-ideas"> <h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2> <p>This PEP has been through several significant revisions. In addition, quite a few interesting ideas were considered both in revisions of <a class="pep reference internal" href="../pep-0501/" title="PEP 501 – General purpose template literal strings">PEP 501</a> and in the <a class="reference external" href="https://discuss.python.org/t/pep-750-tag-strings-for-writing-domain-specific-languages/60408/196">Discourse discussion</a>.</p> <p>We attempt to document the most significant ideas that were considered and rejected.</p> <section id="arbitrary-string-literal-prefixes"> <h3><a class="toc-backref" href="#arbitrary-string-literal-prefixes" role="doc-backlink">Arbitrary String Literal Prefixes</a></h3> <p>Inspired by <a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates">JavaScript tagged template literals</a>, an earlier version of this PEP allowed for arbitrary “tag” prefixes in front of literal strings:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">my_tag</span><span class="s1">'Hello </span><span class="si">{name}</span><span class="s1">'</span> </pre></div> </div> <p>The prefix was a special callable called a “tag function”. Tag functions received the parts of the template string in an argument list. They could then process the string and return an arbitrary value:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">my_tag</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="n">Interpolation</span><span class="p">)</span> <span class="o">-></span> <span class="n">Any</span><span class="p">:</span> <span class="o">...</span> </pre></div> </div> <p>This approach was rejected for several reasons:</p> <ul class="simple"> <li>It was deemed too complex to build in full generality. JavaScript allows for arbitrary expressions to precede a template string, which is a significant challenge to implement in Python.</li> <li>It precluded future introduction of new string prefixes.</li> <li>It seemed to needlessly pollute the namespace.</li> </ul> <p>Use of a single <code class="docutils literal notranslate"><span class="pre">t</span></code> prefix was chosen as a simpler, more Pythonic approach and more in keeping with template strings’ role as a generalization of f-strings.</p> </section> <section id="delayed-evaluation-of-interpolations"> <h3><a class="toc-backref" href="#delayed-evaluation-of-interpolations" role="doc-backlink">Delayed Evaluation of Interpolations</a></h3> <p>An early version of this PEP proposed that interpolations should be lazily evaluated. All interpolations were “wrapped” in implicit lambdas. Instead of having an eagerly evaluated <code class="docutils literal notranslate"><span class="pre">value</span></code> attribute, interpolations had a <code class="docutils literal notranslate"><span class="pre">getvalue()</span></code> method that would resolve the value of the interpolation:</p> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Interpolation</span><span class="p">:</span> <span class="o">...</span> <span class="n">_value</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[],</span> <span class="nb">object</span><span class="p">]</span> <span class="k">def</span> <span class="nf">getvalue</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="nb">object</span><span class="p">:</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_value</span><span class="p">()</span> </pre></div> </div> <p>This was rejected for several reasons:</p> <ul class="simple"> <li>The overwhelming majority of use cases for template strings naturally call for immediate evaluation.</li> <li>Delayed evaluation would be a significant departure from the behavior of f-strings.</li> <li>Implicit lambda wrapping leads to difficulties with type hints and static analysis.</li> </ul> <p>Most importantly, there are viable (if imperfect) alternatives to implicit lambda wrapping when lazy evaluation is desired. See the section on <a class="reference internal" href="#approaches-to-lazy-evaluation">Approaches to Lazy Evaluation</a>, above, for more information.</p> </section> <section id="making-template-and-interpolation-into-protocols"> <h3><a class="toc-backref" href="#making-template-and-interpolation-into-protocols" role="doc-backlink">Making <code class="docutils literal notranslate"><span class="pre">Template</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> Into Protocols</a></h3> <p>An early version of this PEP proposed that the <code class="docutils literal notranslate"><span class="pre">Template</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> types be runtime checkable protocols rather than concrete types.</p> <p>In the end, we felt that using concrete types was more straightforward.</p> </section> <section id="an-additional-decoded-type"> <h3><a class="toc-backref" href="#an-additional-decoded-type" role="doc-backlink">An Additional <code class="docutils literal notranslate"><span class="pre">Decoded</span></code> Type</a></h3> <p>An early version of this PEP proposed an additional type, <code class="docutils literal notranslate"><span class="pre">Decoded</span></code>, to represent the “static string” parts of a template string. This type derived from <code class="docutils literal notranslate"><span class="pre">str</span></code> and had a single extra <code class="docutils literal notranslate"><span class="pre">raw</span></code> attribute that provided the original text of the string. We rejected this in favor of the simpler approach of using plain <code class="docutils literal notranslate"><span class="pre">str</span></code> and allowing combination of <code class="docutils literal notranslate"><span class="pre">r</span></code> and <code class="docutils literal notranslate"><span class="pre">t</span></code> prefixes.</p> </section> <section id="other-homes-for-template-and-interpolation"> <h3><a class="toc-backref" href="#other-homes-for-template-and-interpolation" role="doc-backlink">Other Homes for <code class="docutils literal notranslate"><span class="pre">Template</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code></a></h3> <p>Previous versions of this PEP proposed that the <code class="docutils literal notranslate"><span class="pre">Template</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> types be placed in the <code class="docutils literal notranslate"><span class="pre">types</span></code> module. This was rejected in favor of creating a new top-level standard library module, <code class="docutils literal notranslate"><span class="pre">templatelib</span></code>. This was done to avoid polluting the <code class="docutils literal notranslate"><span class="pre">types</span></code> module with seemingly unrelated types.</p> </section> <section id="enable-full-reconstruction-of-original-template-literal"> <h3><a class="toc-backref" href="#enable-full-reconstruction-of-original-template-literal" role="doc-backlink">Enable Full Reconstruction of Original Template Literal</a></h3> <p>Earlier versions of this PEP attempted to make it possible to fully reconstruct the text of the original template string from a <code class="docutils literal notranslate"><span class="pre">Template</span></code> instance. This was rejected as being overly complex.</p> <p>There are several limitations with respect to round-tripping to the original source text:</p> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre">Interpolation.format_spec</span></code> defaults to <code class="docutils literal notranslate"><span class="pre">""</span></code> if not provided. It is therefore impossible to distinguish <code class="docutils literal notranslate"><span class="pre">t"{expr}"</span></code> from <code class="docutils literal notranslate"><span class="pre">t"{expr:}"</span></code>.</li> <li>The debug specifier, <code class="docutils literal notranslate"><span class="pre">=</span></code>, is treated as a special case. It is therefore not possible to distinguish <code class="docutils literal notranslate"><span class="pre">t"{expr=}"</span></code> from <code class="docutils literal notranslate"><span class="pre">t"expr={expr}"</span></code>.</li> <li>Finally, format specifiers in f-strings allow arbitrary nesting. In this PEP and in the reference implementation, the specifier is eagerly evaluated to set the <code class="docutils literal notranslate"><span class="pre">format_spec</span></code> in the <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code>, thereby losing the original expressions. For example:</li> </ul> <div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">value</span> <span class="o">=</span> <span class="mi">42</span> <span class="n">precision</span> <span class="o">=</span> <span class="mi">2</span> <span class="n">template</span> <span class="o">=</span> <span class="n">t</span><span class="s2">"Value: {value:.</span><span class="si">{precision}</span><span class="s2">f}"</span> <span class="k">assert</span> <span class="n">template</span><span class="o">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">format_spec</span> <span class="o">==</span> <span class="s2">".2f"</span> </pre></div> </div> <p>We do not anticipate that these limitations will be a significant issue in practice. Developers who need to obtain the original template string literal can always use <code class="docutils literal notranslate"><span class="pre">inspect.getsource()</span></code> or similar tools.</p> </section> <section id="disallowing-string-concatenation"> <h3><a class="toc-backref" href="#disallowing-string-concatenation" role="doc-backlink">Disallowing String Concatenation</a></h3> <p>Earlier versions of this PEP proposed that template strings should not support concatenation. This was rejected in favor of allowing concatenation.</p> <p>There are reasonable arguments in favor of rejecting one or all forms of concatenation: namely, that it cuts off a class of potential bugs, particularly when one takes the view that template strings will often contain complex grammars for which concatenation doesn’t always have the same meaning (or any meaning).</p> <p>Moreover, the earliest versions of this PEP proposed a syntax closer to JavaScript’s tagged template literals, where an arbitrary callable could be used as a prefix to a string literal. There was no guarantee that the callable would return a type that supported concatenation.</p> <p>In the end, we decided that the surprise to developers of a new string type <em>not</em> supporting concatenation was likely to be greater than the theoretical harm caused by supporting it. (Developers concatenate f-strings all the time, after all, and while we are sure there are cases where this introduces bugs, it’s not clear that those bugs outweigh the benefits of supporting concatenation.)</p> <p>While concatenation is supported, we expect that code that uses template strings will more commonly build up larger templates through nesting and composition rather than concatenation.</p> </section> <section id="arbitrary-conversion-values"> <h3><a class="toc-backref" href="#arbitrary-conversion-values" role="doc-backlink">Arbitrary Conversion Values</a></h3> <p>Python allows only <code class="docutils literal notranslate"><span class="pre">r</span></code>, <code class="docutils literal notranslate"><span class="pre">s</span></code>, or <code class="docutils literal notranslate"><span class="pre">a</span></code> as possible conversion type values. Trying to assign a different value results in <code class="docutils literal notranslate"><span class="pre">SyntaxError</span></code>.</p> <p>In theory, template functions could choose to handle other conversion types. But this PEP adheres closely to <a class="pep reference internal" href="../pep-0701/" title="PEP 701 – Syntactic formalization of f-strings">PEP 701</a>. Any changes to allowed values should be in a separate PEP.</p> </section> <section id="removing-conv-from-interpolation"> <h3><a class="toc-backref" href="#removing-conv-from-interpolation" role="doc-backlink">Removing <code class="docutils literal notranslate"><span class="pre">conv</span></code> From <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code></a></h3> <p>During the authoring of this PEP, we considered removing the <code class="docutils literal notranslate"><span class="pre">conv</span></code> attribute from <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> and specifying that the conversion should be performed eagerly, before <code class="docutils literal notranslate"><span class="pre">Interpolation.value</span></code> is set.</p> <p>This was done to simplify the work of writing template processing code. The <code class="docutils literal notranslate"><span class="pre">conv</span></code> attribute is of limited extensibility (it is typed as <code class="docutils literal notranslate"><span class="pre">Literal["r",</span> <span class="pre">"s",</span> <span class="pre">"a"]</span> <span class="pre">|</span> <span class="pre">None</span></code>). It is not clear that it adds significant value or flexibility to template strings that couldn’t better be achieved with custom format specifiers. Unlike with format specifiers, there is no equivalent to Python’s <a class="reference external" href="https://docs.python.org/3/library/functions.html#format" title="(in Python v3.13)"><code class="docutils literal notranslate"><span class="pre">format()</span></code></a> built-in. (Instead, we include an sample implementation of <code class="docutils literal notranslate"><span class="pre">convert()</span></code> in the <a class="reference internal" href="#examples">Examples</a> section.)</p> <p>Ultimately we decided to keep the <code class="docutils literal notranslate"><span class="pre">conv</span></code> attribute in the <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> type to maintain compatibility with f-strings and to allow for future extensibility.</p> </section> <section id="alternate-interpolation-symbols"> <h3><a class="toc-backref" href="#alternate-interpolation-symbols" role="doc-backlink">Alternate Interpolation Symbols</a></h3> <p>In the early stages of this PEP, we considered allowing alternate symbols for interpolations in template strings. For example, we considered allowing <code class="docutils literal notranslate"><span class="pre">${name}</span></code> as an alternative to <code class="docutils literal notranslate"><span class="pre">{name}</span></code> with the idea that it might be useful for i18n or other purposes. See the <a class="reference external" href="https://discuss.python.org/t/pep-750-tag-strings-for-writing-domain-specific-languages/60408/122">Discourse thread</a> for more information.</p> <p>This was rejected in favor of keeping t-string syntax as close to f-string syntax as possible.</p> </section> <section id="a-lazy-conversion-specifier"> <h3><a class="toc-backref" href="#a-lazy-conversion-specifier" role="doc-backlink">A Lazy Conversion Specifier</a></h3> <p>We considered adding a new conversion specifier, <code class="docutils literal notranslate"><span class="pre">!()</span></code>, that would explicitly wrap the interpolation expression in a lambda.</p> <p>This was rejected in favor of the simpler approach of using explicit lambdas when lazy evaluation is desired.</p> </section> <section id="alternate-layouts-for-template-args"> <h3><a class="toc-backref" href="#alternate-layouts-for-template-args" role="doc-backlink">Alternate Layouts for <code class="docutils literal notranslate"><span class="pre">Template.args</span></code></a></h3> <p>During the development of this PEP, we considered several alternate layouts for the <code class="docutils literal notranslate"><span class="pre">args</span></code> attribute of the <code class="docutils literal notranslate"><span class="pre">Template</span></code> type. This included:</p> <ul class="simple"> <li>Instead of <code class="docutils literal notranslate"><span class="pre">args</span></code>, <code class="docutils literal notranslate"><span class="pre">Template</span></code> contains a <code class="docutils literal notranslate"><span class="pre">strings</span></code> attribute of type <code class="docutils literal notranslate"><span class="pre">Sequence[str]</span></code> and an <code class="docutils literal notranslate"><span class="pre">interpolations</span></code> attribute of type <code class="docutils literal notranslate"><span class="pre">Sequence[Interpolation]</span></code>. There are zero or more interpolations and there is always one more string than there are interpolations. Utility code could build an interleaved sequence of strings and interpolations from these separate attributes. This was rejected as being overly complex.</li> <li><code class="docutils literal notranslate"><span class="pre">args</span></code> is typed as a <code class="docutils literal notranslate"><span class="pre">Sequence[tuple[str,</span> <span class="pre">Interpolation</span> <span class="pre">|</span> <span class="pre">None]]</span></code>. Each static string is paired with is neighboring interpolation. The final string part has no corresponding interpolation. This was rejected as being overly complex.</li> <li><code class="docutils literal notranslate"><span class="pre">args</span></code> remains a <code class="docutils literal notranslate"><span class="pre">Sequence[str</span> <span class="pre">|</span> <span class="pre">Interpolation]</span></code> but does not support interleaving. As a result, empty strings are not added to the sequence. It is no longer possible to obtain static strings with <code class="docutils literal notranslate"><span class="pre">args[::2]</span></code>; instead, instance checks or structural pattern matching must be used to distinguish between strings and interpolations. We believe this approach is easier to explain and, at first glance, more intuitive. However, it was rejected as offering less future opportunty for performance optimization. We also believe that <code class="docutils literal notranslate"><span class="pre">args[::2]</span></code> may prove to be a useful shortcut in template processing code.</li> </ul> </section> <section id="mechanism-to-describe-the-kind-of-template"> <h3><a class="toc-backref" href="#mechanism-to-describe-the-kind-of-template" role="doc-backlink">Mechanism to Describe the “Kind” of Template</a></h3> <p>If t-strings prove popular, it may be useful to have a way to describe the “kind” of content found in a template string: “sql”, “html”, “css”, etc. This could enable powerful new features in tools such as linters, formatters, type checkers, and IDEs. (Imagine, for example, <code class="docutils literal notranslate"><span class="pre">black</span></code> formatting HTML in t-strings, or <code class="docutils literal notranslate"><span class="pre">mypy</span></code> checking whether a given attribute is valid for an HTML tag.) While exciting, this PEP does not propose any specific mechanism. It is our hope that, over time, the community will develop conventions for this purpose.</p> </section> </section> <section id="acknowledgements"> <h2><a class="toc-backref" href="#acknowledgements" role="doc-backlink">Acknowledgements</a></h2> <p>Thanks to Ryan Morshead for contributions during development of the ideas leading to template strings. Special mention also to Dropbox’s <a class="reference external" href="https://github.com/dropbox/pyxl">pyxl</a> for tackling similar ideas years ago. Finally, thanks to Joachim Viide for his pioneering work on the <a class="reference external" href="https://github.com/jviide/tagged">tagged library</a>. Tagged was not just the precursor to template strings, but the place where the whole effort started via a GitHub issue comment!</p> </section> <section id="copyright"> <h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2> <p>This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.</p> </section> </section> <hr class="docutils" /> <p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0750.rst">https://github.com/python/peps/blob/main/peps/pep-0750.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0750.rst">2024-10-22 09:28:54 GMT</a></p> </article> <nav id="pep-sidebar"> <h2>Contents</h2> <ul> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#relationship-with-other-peps">Relationship With Other PEPs</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#template-string-literals">Template String Literals</a></li> <li><a class="reference internal" href="#the-template-type">The <code class="docutils literal notranslate"><span class="pre">Template</span></code> Type</a></li> <li><a class="reference internal" href="#the-interpolation-type">The <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> Type</a></li> <li><a class="reference internal" href="#processing-template-strings">Processing Template Strings</a></li> <li><a class="reference internal" href="#template-string-concatenation">Template String Concatenation</a></li> <li><a class="reference internal" href="#template-and-interpolation-equality">Template and Interpolation Equality</a></li> <li><a class="reference internal" href="#no-support-for-ordering">No Support for Ordering</a></li> <li><a class="reference internal" href="#support-for-the-debug-specifier">Support for the debug specifier (<code class="docutils literal notranslate"><span class="pre">=</span></code>)</a></li> <li><a class="reference internal" href="#raw-template-strings">Raw Template Strings</a></li> <li><a class="reference internal" href="#interpolation-expression-evaluation">Interpolation Expression Evaluation</a></li> <li><a class="reference internal" href="#exceptions">Exceptions</a></li> <li><a class="reference internal" href="#interleaving-of-template-args">Interleaving of <code class="docutils literal notranslate"><span class="pre">Template.args</span></code></a></li> </ul> </li> <li><a class="reference internal" href="#examples">Examples</a><ul> <li><a class="reference internal" href="#example-implementing-f-strings-with-t-strings">Example: Implementing f-strings with t-strings</a></li> <li><a class="reference internal" href="#example-structured-logging">Example: Structured Logging</a><ul> <li><a class="reference internal" href="#approach-1-custom-log-messages">Approach 1: Custom Log Messages</a></li> <li><a class="reference internal" href="#approach-2-custom-formatters">Approach 2: Custom Formatters</a></li> </ul> </li> <li><a class="reference internal" href="#example-html-templating">Example: HTML Templating</a></li> </ul> </li> <li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li> <li><a class="reference internal" href="#security-implications">Security Implications</a></li> <li><a class="reference internal" href="#how-to-teach-this">How To Teach This</a></li> <li><a class="reference internal" href="#common-patterns-seen-in-processing-templates">Common Patterns Seen in Processing Templates</a><ul> <li><a class="reference internal" href="#structural-pattern-matching">Structural Pattern Matching</a></li> <li><a class="reference internal" href="#memoizing">Memoizing</a></li> <li><a class="reference internal" href="#parsing-to-intermediate-representations">Parsing to Intermediate Representations</a></li> <li><a class="reference internal" href="#context-sensitive-processing-of-interpolations">Context-sensitive Processing of Interpolations</a></li> <li><a class="reference internal" href="#nested-template-strings">Nested Template Strings</a></li> <li><a class="reference internal" href="#approaches-to-lazy-evaluation">Approaches to Lazy Evaluation</a></li> <li><a class="reference internal" href="#approaches-to-asynchronous-evaluation">Approaches to Asynchronous Evaluation</a></li> <li><a class="reference internal" href="#approaches-to-template-reuse">Approaches to Template Reuse</a></li> </ul> </li> <li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li> <li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul> <li><a class="reference internal" href="#arbitrary-string-literal-prefixes">Arbitrary String Literal Prefixes</a></li> <li><a class="reference internal" href="#delayed-evaluation-of-interpolations">Delayed Evaluation of Interpolations</a></li> <li><a class="reference internal" href="#making-template-and-interpolation-into-protocols">Making <code class="docutils literal notranslate"><span class="pre">Template</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code> Into Protocols</a></li> <li><a class="reference internal" href="#an-additional-decoded-type">An Additional <code class="docutils literal notranslate"><span class="pre">Decoded</span></code> Type</a></li> <li><a class="reference internal" href="#other-homes-for-template-and-interpolation">Other Homes for <code class="docutils literal notranslate"><span class="pre">Template</span></code> and <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code></a></li> <li><a class="reference internal" href="#enable-full-reconstruction-of-original-template-literal">Enable Full Reconstruction of Original Template Literal</a></li> <li><a class="reference internal" href="#disallowing-string-concatenation">Disallowing String Concatenation</a></li> <li><a class="reference internal" href="#arbitrary-conversion-values">Arbitrary Conversion Values</a></li> <li><a class="reference internal" href="#removing-conv-from-interpolation">Removing <code class="docutils literal notranslate"><span class="pre">conv</span></code> From <code class="docutils literal notranslate"><span class="pre">Interpolation</span></code></a></li> <li><a class="reference internal" href="#alternate-interpolation-symbols">Alternate Interpolation Symbols</a></li> <li><a class="reference internal" href="#a-lazy-conversion-specifier">A Lazy Conversion Specifier</a></li> <li><a class="reference internal" href="#alternate-layouts-for-template-args">Alternate Layouts for <code class="docutils literal notranslate"><span class="pre">Template.args</span></code></a></li> <li><a class="reference internal" href="#mechanism-to-describe-the-kind-of-template">Mechanism to Describe the “Kind” of Template</a></li> </ul> </li> <li><a class="reference internal" href="#acknowledgements">Acknowledgements</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> <br> <a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0750.rst">Page Source (GitHub)</a> </nav> </section> <script src="../_static/colour_scheme.js"></script> <script src="../_static/wrap_tables.js"></script> <script src="../_static/sticky_banner.js"></script> </body> </html>