[HN Gopher] PEP 750 - Template Strings (t-strings) have been acc...
       ___________________________________________________________________
        
       PEP 750 - Template Strings (t-strings) have been accepted
        
       Author : grep_it
       Score  : 237 points
       Date   : 2025-04-10 20:24 UTC (2 hours ago)
        
 (HTM) web link (peps.python.org)
 (TXT) w3m dump (peps.python.org)
        
       | pphysch wrote:
       | This could be a game-changer for applications that involve lots
       | of HTML, SQL, or other languages that tend to be embedded within
       | Python programs. Frankly, HTML templating is the worst part of
       | Python webdev DX for me.
       | 
       | Excited to see what libraries and tooling comes out of this.
        
         | ohnoesjmr wrote:
         | Whats wrong with jinja?
        
           | pphysch wrote:
           | Jinja et al. are fine. But they still forces me to choose
           | Python OR HTML. I have to jump between files and LSPs.
           | t-strings pave the way for true Python+HTML seamless DX.
        
         | paulddraper wrote:
         | These developer ergonomics reduce injection 90% in my
         | experience.
         | 
         | When concatenating strings is the _harder_ approach, it is
         | really beautiful.
        
         | neilv wrote:
         | Agreed, although this doesn't work as well for HTML or SQL that
         | doesn't fit on one line. Since having everything in a string
         | literal is a little messy, and doesn't have the benefit of
         | autoindent, syntax coloring, etc.
         | 
         | This is one place where s-expressions of Lisp make embedding
         | these DSLs syntactically easier.
         | 
         | To borrow the PEP's HTML examples:                   #lang
         | racket/base         (require html-template)
         | (define evil "<script>alert('evil')</script>")
         | (html-template (p (% evil)))              ;
         | <p>&lt;script&gt;alert('evil')&lt;/script&gt;</p>
         | (define attributes '((src "shrubbery.jpg") (alt "looks nice")))
         | (html-template (img (@ (%sxml attributes))))              ;
         | <img src="shrubbery.jpg" alt="looks nice">
         | 
         | You can see how the parentheses syntax will practically scale
         | better, to a larger and more complex mix of HTML and host
         | language expressions. (A multi-line example using the normal
         | text editor autoindent is on "https://docs.racket-
         | lang.org/html-template/".)
        
           | davepeck wrote:
           | > Agreed, although this doesn't work as well for HTML or SQL
           | that doesn't fit on one line.
           | 
           | PEP 750 t-strings literals work with python's tripe-quote
           | syntax (and its lesser-used implicit string concat syntax):
           | lots_of_html = t"""            <div>               <main>
           | <h1>Hello</h1>               </main>            </div>
           | """
           | 
           | My hope is that we'll quickly see the tooling ecosystem catch
           | up and -- just like in JavaScript-land -- support syntax
           | coloring and formatting specific types of content in
           | t-strings, like HTML.
        
             | neilv wrote:
             | Yeah, editor support will help a lot. Though the PEP's way
             | of doing things doesn't specify the language of the
             | template where the literal occurs, so detection of that
             | might have to be kludged.
             | 
             | (Or, in a sufficiently working program, an editor with
             | semantic analysis access could use something like type
             | inference in the Python side, to determine the language in
             | a less-kludgey way.)
        
               | davepeck wrote:
               | > doesn't specify the language of the template where the
               | literal occurs
               | 
               | Yeah, something we spent a bunch of time considering. In
               | the end, we decided it probably needed to stay out of
               | scope for the PEP.
               | 
               | You're right that JavaScript has an easier time here.
               | Most of the JS tools we looked at simply inspect the name
               | of the tag and if it's (say) html, they attempt to
               | color/format string content as HTML regardless of what
               | the html() function actually does or the string's
               | contents.
               | 
               | Currently, tools like black have no knowledge of types.
               | I'm guessing some amount of kludging is to be expected on
               | day one. But my hope is over the long term, we'll see a
               | story emerge for how annotations can indicate the
               | expected content type.
        
       | kstrauser wrote:
       | Most excellent! I love f-strings and replaced all the various
       | other string interpolation instances in my code with them, but
       | they have the significant issue that you can't defer evaluating
       | them. For instance, you can write:                 >>> template =
       | 'Hello, {name}'       >>> template.format(name='Bob')
       | 'Hello, Bob'
       | 
       | Until this, there wasn't a way to use f-strings formatting
       | without interpolating the results at that moment:
       | >>> template = f'Hello, {name}'       Traceback (most recent call
       | last):         File "<python-input-5>", line 1, in <module>
       | template = f'Hello, {name}'                                ^^^^
       | NameError: name 'name' is not defined
       | 
       | It was annoying being able to use f-strings almost everywhere,
       | but str.format in enough odd corners that you have to put up with
       | it.
        
         | ossopite wrote:
         | I'm not sure if t-strings help here? unless I misread the PEP,
         | it seems like they still eagerly evaluate the interpolations.
         | 
         | There is an observation that you can use `lambda` inside to
         | delay evaluation of an interpolation, but I think this lambda
         | captures any variables it uses from the context.
        
           | tylerhou wrote:
           | You could do something like t"Hello, {"name"}" (or wrap
           | "name" in a class to make it slightly less hacky).
        
           | notpushkin wrote:
           | > An early version of this PEP proposed that interpolations
           | should be lazily evaluated. [...] This was rejected for
           | several reasons [...]
           | 
           | Bummer. This could have been so useful:
           | statement_endpoint: Final =
           | "/api/v2/accounts/{iban}/statement"
           | 
           | (Though str.format isn't really that bad here either.)
        
             | LordShredda wrote:
             | Would be useful in that exact case, but would be an
             | absolute nightmare to debug, on par with using global
             | variables as function inputs
        
               | notpushkin wrote:
               | Yeah, to be honest, every time this comes to mind I think
               | "wow, this would be really neat!", then realize just
               | using .format() explicitly is way easier to read.
        
             | nhumrich wrote:
             | There was a very very long discussion on this point alone,
             | and there are a lot of weird edge cases, and led to weird
             | syntax things. The high level idea was to defer lazy eval
             | to a later PEP if its still needed enough.
             | 
             | There are a lot of existing workarounds in the discussions
             | if you are interested enough in using it, such as using
             | lambdas and t-strings together.
        
           | davepeck wrote:
           | > I'm not sure if t-strings help here?
           | 
           | That's correct, they don't. Evaluation of t-string
           | expressions is immediate, just like with f-strings.
           | 
           | Since we have the full generality of Python at our disposal,
           | a typical solution is to simply wrap your t-string in a
           | function or a lambda.
           | 
           | (An early version of the PEP had tools for deferred
           | evaluation but these were dropped for being too complex,
           | particularly for a first cut.)
        
             | krick wrote:
             | And that actually makes "Template Strings" a misnomer in my
             | mind. I mean, deferred (and repeated) evaluation of a
             | template is _the_ thing that makes template a template.
             | 
             | Kinda messy PEP, IMO, I'm less excited by it than I'd like
             | to be. The goal is clear, but the whole design feels
             | backwards.
        
               | davepeck wrote:
               | Naming is hard; for instance, JavaScript also has its own
               | template strings, which are also eagerly evaluated.
        
           | actinium226 wrote:
           | > There is an observation that you can use `lambda` inside to
           | delay evaluation of an interpolation, but I think this lambda
           | captures any variables it uses from the context.
           | 
           | Actually lambda works fine here                   >>> name =
           | 'Sue'         >>> template = lambda name: f'Hello {name}'
           | >>> template('Bob')         'Hello Bob'
        
           | skeledrew wrote:
           | Lambda only captures variables which haven't been passed in
           | as argument.
        
         | ratorx wrote:
         | Delayed execution is basically equivalent to a function call,
         | which is already a thing. It also has basically the same API as
         | point of use and requires maybe 1 extra line.
        
         | foobahify wrote:
         | The issue was solved by having a rich and Turing complete
         | language. I am not huge on adding language features. This seems
         | like userland stuff.
        
           | actinium226 wrote:
           | I tend to agree. I think it's easy enough to use a lambda in
           | this case                   >>> template = lambda name:
           | f'Hello {name}'         >>> template('Bob')
        
         | a3w wrote:
         | I read 'most excellent' in the Bill and Ted voice
        
           | kstrauser wrote:
           | Then it worked.
        
         | davepeck wrote:
         | PEP 750 doesn't directly address this because it's
         | straightforward to simply wrap template creation in a function
         | (or a lambda, if that's your style):                   def
         | my_template(name: str) -> Template:             return t"Hello,
         | {name}"
        
       | simonw wrote:
       | I'm excited about this. I really like how JavaScript's tagged
       | template literals https://developer.mozilla.org/en-
       | US/docs/Web/JavaScript/Refe... can help handle things like
       | automatic HTML escaping or SQL parameterization, it looks like
       | these will bring the same capability to Python.
        
         | davepeck wrote:
         | Yes! PEP 750 landed exactly there: as a pythonic parallel to
         | JavaScript's tagged template strings. I'm hopeful that the
         | tooling ecosystem will catch up soon so we see syntax coloring,
         | formatting of specific t-string content types, etc. in the
         | future.
        
         | its-summertime wrote:
         | I just wish it didn't get Pythonified in the process, e.g.
         | needing to be a function call because backtick is hard to type
         | on some keyboards, nearly having a completely separate concept
         | of evaluating the arguments, etc. x`` vs x(t'') is a 2x blowup
         | in terms of line-noise at worst.
        
           | itishappy wrote:
           | I don't think that's the reason.
           | 
           | https://peps.python.org/pep-0750/#arbitrary-string-
           | literal-p...
        
       | unsnap_biceps wrote:
       | This is super exciting but I wish they added a function to render
       | the template to a string rather then having everyone write their
       | own version of basic rendering.
        
         | davepeck wrote:
         | One place we landed with PEP 750 is that Template instances
         | _have_ no natural string rendering.
         | 
         | That is, you _must_ process a Template in some way to get a
         | useful string out the other side. This is why
         | Template.__str__() is spec 'd to be the same as
         | Template.__repr__().
         | 
         | If you want to render a Template like an f-string for some
         | reason, the pep750 examples repo contains an implementation of
         | an `f(template: Template) -> str` method:
         | https://github.com/davepeck/pep750-examples/blob/main/pep/fs...
         | 
         | This could be revisited, for instance to add
         | `Template.format()` in the future.
        
         | nhumrich wrote:
         | PEP 501 allowed `.format()` to do this, but discussions on PEP
         | 750 felt that format shouldn't be needed, because when you need
         | a basic format, you should probably just use f-strings. But
         | maybe `__format__` can be added in in a future version of
         | python if this is really wanted.
        
       | ydnaclementine wrote:
       | can't wait to have my linter tell me I should be using t-strings
       | instead of f-strings. apologies for not being patient enough to
       | read through this to find it, but I hope they can remove
       | f-strings:
       | 
       | > There should be one-- and preferably only one --obvious way to
       | do it.
        
         | itishappy wrote:
         | They do different things. You can implement f-strings in
         | t-strings, but it's extra work. The obvious way is therefore:
         | 
         | Use f-strings if you can, otherwise use t-strings.
        
         | gnfedhjmm2 wrote:
         | Just use ChatGPT to change it.
        
       | behnamoh wrote:
       | Is it a replacement for Jinja2 templates? I use them a lot in LLM
       | pipelines (e.g., to fill in the system prompt and provide more
       | context).
        
         | netghost wrote:
         | If you just need value replacement yes. But you could also
         | already do that with str.format.
         | 
         | I think this gives you slightly more control before
         | interpolating.
         | 
         | If you want control flow inside a template, jinja and friends
         | are probably still useful.
        
         | nhumrich wrote:
         | No. Not really intended to be a replacement for jinja. But it
         | also depends on how you use jinja. If you use only very basic
         | functionality of jinja, maybe.
        
       | ratorx wrote:
       | I'm not convinced that a language level feature is worth it for
       | this. You could achieve the same thing with a function returning
       | an f-string no? And if you want injection safety, just use a tag
       | type and a sanitisation function that takes a string and returns
       | the type. Then the function returning the f-string could take the
       | Sanitised string as an argument to prevent calling it with
       | unsanitised input.
       | 
       | I guess it's more concise, but differentiating between eager and
       | delayed execution with a single character makes the language less
       | readable for people who are not as familiar with Python
       | (especially latest update syntax etc).
       | 
       | EDIT: to flesh out with an example:
       | 
       | class Sanitised(str): # init function that sanitises or just use
       | as a tag type that has an external sanitisation function.
       | 
       | def sqltemplate(name: Sanitised) -> str: return f"select * from
       | {name}"
       | 
       | # Usage sqltemplate(name=sanitise("some injection"))
       | 
       | # Attempt to pass unsanitised sqltemplate(name="some injection")
       | # type check error
        
         | vjerancrnjak wrote:
         | It's worse than function returning an f-string. Template type
         | is very flat, you won't know which arguments are left unbound.
         | 
         | modules, classes, protocols, functions returning functions, all
         | options in Python, each work well for reuse, no need to use
         | more than 2 at once, yet the world swims upstream.
        
           | itishappy wrote:
           | How do you leave arguments unbound?
        
             | davepeck wrote:
             | Yes, exactly. T-string arguments can't be unbound; they are
             | eagerly evaluated.
        
         | shikon7 wrote:
         | If its only use is to make injecton safety a bit easier to
         | achieve, it's worth it to me.
        
           | ratorx wrote:
           | Does it make it easier? The "escape" for both is to just use
           | unsafe version of the Template -> string function or
           | explicitly mark an unsafe string as sanitised. Both seem
           | similar in (un)safety
        
             | davepeck wrote:
             | > the Template -> string function
             | 
             | There is no such function; Template.__str__() returns
             | Template.__repr__() which is very unlikely to be useful.
             | You pretty much _have_ to process your Template instance in
             | some way before converting to a string.
        
               | ratorx wrote:
               | Right, but it is possible to write a template -> string
               | function that _doesn't_ sanitise and use it (or more
               | realistically use the wrong one). Just as it's possible
               | to unsafely cast an unsafe string to a sanitised one and
               | use it (rather than use a sanitise function that returns
               | the wrapper type).
               | 
               | They are both similar in their unsafety.
        
         | itishappy wrote:
         | I don't see how this prevents calling your returned f-string
         | with unsensitized inputs.                   evil =
         | "<script>alert('evil')</script>"         sanitized =
         | Sanitized(evil)         whoops = f"<p>{evil}</p>"
        
           | ratorx wrote:
           | I'm not sure you understood my example. The f-string is
           | within a function. The function argument only accepts
           | sanitised input type.
           | 
           | If you create a subclass of str which has an init function
           | that sanitises, then you can't create a Sanitised type by
           | casting right?
           | 
           | And even if you could, there is also nothing stopping you
           | from using a different function to "html" that just returns
           | the string without sanitising. They are on the same relative
           | level of safety.
        
             | itishappy wrote:
             | Oh, I'm pretty sure I didn't understand your example and am
             | probably missing something obvious. That's why I'm here
             | asking dumb questions!
             | 
             | I think I'm following more, and I see how you can
             | accomplish this by encapsulating the rendering, but I'm
             | still not seeing how this is possible with user facing
             | f-strings. Think you can write up a quick example?
        
               | ratorx wrote:
               | Added example to parent comment.
        
               | itishappy wrote:
               | Thanks mate! (BTW: Indenting code with four spaces makes
               | HN format it like code.)
               | 
               | So the thing I'm still not getting from your example is
               | allowing the template itself to be customized.
               | evil = "<script>alert('evil')</script>"        template1
               | = t"<p>{evil}</p>"        template2 = t"<h1>{evil}</h1>"
               | html(template1)        html(template2)
        
               | ratorx wrote:
               | template1 is a function that takes in a parameter evil
               | (with a SanitisedString type that wraps a regular str)
               | and returns the fully expanded str. It is implemented by
               | just returning an f-string equivalent to the t-string in
               | your example. Same with template2.
               | 
               | Using the SanitisedString type forces the user to
               | explicitly call a sanitiser function that returns a
               | SanitisedString and prevents them from passing in an
               | unsanitised str.
        
         | nhumrich wrote:
         | > You could achieve the same thing with a function returning an
         | f-string no no.
         | 
         | > just use a tag type and a sanitisation function that takes a
         | string and returns the type
         | 
         | Okay, so you have a `sqlstring(somestring)` function, and the
         | dev has to call it. But... what if they pass in an f-string?
         | 
         | `sqlstring(f'select from mytable where col = {value}')`
         | 
         | You havent actually prevented/enforced anything. With template
         | strings, its turtles all the way down. You can enforce they
         | pass in a template and you can safely escape anything that is a
         | variable because its impossible to have a variable type
         | (possible injection) in the template literal.
        
           | ratorx wrote:
           | Added example to parent comment.
           | 
           | This example still works, the entire f-string is sanitised
           | (including whatever the value of name was). Assuming
           | sqlstring is the sanitisation function.
           | 
           | The "template" would be a separate function that returns an
           | f-string bound from function arguments.
        
             | nhumrich wrote:
             | Yes. Only if your dev remembers to use sanatized all the
             | time. This is how most SQL works today. You could also
             | forget and accidentally write a f-string, or because you
             | dont know. But with t-strings you can actually prevent
             | unsanatized inputs. With your example, you need to
             | intentionally sanitize still.
             | 
             | You cant throw an error on unsanitized because the language
             | has no way to know if its sanitized or not. Either way, its
             | just a string. "returning an f-string" is equivalent to
             | returning a normal string at runtime.
        
               | ratorx wrote:
               | Well you enforce this with types. That's how every other
               | language does it. By specifying that the type of the
               | function has to be a sanitised string, it will reject
               | unsanitised string with the type checker.
               | 
               | > it has no way of knowing if it's sanitised or not
               | 
               | It does. You define the SanitisedString class.
               | Constructing one sanitises the string. Then when you
               | specify that as the argument, it forces the user to
               | sanitise the string.
               | 
               | If you want to do it without types, you can check with
               | `isinstance` at runtime, but that is not as safe.
        
               | nhumrich wrote:
               | Your example is a bit too simple. What I mean by that is,
               | you have hardcoded your function to inject a specific
               | part of your string. But t-strings allow you to write the
               | full query `t'select * from table where name = {name}'`
               | directly, without have to use a function. This matters
               | because the SQL connection library itself can enforce
               | templates. SQL libraries can NOT enforce "sanitized
               | types" because then you couldnt write raw sql without
               | problems. They have to know the difference between "this
               | is hard coded" and "this is a dynamic user variable". And
               | the libraries can't know that, without t-strings.
        
               | stefan_ wrote:
               | No, most SQL today uses placeholders and has since circa
               | 2008. If you are sanitizing you are doing it wrong to
               | begin with.
        
       | throwawayffffas wrote:
       | So we are well on our way to turning python to PHP.
       | 
       | Edit: Sorry I was snarky, its late here.
       | 
       | I already didn't like f-strings and t-strings just add complexity
       | to the language to fix a problem introduced by f-strings.
       | 
       | We really don't need more syntax for string interpolation, in my
       | opinion string.format is the optimal. I could even live with %
       | just because the syntax has been around for so long.
       | 
       | I'd rather the language team focus on more substantive stuff.
        
         | turtledragonfly wrote:
         | > turning python to PHP.
         | 
         | Why stop there? Go full Perl (:
         | 
         | I think Python needs more quoting operators, too. Maybe qq{}
         | qq() q// ...
         | 
         | [I say this as someone who actually likes Perl and chuckles
         | from afar at such Python developments. May you get there one
         | day!]
        
           | tdeck wrote:
           | Quoting operators are something I actually miss in Python
           | whereas t-strings are something I have never wanted in 17
           | years of writing Python.
        
         | nhumrich wrote:
         | Pretty sure PHP does not have this feature. Can you give me an
         | example?
        
           | fshr wrote:
           | I believe that jab was that PHP has a bunch of ways to do
           | similar things and Python, in their view, is turning out that
           | way, too.
        
             | throwawayffffas wrote:
             | On a more philosophical level php is this feature. At least
             | as it was used originally and how it's mostly used today.
             | PHP was and is embedded in html code. If you have a look at
             | a wordpress file you are going to see something like this:
             | 
             | <?php ... ?><some_markup>...<? php ... ?><some_more_markup
             | here>...
        
       | sgarland wrote:
       | Am I missing something, or is this a fancier string.Template [0]?
       | Don't get me wrong, it looks very useful, especially the
       | literals.
       | 
       | [0]: https://docs.python.org/3/library/string.html#template-
       | strin...
        
       | btilly wrote:
       | I dislike this feature.
       | 
       | The stated use case is to avoid injection attacks. However the
       | primary reason why injection attacks work is that the easiest way
       | to write the code makes it vulnerable to injection attacks. This
       | remains true, and so injection attacks will continue to happen.
       | 
       | Templates offer to improve this by adding interpolations, which
       | are able to do things like escaping. However the code for said
       | interpolations is now located at some distance from the template.
       | You therefore get code that locally looks good, even if it has
       | security mistakes. Instead of one source of error - the developer
       | interpolated - you now have three. The developer forgot to
       | interpolate, the developer chose the wrong interpolation, or the
       | interpolation itself got it wrong. We now have more sources of
       | error, and more action at a distance. Which makes it harder to
       | audit the code for sources of potential error.
       | 
       | This is something I've observed over my life. Developers don't
       | notice the cognitive overhead of all of the abstractions that
       | they have internalized. Therefore over time they add more. This
       | results in code that works "by magic". And serious problems if
       | the magic doesn't quite work in the way that developers are
       | relying on.
       | 
       | Templates are yet another step towards "more magic". With
       | predictable consequences down the road.
        
         | gnfedhjmm2 wrote:
         | It's kinda like saying += is magic. Yeah the best kind.
        
           | sodality2 wrote:
           | That's such a small scope of an addition I don't see the
           | comparison. I suppose overloading arbitrary types with it can
           | sometimes make the actions performed opaque.
        
         | davepeck wrote:
         | I'm not sure I agree with this analysis.
         | 
         | Template.__str__() is equivalent to Template.__repr__(), which
         | is to say that these aren't f-strings in an important sense:
         | you _can 't_ get a useful string out of them until you process
         | them in some way.
         | 
         | The expectation is that developers will typically make use of
         | well-established libraries that build on top of t-strings. For
         | instance, developers might grab a package that provides an
         | html() function that accepts Template instances and returns
         | some Element type, which can then be safely converted into a
         | string.
         | 
         | Stepping back, t-strings are a pythonic parallel to
         | JavaScript's tagged template strings. They have many of the
         | same advantages and drawbacks.
        
         | nhumrich wrote:
         | Libraries can enforce only template strings, and properly
         | escape the output. This is already possible in Javascript, and
         | you can completely prevent injection attacks using it. > The
         | developer forgot to interpolate not possible if you enforce
         | only templates
         | 
         | > the developer chose the wrong interpolation Not possible if
         | the library converts from template to interpolation itself
         | 
         | > or the interpolation itself got it wrong Sure, but that would
         | be library code.
        
       | nhumrich wrote:
       | Nick Humrich here, the author who helped rewrite PEP 501 to
       | introduce t-strings, which was the foundation for this PEP. I am
       | not an author on this accepted PEP, but I know this PEP and story
       | pretty well. Let me know if you have any questions.
       | 
       | I am super excited this is finally accepted. I started working on
       | PEP 501 4 years ago.
        
         | _cs2017_ wrote:
         | Thank you! Curious what options for deferred evalution were
         | considered and rejected? IMHO, the main benefit of deferred
         | evaluation isn't in the saving of a bit of code to define a
         | deferred evaluation class, but in standardazing the API so that
         | anyone can read the code without having to learn what it means
         | in each project.
         | 
         | Also: were prompt templates for LLM prompt chaining a use case
         | that influenced the design in any way (examples being LangChain
         | and dozens of other libraries with similar functionlity)?
        
           | nhumrich wrote:
           | One solution that existed for a while was using the `!`
           | operator for deferred. `t!'my defered {str}'`
           | 
           | The main reason for non having deferred evaluation was that
           | it over-complicated the feature quite a bit and introduces a
           | rune. Deferred evaluation also has the potential to
           | dramatically increase complexity for beginners in the
           | language, as it can be confusing to follow if you dont know
           | what is going on. Which means "deferred by default" wasnt
           | going to be accepted.
           | 
           | As for LLM's, it was not the main consideration, as the PEP
           | process here started before LLM's were popular.
        
           | davepeck wrote:
           | > were prompt templates for LLM prompt chaining a use case
           | that influenced the design in any way
           | 
           | Maybe not directly, but the Python community is full of LLM
           | users and so I think there's a general awareness of the
           | issues.
        
             | andy99 wrote:
             | Is there an example of how these could be used in LLM
             | prompting?
        
         | Waterluvian wrote:
         | I often read concerns that complexity keeps being added to the
         | language with yet another flavour of string or whatnot. Given
         | that those who author and deliberate on PEPs are, kind of by
         | definition, experts who spend a lot of time with the language,
         | they might struggle to grok the Python experience from the
         | perspective of a novice or beginner. How does the PEP process
         | guard against this bias?
        
           | davepeck wrote:
           | You might find the Python discussion forums ([0] and [1])
           | interesting; conversation that guides the evolution of PEPs
           | happens there.
           | 
           | As Nick mentioned, PEP 750 had a long and winding road to its
           | final acceptance; as the process wore on, and the
           | complexities of the earliest cuts of the PEPs were
           | reconsidered, the two converged.
           | 
           | [0] The very first announcement:
           | https://discuss.python.org/t/pep-750-tag-strings-for-
           | writing...
           | 
           | [1] Much later in the PEP process:
           | https://discuss.python.org/t/pep750-template-strings-new-
           | upd...
        
           | nhumrich wrote:
           | All discussion on PEP's happens in public forums where anyone
           | can opine on things before they are accepted. I agree that
           | the experts are more likely to participate in this exchange.
           | And while this is wish-washy, I feel like the process is
           | really intended to benefit the experts more than the novices
           | anyways.
           | 
           | There have been processes put into place in recent years to
           | try to curb the difficulty of things. One of those is that
           | all new PEPs have to include a "how can you teach this to
           | beginers" section, as seen here on this pep:
           | https://peps.python.org/pep-0750/#how-to-teach-this
        
             | Waterluvian wrote:
             | I think "how can you teach this to beginners?" is a
             | fantastic, low-hanging fruit option for encouraging the
             | wizards to think about that very important type of user.
             | 
             | Other than a more broad "how is the language as a whole
             | faring?" test, which might be done through surveys or other
             | product-style research, I think this is just plainly a hard
             | problem to approach, just by the nature that it's largely
             | about user experience.
        
           | jackpirate wrote:
           | Building off this question, it's not clear to me why Python
           | should have both t-strings and f-strings. The difference
           | between the two seems like a stumbling block to new
           | programmers, and my "ideal python" would have only one of
           | these mechanisms.
        
             | davepeck wrote:
             | For one thing, `f"something"` is of type `str`;
             | `t"something"` is of type `string.templatelib.Template`.
        
               | all2 wrote:
               | The types aren't so important. __call__ or reference
               | returns type string, an f and a t will be interchangeable
               | from the consumer side.
               | 
               | Example, if you can go through (I'm not sure you can) and
               | trivially replace all your fs with ts, and then have some
               | minor fixups where the final product is used, I don't
               | think a migration from one to the other would be terribly
               | painful. Time-consuming, yes.
        
             | nhumrich wrote:
             | f-strings immediately become a string, and are "invisible"
             | to the runtime from a normal string. t-strings introduce an
             | object so that libraries can do custom logic/formatting on
             | the template strings, such as decided _how_ to format the
             | string.
             | 
             | My main motivation as an author of 501 was to ensure user
             | input is properly escaped when inserting into sql, which
             | you cant enforce with f-strings.
        
             | skeledrew wrote:
             | Give it a few years to when f-string usage has worn off to
             | the point that a decision can be made to remove it without
             | breaking a significant number of projects in the wild.
        
               | milesrout wrote:
               | That will never happen.
        
               | skeledrew wrote:
               | Well if it continues to be popular then that is all good.
               | Just keep it. What matters is that usage isn't complex
               | for anyone.
        
               | macNchz wrote:
               | Well now we'll have four different ways to format
               | strings, since removing old ones is something that
               | doesn't actually happen:                   "foo %s" %
               | "bar"         "foo {}".format("bar")         bar = "bar";
               | f"foo {bar}"         bar = "bar"; t"foo {bar}" # has
               | extra functionality!
        
         | patrec wrote:
         | My memory is that ES6's template strings preceded f-strings. If
         | that is correct, do you happen to know why python was saddled
         | with f-strings, which seem like an obviously inferior design,
         | in the first place? We are now at five largely redundant string
         | interpolation systems (%, .format, string.Template, f-string,
         | t-string).
        
           | nhumrich wrote:
           | PEP 501 when originally written (not by me) was intended to
           | be the competing standard against f-strings, and to have been
           | more inline with ES6's template strings. There was debate
           | between the more simple f-string PEP (PEP 498) and PEP 501.
           | Ultimately, it was decided to go with f-strings as a less
           | confusing, more approachable version (and also easier to
           | implement) and to "defer" PEP 501 to "see what happens".
           | Since then, the python internal have also changed, allowing
           | t-strings to be even easier to implement (See PEP 701). We
           | have seen what happens, and now its introduced. f-strings and
           | t-strings are not competing systems. They are different.
           | Similar to ES6 templates and namedTaggedTemplates, they are
           | used for different things while API feels similar
           | intentionally. f-strings are not inferior to t-strings, they
           | are better for most use cases of string templating where what
           | you really want, is just a string.
        
         | bjourne wrote:
         | Does Python really need yet another type of string literal? I
         | feel like while templating is a good addition to the standard
         | library, it's not something that needs syntactic support.
         | t"blah blah" is just an alias for Template("blah blah",
         | context), isn't it?
        
       | pjmlp wrote:
       | Yet another way to do strings in Python, I was more than happy
       | with the original way with tupple parameters.
        
       | meisel wrote:
       | Aside from sanitization, this also allows replication of Ruby's
       | %W[...] syntax
        
       | AlienRobot wrote:
       | Thanks but I still use "%s" % (a,) the way I learned a dozen
       | years ago and I'll keep doing it until the day I die.
        
         | nhumrich wrote:
         | Good for you.
        
       | pgjones wrote:
       | If you want to see a usage for this I've built, and use, [SQL-
       | tString](https://github.com/pgjones/sql-tstring) as an SQL
       | builder.
        
       | actinium226 wrote:
       | So does this mean that any place where code exists that looks for
       | `type(someinstance) == str` will break because the type will be
       | `Template` even though `someinstance` could still be used in the
       | following code?
        
         | nhumrich wrote:
         | yes. t-strings are not `str`
        
       | wruza wrote:
       | As a python meh-er, this is actually good design. Everyone is
       | jumping on C that it has no strings, but then other languages
       | throw raw strings at you with some interpolation and call it a
       | day. Also it's 2025 and people will still comment "do we need
       | such a bloated string mechanism" and then watch new devs produce
       | bulks of injectionable strings.
        
       | fmajid wrote:
       | These templates don't seem to be semantically aware like Go's
       | html/template that takes care of mitigating XSS for you, among
       | other things.
        
         | nhumrich wrote:
         | Correct. Intended for library authors to do that. A SQL
         | library, for example, could accept a template type and mitigate
         | against SQL injection for you.
        
       | pansa2 wrote:
       | Putting aside template strings themselves for the moment, I'm
       | stunned by some of the code in this PEP. It's so verbose! For
       | example, "Implementing f-strings with t-strings":
       | def f(template: Template) -> str:             parts = []
       | for item in template:                 match item:
       | case str() as s:                         parts.append(s)
       | case Interpolation(value, _, conversion, format_spec):
       | value = convert(value, conversion)                         value
       | = format(value, format_spec)
       | parts.append(value)             return "".join(parts)
       | 
       | Is this what idiomatic Python has become? 11 lines to express a
       | loop, a conditional and a couple of function calls? I use Python
       | because I want to write _executable pseudocode_ , not _excessive
       | superfluousness_.
       | 
       | By contrast, here's the equivalent Ruby:                   def
       | f(template) = template.map { |item|
       | item.is_a?(Interpolation) ?
       | item.value.convert(item.conversion).format(item.format_spec) :
       | item             }.join
        
         | the-grump wrote:
         | This is how Python has always been. It's more verbose and IMO
         | easier to grok, but it still lets you create expressive DSLs
         | like Ruby does.
         | 
         | Python has always been my preference, and a couple of my
         | coworkers have always preferred Ruby. Different strokes for
         | different folks.
        
           | pansa2 wrote:
           | > _This is how Python has always been._
           | 
           | Nah, idiomatic Python always used to prefer comprehensions
           | over explicit loops. This is just the `match` statement
           | making code 3x longer than it needs to be.
        
         | davepeck wrote:
         | We wanted at least a couple examples that showed use of
         | Python's newer pattern matching capabilities. From this
         | outsider's perspective, I'd say that developer instincts and
         | aesthetic preferences are decidedly mixed here -- even amongst
         | the core team! You can certainly write this as:
         | def f(template: Template) -> str:             return "".join(
         | item if isinstance(item, str) else
         | format(convert(item.value, item.conversion), item.format_spec)
         | for item in template             )
         | 
         | Or, y'know, several other ways that might feel more idiomatic
         | depending on where you're coming from.
        
       | metadat wrote:
       | What kind of special string will be added next? We already have
       | f-strings, .format, %s ...
        
       | spankalee wrote:
       | Maintainer of lit-html here, which uses tagged template literals
       | in JavaScript extensively.
       | 
       | This looks really great! It's almost exactly like JavaScript
       | tagged template literals, just with a fixed tag function of:
       | (strings, ...values) => {strings, values};
       | 
       | It's pretty interesting how what would be the tag function in
       | JavaScript, and the arguments to it, are separated by the
       | Template class. At first it seems like this will add noise since
       | it takes more characters to write, but it can make nested
       | templates more compact.
       | 
       | Take this type of nested template structure in JS:
       | html`<ul>${items.map((i) => html`<li>${i}</li>`}</ul>`
       | 
       | With PEP 750, I suppose this would be:
       | html(t"<ul>{map(lambda i: t"<li>{i}</li>", items)}</ul>")
       | 
       | Python's unfortunate lambda syntax aside, not needing html()
       | around nested template could be nice (assuming an html() function
       | would interpret plain Templates as HTML).
       | 
       | In JavaScript reliable syntax highlighting and type-checking are
       | keyed off the fact that a template can only ever have a single
       | tag, so a static analyzer can know what the nested language is.
       | In Python you could separate the template creation from the
       | processing possibly introduce some ambiguities, but hopefully
       | that's rare in practice.
       | 
       | I'm personally would be interested to see if a special html()
       | processing instruction could both emit server-rendered HTML and
       | say, lit-html JavaScript templates that could be used to update
       | the DOM client-side with new data. That could lead to some very
       | transparent fine-grained single page updates, from what looks
       | like traditional server-only code.
        
         | davepeck wrote:
         | > assuming an html() function would interpret plain Templates
         | as HTML
         | 
         | It does feel natural to accept plain templates ( _and_ simple
         | sequences of plain templates) as such; this is hinted at in the
         | PEP.
         | 
         | > html(t"<ul>{map(lambda i: t"<li>{i}</li>", items)}</ul>")
         | 
         | Perhaps more idiomatically: html(t"<ul>{[t"<li>{i}</li>" for i
         | in items]}</ul>")
         | 
         | > syntax highlighting and type-checking are keyed off the fact
         | that a template can only ever have a single tag
         | 
         | Yes, this is a key difference and something we agonized a bit
         | over as the PEP came together. In the (very) long term, I'm
         | hopeful that we see type annotations used to indicate the
         | expected string content type. In the nearer term, I think a
         | certain amount of content sniffing will be necessary in tools
         | like (say) black if they wish to provide specialized formatting
         | for common types.
         | 
         | > a special html() processing instruction could both emit
         | server-rendered HTML and say, lit-html JavaScript templates
         | that could be used to update the DOM client-side with new data
         | 
         | I'd love to see this and it's exactly the sort of thing I'm
         | hoping emerges from PEP 750 over time. Please do reach out if
         | you'd like to talk it over!
        
       | ic_fly2 wrote:
       | In the past when I needed this I just made a function that
       | processed the f string, often enough a simple lambda function
       | would do. This looks like additional complexity for not a lot of
       | gain.
        
         | nhumrich wrote:
         | You cant process an f-string the same way you can process a
         | t-string. An f-string does not preserve which parts of it are
         | static and dynamic, which you need to know to properly escape
         | user input.
        
       | bhargavtarpara wrote:
       | prob dont need jinja anymore then
        
       ___________________________________________________________________
       (page generated 2025-04-10 23:00 UTC)