[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><script>alert('evil')</script></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)