Most active commenters
  • zahlman(11)
  • Tenoke(9)
  • masklinn(9)
  • throwawaymaths(7)
  • sanderjd(6)
  • Izkata(6)
  • dragonwriter(5)
  • (5)
  • skeledrew(4)
  • thunky(3)

←back to thread

620 points tambourine_man | 172 comments | | HN request time: 3.339s | source | bottom
1. serbuvlad ◴[] No.43750075[source]
All things considered, this is pretty cool. Basically, this replaces

    db.execute("QUERY WHERE name = ?", (name,))
with

    db.execute(t"QUERY WHERE name = {name}")
Does the benefit from this syntactic sugar outweigh the added complexity of a new language feature? I think it does in this case for two reasons:

1. Allowing library developers to do whatever they want with {} expansions is a good thing, and will probably spawn some good uses.

2. Generalizing template syntax across a language, so that all libraries solve this problem in the same way, is probably a good thing.

replies(12): >>43750226 #>>43750250 #>>43750260 #>>43750279 #>>43750513 #>>43750750 #>>43752117 #>>43752173 #>>43752293 #>>43754738 #>>43756560 #>>43763190 #
2. pinoy420 ◴[] No.43750226[source]
Now instead of being explicit all it takes is someone unfamiliar with t strings (which will be almost everyone - still few know about f strings and their formatting capabilities) to use an f instead and you are in for a bad time.
replies(5): >>43750270 #>>43750280 #>>43750323 #>>43751292 #>>43753934 #
3. Tenoke ◴[] No.43750250[source]
I don't see what it adds over f-string in that example?
replies(6): >>43750258 #>>43750261 #>>43750262 #>>43750265 #>>43750295 #>>43750581 #
4. evertedsphere ◴[] No.43750258[source]
safety against sql injection
5. NewEntryHN ◴[] No.43750260[source]
Assuming you also need to format non-values in the SQL (e.g. column names), how does the `execute` function is supposed to make the difference between stuff that should be formatted in the string vs a parametrized value?
replies(1): >>43750285 #
6. ds_ ◴[] No.43750261[source]
The execute function can recognize it as a t-string and prevent SQL injection if the name is coming from user input. f-strings immediately evaluate to a string, whereas t-strings evaluate to a template object which requires further processing to turn it into a string.
replies(1): >>43750286 #
7. burky ◴[] No.43750262[source]
f-strings won’t sanitize the value, so it’s not safe. The article talks about this.
replies(1): >>43750427 #
8. teruakohatu ◴[] No.43750265[source]
If I pass an f-string to a method, it just sees a string. If I pass a t-string the method can decide how to process the t-string.
9. falcor84 ◴[] No.43750270[source]
That is an issue, but essentially it boils down to the existing risk of unknowledgeable people not escaping untrusted inputs. The solution should be more education and better tooling (linters, SAST), and t-strings are likely to help with both.
replies(1): >>43750308 #
10. amelius ◴[] No.43750279[source]
One thing it misses is compile-time checks for e.g. the format spec.
replies(2): >>43750311 #>>43759637 #
11. ◴[] No.43750280[source]
12. masklinn ◴[] No.43750285[source]
Same as currently: the library provides some sort of `Identifier` wrapper you can apply to those.
replies(1): >>43750354 #
13. Tenoke ◴[] No.43750286{3}[source]
Then the useful part is the extra execute function you have to write (it's not just a substitute like in the comment) and an extra function can confirm the safety of a value going into a f-string just as well.

I get the general case, but even then it seems like an implicit anti-pattern over doing db.execute(f"QUERY WHERE name = {safe(name)}")

replies(5): >>43750324 #>>43750380 #>>43750409 #>>43754093 #>>43756889 #
14. sureglymop ◴[] No.43750295[source]
Wouldn't this precisely lead to sql injection vulnerabilities with f-strings here?
15. masklinn ◴[] No.43750308{3}[source]
t-strings allow building APIs which don't accept strings at all (or require some sort of opt-in), and will always error on such. That's the boon.

Having to write

    cr.execute(t"...")
even when there's nothing to format in is not a big imposition.
16. karamanolev ◴[] No.43750311[source]
Doesn't all of Python miss that, having (close to) no compile time?
replies(1): >>43750359 #
17. mcintyre1994 ◴[] No.43750323[source]
Any sane library will just error when you pass a string to a function that expects a template though. And that library will have types too so your IDE tells you before you get that far.
replies(2): >>43752466 #>>43753438 #
18. ubercore ◴[] No.43750324{4}[source]
Problem with that example is where do you get `safe`? Passing a template into `db.execute` lets the `db` instance handle safety specifically for the backend it's connected to. Otherwise, you'd need to create a `safe` function with a db connection to properly sanitize a string.

And further, if `safe` just returns a string, you still lose out on the ability for `db.execute` to pass the parameter a different way -- you've lost the information that a variable is being interpolated into the string.

replies(1): >>43750412 #
19. NewEntryHN ◴[] No.43750354{3}[source]
Fair enough. It would be nice if Python allowed to customize the formatting options after `:`

This way you could encode such identifier directly in the t-string variable rather than with some "out-of-band" logic.

replies(2): >>43750418 #>>43751198 #
20. amelius ◴[] No.43750359{3}[source]
Python does some checks before it runs code. E.g.:

    print("hello")

    def f():
        nonlocal foo
gives:

    SyntaxError: no binding for nonlocal 'foo' found
before printing hello, and note that f() wasn't even called.
replies(1): >>43754533 #
21. NewEntryHN ◴[] No.43750380{4}[source]
Some SQL engines support accepting parameters separately so that values get bound to the query once the abstract syntax tree is already built, which is way safer than string escapes shenanigans.
replies(1): >>43751841 #
22. Mawr ◴[] No.43750409{4}[source]
But you have to remember to call the right safe() function every time:

    db.execute(f"QUERY WHERE name = {name}")

    db.execute(f"QUERY WHERE name = {safe_html(name)}")
Oops, you're screwed and there is nothing that can detect that. No such issue with a t-string, it cannot be misused.
23. Tenoke ◴[] No.43750412{5}[source]
db.safe same as the new db.execute with safety checks in it you create for the t-string but yes I can see some benefits (though I'm still not a fan for my own codebases so far) with using the values further or more complex cases than this.
replies(1): >>43750482 #
24. mcintyre1994 ◴[] No.43750418{4}[source]
The article does mention that the function receiving the template has access to those formatting options for each interpolation, so presumably you could abuse the ones that are available for that purpose?
25. Tenoke ◴[] No.43750427{3}[source]
The article talked about it but the example here just assumes they'll be there.
replies(2): >>43751261 #>>43751285 #
26. ubercore ◴[] No.43750482{6}[source]
Yeah but it would have to be something like `db.safe("SELECT * FROM table WHERE id = {}", row_id)` instead of `db.execute(t"SELECT * FROM table WHERE id = {row_id}")`.

I'd prefer the second, myself.

replies(2): >>43750548 #>>43753222 #
27. rwmj ◴[] No.43750513[source]
I did a safe OCaml implementation of this about 20 years ago, the latest version being here:

https://github.com/darioteixeira/pgocaml

Note that the variables are safely and correctly interpolated at compile time. And it's type checked across the boundary too, by checking (at compile time) the column types with the live database.

replies(1): >>43750774 #
28. Tenoke ◴[] No.43750548{7}[source]
No, just `db.execute(f"QUERY WHERE name = {db.safe(name)}")`

And you add the safety inside db.safe explicitly instead of implicitly in db.execute.

If you want to be fancy you can also assign name to db.foos inside db.safe to use it later (even in execute).

replies(4): >>43750786 #>>43751243 #>>43751257 #>>43759309 #
29. sim7c00 ◴[] No.43750581[source]
it makes it so people too lazy to make good types and class will be getting closer to sane code without doing sane code...

imagine writing a SqL where u put user input into query string directly.

now remember its 2025, lie down try not to cry.

30. tetha ◴[] No.43750750[source]
Or you could use this in a library like sh with

    sh(t"stat {some_file}")
With t-strings you could run proper escaping over the contents of `some_file` before passing it to a shell.

I'd have to take a look at the order things happen in shell, but you might even be able to increase security/foot-gun-potential a little bit here by turning this into something like `stat "$( base64 -d [base64 encoded content of some_file] )"`.

replies(2): >>43751303 #>>43756169 #
31. tasuki ◴[] No.43750774[source]
Yes, what you did is strictly more powerful than what the Python people did. And you did it 20 years ago. Well done, have an upvote. And yet, here we are in 2025 with Python popularity growing unstoppably and (approximately) no one caring about OCaml (and all the other languages better than Python). It makes me sad.
replies(3): >>43751186 #>>43751601 #>>43755054 #
32. ZiiS ◴[] No.43750786{8}[source]
But if someone omits the `safe` it may still work but allow injection.
replies(1): >>43751449 #
33. sanderjd ◴[] No.43751186{3}[source]
Network effects are a beast!

But my two cents is that we're pretty lucky it's python that has taken off like a rocket. It's not my favorite language, but there are far worse that it could have been.

replies(1): >>43754978 #
34. masklinn ◴[] No.43751198{4}[source]
> Fair enough. It would be nice if Python allowed to customize the formatting options after `:`

It does, the `Interpolation` object contains an arbitrary `format_spec` string: https://peps.python.org/pep-0750/#the-interpolation-type

However I think using the format spec that way would be dubious and risky, because it makes the sink responsible for whitelisting values, and that means any processing between the source and sink becomes a major risk. It's the same issue as HTML templates providing `raw` output, now you have to know to audit any modification to the upstream values which end there, which is a lot harder to do than when "raw markup" values are reified.

> rather than with some "out-of-band" logic.

It's the opposite, moving it to the format spec is out of band because it's not attached to values, it just says "whatever value is here is safe", which is generally not true.

Unless you use the format spec as a way to signal that a term should use identifier escaping rules rather than value escaping rules (something only the sink knows), and an `Identifier` wrapper remains a way to bypass that.

replies(1): >>43752672 #
35. sanderjd ◴[] No.43751243{8}[source]
This is just extra boilerplate though, for what purpose?.

I think one thing you might be missing is that in the t-string version, `db.execute` is not taking a string; a t-string resolves to an object of a particular type. So it is doing your `db.safe` operation, but automatically.

36. panzi ◴[] No.43751257{8}[source]
Of course you can write code like that. This is about making it easier not to accidentally cause code injection by forgetting the call of safe(). JavaScript had the same feature and some SQL libraries allow only the passing of template strings, not normal strings, so you can't generate a string with code injection. If you have to dynamically generate queries they allow that a parameter is another template string and then those are merged correctly. It's about reducing the likelihood of making mistakes with fewer key strokes. We could all just write untyped assembly instead and could do it safely by paying really good attention.
37. sanderjd ◴[] No.43751261{4}[source]
What do you mean by "they"? You mean the template interpolation functions?

Yes, the idea is that by having this in the language, library authors will write these implementations for use cases where they are appropriate.

replies(1): >>43751976 #
38. masklinn ◴[] No.43751285{4}[source]
Because t-strings don't create strings, so if the library doesn't support t-strings the call can just error.
39. sanderjd ◴[] No.43751292[source]
No, because they don't return a string, so good library authors will raise a type error when that happens, for exactly this reason.
40. nhumrich ◴[] No.43751303[source]
You should check out PEP 787
replies(4): >>43754650 #>>43755129 #>>43756826 #>>43761190 #
41. thunky ◴[] No.43751449{9}[source]
Same is true if someone forgets to use t" and uses f" instead.

At least db.safe says what it does, unlike t".

replies(2): >>43751947 #>>43751995 #
42. rwmj ◴[] No.43751601{3}[source]
I'm switching between C, OCaml, Python, bash & Rust roughly equally every day (to a lesser extent, Perl as well). Not everything is what gets on the front page of HN.
43. ljm ◴[] No.43751841{5}[source]
I’d always prefer to use a prepared statement if I can, but sadly that’s also less feasible in the fancy new serverless execution environments where the DB adapter often can’t support them.

For me it just makes it easier to identify as safe, because it might not be obvious at a glance that an interpolated template string is properly sanitised.

44. ewidar ◴[] No.43751947{10}[source]
Not really, since f"" is a string and t"" is a template, you could make `db.execute` only accept templates, maybe have

`db.execute(Template)` and `db.unsafeExecute(str)`

replies(1): >>43755075 #
45. Tenoke ◴[] No.43751976{5}[source]
The sanitization. Just using a t-string in your old db.execute doesn't imply anything safer is going on than before.
replies(2): >>43752377 #>>43752677 #
46. fwip ◴[] No.43751995{10}[source]
Your linter can flag the type mismatch, and/or the function can reject f"" at runtime. This is because t"" yields a Template, not a str.

Template is also more powerful/concise in that the stringify function can handle the "formatting" args however it looks.

Note also, that there's no requirement that the template ever become a str to be used.

47. mikeholler ◴[] No.43752117[source]
A potential concern is how close this looks to the pattern they're trying to override.

    db.execute(f"QUERY WHERE name = {name}")
versus

    db.execute(t"QUERY WHERE name = {name}")
replies(2): >>43752225 #>>43753358 #
48. benwilber0 ◴[] No.43752173[source]
Aren't there other benefits to server-side parameter binding besides just SQL-injection safety? For instance, using PG's extended protocol (binary) instead of just raw SQL strings. Caching parameterized prepared statements, etc.

Also:

    db.execute(t"QUERY WHERE name = {name}")
Is dangerously close to:

    db.execute(f"QUERY WHERE name = {name}")

A single character difference and now you've just made yourself trivially injectible.

I don't think this new format specifier is in any way applicable to SQL queries.

replies(12): >>43752236 #>>43752283 #>>43752331 #>>43752336 #>>43752358 #>>43752859 #>>43753280 #>>43753699 #>>43754372 #>>43754646 #>>43755330 #>>43756720 #
49. fzzzy ◴[] No.43752225[source]
But won't the f string version fail loudly because there's no name parameter?
replies(1): >>43752231 #
50. benwilber0 ◴[] No.43752231{3}[source]
the {name} parameter is in the locals() dict like it always is
replies(1): >>43752248 #
51. ◴[] No.43752236[source]
52. fzzzy ◴[] No.43752248{4}[source]
Good point. Perhaps the database api could refuse strings and require Templates.
replies(1): >>43753252 #
53. VWWHFSfQ ◴[] No.43752283[source]
> I don't think this new format specifier is in any way applicable to SQL queries.

Agree. And the mere presence of such a feature will trigger endless foot-gunning across the Python database ecosystem.

54. VWWHFSfQ ◴[] No.43752293[source]
> Allowing library developers to do whatever they want with {} expansions is a good thing, and will probably spawn some good uses.

I completely disagree with this. Look what happened to Log4J when it was given similar freedoms.

replies(1): >>43756280 #
55. masklinn ◴[] No.43752331[source]
> Aren't there other benefits to server-side parameter binding besides just SQL-injection safety? For instance, using PG's extended protocol (binary) instead of just raw SQL strings. Caching parameterized prepared statements, etc.

All of which can be implemented on top of template strings.

> A single character difference and now you've just made yourself trivially injectible.

It's not just a one character difference, it's a different type. So `db.execute` can reject strings both statically and dynamically.

> I don't think

Definitely true.

> this new format specifier is in any way applicable to SQL queries.

It's literally one of PEP 750's motivations.

replies(7): >>43752391 #>>43752395 #>>43752558 #>>43752752 #>>43754441 #>>43755649 #>>43755673 #
56. MR4D ◴[] No.43752336[source]
Dang! Thanks for pointing this out.

I had to look SEVERAL times at your comment before I noticed one is an F and the other is a T.

This won’t end well. Although I like it conceptually, this few pixel difference in a letter is going to cause major problems down the road.

replies(1): >>43752552 #
57. ◴[] No.43752358[source]
58. masklinn ◴[] No.43752377{6}[source]
Using a t-string in a db.execute which is not compatible with t-strings will result in an error.

Using a t-string in a db-execute which is, should be as safe as using external parameters. And using a non-t-string in that context should (eventually) be rejected.

replies(1): >>43752480 #
59. VWWHFSfQ ◴[] No.43752391{3}[source]
> It's literally one of PEP 750's motivations.

Python is notorious for misguided motivations. We're not "appealing to authority" here. We're free to point out when things are goofy.

60. willcipriano ◴[] No.43752395{3}[source]

    from string.templatelib import Template

    def execute(query: Template)
Should allow for static analysis to prevent this issue if you run mypy as part of your pr process.

That would be in addition to doing any runtime checks.

replies(1): >>43752563 #
61. Dx5IQ ◴[] No.43752466{3}[source]
Such library functions tend to also accept a string as a valid input. E.g. db.execute from the GP usually works with strings to allow non-parametrized SQL queries.
replies(2): >>43752850 #>>43769225 #
62. Tenoke ◴[] No.43752480{7}[source]
Again, just because a function accepts a t string it doesn't mean there's sanitization going on by default.
replies(1): >>43752636 #
63. pphysch ◴[] No.43752552{3}[source]
How? tstrings and fstrings are literals for completely different types.

CS has survived for decades with 1 and 1.0 being completely different types.

replies(3): >>43753130 #>>43753177 #>>43754544 #
64. woodrowbarlow ◴[] No.43752558{3}[source]
nitpicking:

> It's not just a one character difference, it's a different type. So `db.execute` can reject strings both statically and dynamically.

in this case, that's not actually helpful because SQL statements don't need to have parameters, so db.execute will always need to accept a string.

replies(2): >>43753027 #>>43754776 #
65. benwilber0 ◴[] No.43752563{4}[source]
The first mistake we're going to see a library developer make is:

    def execute(query: Union[str, Template]):

Maybe because they want their execute function to be backwards compatible, or just because they really do want to allow either raw strings are a template string.
replies(1): >>43754807 #
66. tikhonj ◴[] No.43752636{8}[source]
Yes, but if a function accepts a template (which is a different type of object from a string!), either it is doing sanitization, or it explicitly implemented template support without doing sanitization—hard to do by accident!

The key point here is that a "t-string" isn't a string at all, it's a new kind of literal that's reusing string syntax to create Template objects. That's what makes this new feature fundamentally different from f-strings. Since it's a new type of object, libraries that accept strings will either have to handle it explicitly or raise a TypeError at runtime.

replies(1): >>43753556 #
67. pphysch ◴[] No.43752672{5}[source]
> Unless you use the format spec as a way to signal that a term should use identifier escaping rules rather than value escaping rules (something only the sink knows)

This should be quiet common in the SQL applications. It will be nice to write t"select {name:id} from {table:id} where age={age}" and be confident that the SQL will be formatted correctly, with interpolations defaulting to (safe) literal values.

68. nemetroid ◴[] No.43752677{6}[source]
Your "old" db.execute (which presumably accepts a regular old string) would not accept a t-string, because it's not a string. In the original example, it's a new db.execute.
69. tczMUFlmoNk ◴[] No.43752752{3}[source]
> > I don't think

> Definitely true.

The rest of your comment is valuable, but this is just mean-spirited and unnecessary.

70. kccqzy ◴[] No.43752850{4}[source]
The library should just refuse strings. If a non parametrized query is desired, it could require the user to supply a t-string with no {}.
71. WorldMaker ◴[] No.43752859[source]
Templates are a very different duck type from strings and intentionally don't support __str__(). The SQL tool can provide a `safe_execute(Template)` that throws if passed a string and not a Template. You can imagine future libraries that only support Template and drop all functions that accept strings as truly safe query libraries.

> Caching parameterized prepared statements, etc.

Templates give you all the data you need to also build things like cacheable parameterized prepared statements. For DB engines that support named parameters you can even get the interpolation expression to auto-name parameters (get the string "name" from your example as the name of the variable filling the slot) for additional debugging/sometimes caching benefits.

72. anamexis ◴[] No.43753027{4}[source]
You can just pass it a template with no substitutions.
73. Izkata ◴[] No.43753130{4}[source]
Because they're both passed to "execute", which can't tell between the f-string and a non-interpolated query, so it just has to trust you did the right thing. Typoing the "t" as an "f" introduces SQL injection that's hard to spot.
replies(2): >>43753238 #>>43756782 #
74. Certhas ◴[] No.43753177{4}[source]
I had an extended debugging session last week that centered on 1 and 1. confusion in a library I have to use...
replies(1): >>43756918 #
75. Izkata ◴[] No.43753222{7}[source]
The first one already exists like:

  db.execute("SELECT * FROM table WHERE id = ?", (row_id,))
76. vlovich123 ◴[] No.43753238{5}[source]
Assuming `execute` takes both. You could have `execute(template)` and `execute_interpolated(str, ...args)` but yeah if it takes both you'll have challenges discouraging plain-text interpolation.
replies(1): >>43753703 #
77. bshacklett ◴[] No.43753252{5}[source]
That’s a big breaking change around a brand new feature. I’m sure it could be done well, but it gives me the shivers.
replies(2): >>43755459 #>>43769208 #
78. ◴[] No.43753280[source]
79. notatoad ◴[] No.43753358[source]
The key point is that t-strings are not strings. Db.execute(t”…”) would throw an exception, because t”…” is not a string and cannot be interpreted as one.

In order for a library to accept t-strings, they need to make a new function. Or else change the behavior and method signature of an old function, which I guess they could do but any sanely designed library doesn’t do.

Handling t-strings will require new functions to be added to libraries.

replies(1): >>43754592 #
80. _Algernon_ ◴[] No.43753438{3}[source]
This would break backwardcompatibility pretty hard. In many cases it may not be worth it.
replies(2): >>43753990 #>>43754551 #
81. Tenoke ◴[] No.43753556{9}[source]
I'm not sure why you think it's harder to use them without sanitization - there is nothing inherent about checking the value in it, it's just a nice use.

You might have implemented the t-string to save the value or log it better or something and not even have thought to check or escape anything and definitely not everything (just how people forget to do that elsewhere).

replies(1): >>43753640 #
82. sanderjd ◴[] No.43753640{10}[source]
I really think you're misunderstanding the feature. If a method has a signature like:

    class DB:
        def execute(query: Template):
            ...
It would be weird for the implementation to just concatenate everything in the template together into a string without doing any processing of the template parameters. If you wanted an unprocessed string, you would just have the parameter be a string.
replies(1): >>43754238 #
83. rastignack ◴[] No.43753699[source]
Quite easy to detect with a proper linter.
84. Izkata ◴[] No.43753703{6}[source]
It would have to be the other way around or be a (possibly major) breaking change. Just execute() with strings is already standard python that all the frameworks build on top of, not to mention tutorials:

https://docs.python.org/3/library/sqlite3.html

https://www.psycopg.org/docs/cursor.html

https://dev.mysql.com/doc/connector-python/en/connector-pyth...

replies(1): >>43769181 #
85. hackrmn ◴[] No.43753934[source]
I suppose lack of overlap in the "interface surface" (attributes, including callables) between `str` and `Template` should nip the kind of issue in the bud -- being passed a `Template` and needing to actually "instantiate" it -- accessing `strings` and `values` attributes on the passed object, will likely fail at runtime when attempted on a string someone passed instead (e.g. confusing a `t`-string with an `f`-string)?
86. eichin ◴[] No.43753990{4}[source]
But now at least the language has the necessary rope (and an opportunity for a cultural push to insist on it.)
87. dragonwriter ◴[] No.43754093{4}[source]
> and an extra function can confirm the safety of a value going into a f-string just as well.

Yes, you could require consumers to explicitly sanitize each parameter before it goes into the f-string, or, because it has the structure of what is fixed and what is parameters, it can do all of that for all parameters when it gets a t-string.

The latter is far more reliable, and you can't do it with an f-string because an f-string after creation is just a static string with no information about construction.

88. Tenoke ◴[] No.43754238{11}[source]
I'm not. Again, you might be processing the variable for logging or saving or passing elsewhere as well or many other reasons unrelated to sanitization.
replies(3): >>43754564 #>>43754600 #>>43755078 #
89. hombre_fatal ◴[] No.43754372[source]
You solve that with an execute(stmt) function that requires you to pass in a template.

In Javascript, sql`where id = ${id}` is dangerously close to normal string interpolation `where id = ${id}`, and db libs that offer a sql tag have query(stmt) fns that reject strings.

90. rangerelf ◴[] No.43754441{3}[source]
>> I don't think >Definitely true.

I thought we left middle-school playground tactics behind.

91. nomel ◴[] No.43754533{4}[source]
I think it's just giving an error because a valid AST can't be made, which means valid bytecode can't be made. "<word> <word>" is only valid syntax if one is a reserved word. `nonlocal(foo)` is just fine, of course.
replies(2): >>43756609 #>>43756932 #
92. MR4D ◴[] No.43754544{4}[source]
Reread my comment. It’s about noticing you have an “f” or a “t” and both are very similar characters.
replies(1): >>43754644 #
93. hombre_fatal ◴[] No.43754551{4}[source]
Javascript already has prior art here.

A library can extend an existing database library like 'pg' so that PgClient#query() and PgPool#query() require string template statements.

That way 'pg' can continue working with strings, and people who want nice templated strings can use the small extension library, and the small extension library makes it impossible to accidentally pass strings into the query functions.

94. nemetroid ◴[] No.43754564{12}[source]
Sure, and the safe() function proposed upthread might also just be doing logging.
95. gls2ro ◴[] No.43754592{3}[source]
yes but the bug is writing f instead of t and I assume f will just work.

To clarify even more:

The problem is not writing by mistake t instead of f => this is what we want and then for this we implement a new function

The problem is writing f instead of t => and this will silently work I assume (not a Python dev just trying to understand the language design)

replies(2): >>43754870 #>>43758950 #
96. Ukv ◴[] No.43754600{12}[source]
The original comment said that it'd replace

    db.execute("QUERY WHERE name = ?", (name,))
with

    db.execute(t"QUERY WHERE name = {name}")
It's true that in theory `db.execute` could ignore semantics and concatenate together the template and variables to make a string without doing any sanitisation, but isn't the same true of the syntax it was claimed to replace?

Just because templates (or the previous syntax of passing in variables separately) could be used in a way that's equivalent safety-wise to an f-string by a poorly designed library does not mean that they add nothing over an f-string in general - they move the interpolation into db.execute where it can do its own sanitization and, realistically, sqlite3 and other libraries explicitly updated to take these will use it to do proper sanitization.

97. rocha ◴[] No.43754644{5}[source]
Yes, but you will get an error since string and templates are different types and have different interfaces.
replies(1): >>43755714 #
98. InstaPage ◴[] No.43754646[source]
t vs f going to be hard to spot.
replies(1): >>43754771 #
99. tetha ◴[] No.43754650{3}[source]
Hmm, PEP-787 has some interesting discussions around it. I'll have to sort my thoughts on these aspects a bit.
100. int_19h ◴[] No.43754738[source]
Python is not the first one to get this feature. It's been present in JS for some time now, and before that in C# (not sure if that's the origin or they also borrowed it from somewhere). Python adopted it based in part on successful experience in those other languages.
replies(1): >>43756224 #
101. acdha ◴[] No.43754771{3}[source]
This is true of many other things, which is why we have type checkers and linters to be perfectly rigorous rather than expecting humans to never make mistakes.
replies(1): >>43769324 #
102. masklinn ◴[] No.43754776{4}[source]
> db.execute will always need to accept a string.

No. A t-string with no placeholders is perfectly fine. You can use that even if you have no parameters.

103. masklinn ◴[] No.43754807{5}[source]
> they really do want to allow either raw strings are a template string.

I’d consider that an invalid use case:

1. You can create a template string without placeholders.

2. Even if the caller does need to pass in a string (because they’re executing from a file, or t-strings don’t support e.g. facetting) then they can just… wrap the string in a template explicitly.

104. masklinn ◴[] No.43754870{4}[source]
> The problem is writing f instead of t => and this will silently work I assume (not a Python dev just trying to understand the language design)

In the fullness of time it has no reason to. Even in the worst case scenario where you have to compose the query dynamically in a way t-strings can’t support, you can just instantiate a Template object explicitely.

105. psychoslave ◴[] No.43754978{4}[source]
You mean like Cobol? Oh wait!
106. skeledrew ◴[] No.43755054{3}[source]
It's interesting how the majority has explicitly chosen NOT to use the "better" languages. Is the majority really that bad in their judgment? Or is it that "better" is actually defined by adoption over time?
replies(3): >>43755431 #>>43756148 #>>43757638 #
107. thunky ◴[] No.43755075{11}[source]
agreed. but then you're breaking the existing `db.execute(str)`. if you don't do that, and instead add `db.safe_execute(tpl: Template)`, then you're back to the risk that a user can forget to call the safe function.

also, you're trusting that the library implementer raises a runtime exception if a string a passed where a template is expected. it's not enough to rely on type-checks/linting. and there is probably going to be a temptation to accept `db.execute(sql: Union[str, Template])` because this is non-breaking, and sql without params doesn't need to be templated - so it's breaking some stuff that doesn't need to be broken.

i'm not saying templates aren't a good step forward, just that they're also susceptible to the same problems we have now if not used correctly.

replies(1): >>43759869 #
108. sanderjd ◴[] No.43755078{12}[source]
Taking a Template parameter into a database library's `execute` method is a big bright billboard level hint that the method is going to process the template parameters with the intent to make the query safe. The documentation will also describe the behavior.

You're right that the authors of such libraries could choose to do something different with the template parameter. But none of them will, for normal interface design reasons.

A library author could also write an implementation of a `plus` function on a numerical type that takes another numerical type, and return a string with the two numbers concatenated, rather than adding them together.

But nobody will do that, because libraries with extremely surprising behavior like that won't get used by anybody, and library authors don't want to write useless libraries. This is the same.

109. pauleveritt ◴[] No.43755129{3}[source]
We really should just point most of these comments at that PEP. Thanks for getting it out so fast.
110. kazinator ◴[] No.43755330[source]
But t"..." and f"..." have different types; we can make db.execute reject character strings and take only template objects.
replies(1): >>43764175 #
111. daedrdev ◴[] No.43755431{4}[source]
It's clearly better in their opinion, they just aren't optimizing for the same metrics that you are. Python is better because it's easy for people to learn, imo.
replies(1): >>43755740 #
112. daedrdev ◴[] No.43755459{6}[source]
much better would be execute_template(t"...")
113. ◴[] No.43755649{3}[source]
114. davepeck ◴[] No.43755673{3}[source]
> Caching parameterized prepared statements, etc.

I didn’t explicitly mention this in my post but, yes, the Template type is designed with caching in mind. In particular, the .strings tuple is likely to be useful as a cache key in many cases.

115. Izkata ◴[] No.43755714{6}[source]
Click "parent" a few times and look at the code example that started this thread. It's using the same function in a way that can't distinguish whether the user intentionally used a string (including an f-string) and a t-string.
replies(1): >>43756771 #
116. throwawaymaths ◴[] No.43755740{5}[source]
its not easy to learn. its a challenge even getting it installed and running. what even is a venv? how do you explain that to a beginner?

python is popular because its what teachers teach.

replies(6): >>43756172 #>>43756692 #>>43756781 #>>43756970 #>>43757280 #>>43758584 #
117. dhruvrajvanshi ◴[] No.43756148{4}[source]
I think you're being too unfair. People aren't dumb.

It's also about how much better.

Beyond a decent enough type system, the advantages start to flatten and other factors start to matter more.

Can't speak too much for python, but as someone who's written large amounts of code in OCaml and Typescript, the strictest compiler options for Typescript are good enough.

replies(1): >>43758699 #
118. dhruvrajvanshi ◴[] No.43756169[source]
Not Python but this is exactly the idea behind zx

https://github.com/google/zx

119. acdha ◴[] No.43756172{6}[source]
You don’t need to teach it to a beginner. The first of learning doesn’t need more than the standard library and when you need more than that you’re either giving them the single command necessary to run or, more likely, having them use a template project where a tool like Poetry is doing that automatically.

What this usually hits isn’t that managing Python packages is hard in 2025 but that many people do not learn how their operating system works conceptually until the first time they learn to program and it’s easy to conflate that with the first language you learn.

replies(1): >>43759089 #
120. serbuvlad ◴[] No.43756224[source]
That's really cool. I don't use JS or C#, so I wasn't aware of this, but it's a good idea.
121. serbuvlad ◴[] No.43756280[source]
I think this would have solved the log4j vulnerability, no?

As I understand it, log4j allowed malicious ${} expansion in any string passed to logging functions. So logging user generated code at all would be a security hole.

But Python's t-strings purposely _do not_ expand user code, they only expand the string literal.

122. zahlman ◴[] No.43756560[source]
3. It prevents the developer from trying

  db.execute(f"QUERY WHERE name = {name}")
or

  db.execute("QUERY WHERE name = %s" % name, ())
or other ways of manually interpolating the string - because `db.execute` can flag a `TypeError` if given a string (no matter how it was constructed) rather than a `Template` instance.
123. pansa2 ◴[] No.43756609{5}[source]
> "<word> <word>" is only valid syntax if one is a reserved word.

`nonlocal` is a keyword

124. zahlman ◴[] No.43756692{6}[source]
On modern Linux you can type `python` at the command prompt and get a REPL. On Windows you download an installer from the official website (just like one usually does to install anything on Windows), then use `py` at the command prompt.

You don't need to `import` anything to start teaching Python. Even then you can do quite a lot with the standard library. Even then, unless you're using 3.11 or later on Linux you can let Pip install with `--user` until you actually need to isolate things between projects. (And even with new Python on Linux, the instructor can typically avert this by just installing a separate Python in `/usr/local/bin` for example. Yes, that's "cheating", depending on the classroom environment. But that's part of the point: installation hurdles are hurdles for self-learners, not for students.)

You only need to learn about virtual environments once you have projects with mutually conflicting dependencies, and/or once you're at a point where you're ready to publish your own software and should be learning proper testing and development practices. (Which will be largely orthogonal to programming, and not trivial, in any language.)

And when your students do get to that point, you can give them a link such as https://chriswarrick.com/blog/2018/09/04/python-virtual-envi... .

Teachers teach Python because it's easy to teach while still being relevant to the real world, in particular because boilerplate is minimized. You don't have to explain jargon-y keywords like "public" or "static" up front. You don't have to use classes for quite some time (if ever, really). You can express iteration naturally. Types are naturally thought of in terms of capabilities.

In my mind, Python has all the pedagogical advantages of Lisp, plus enough syntactic cues to prevent getting "lost in a sea of parentheses". (Of course, it lacks plenty of other nice Lisp-family features.)

replies(3): >>43757779 #>>43759118 #>>43761005 #
125. zahlman ◴[] No.43756720[source]
> A single character difference and now you've just made yourself trivially injectible.

No; a single character difference and now you get a `TypeError`, which hopefully the library has made more informative by predicting this common misuse pattern.

126. zahlman ◴[] No.43756771{7}[source]
Yes, and the parent is misguided. As was pointed out in multiple replies, the library can distinguish whether an ordinary string or a t-string is passed because the t-string is not a string instance, but instead creates a separate library type. A user who mistakenly uses an f prefix instead of a t prefix will, with a properly designed library, encounter a `TypeError` at runtime (or a warning earlier, given type annotations and a checker), not SQL injection.
replies(1): >>43758123 #
127. daedrdev ◴[] No.43756781{6}[source]
someone learning python as their first language knows so little its perfectly fine to let them pollute their global environment. Someone who knows other languages can understand what venv is for.

Instead they can type python to open a shell and use python to immediately run their file.

replies(1): >>43761701 #
128. zahlman ◴[] No.43756782{5}[source]
`execute` can tell the difference, because `t"..."` does not create the same type of object that `f"..."` does.
129. zahlman ◴[] No.43756826{3}[source]
Oh! I missed this one because I've been looking specifically at the Packaging forum rather than the PEPs forum. This looks like a brilliant use case. (I'm aiming for wide compatibility - back to 3.6 - with my current projects, but I look forward to trying this out if and when it's accepted and implemented.)

Now if only the overall `subprocess` interface weren't so complex....

130. zahlman ◴[] No.43756889{4}[source]
> Then the useful part is the extra execute function you have to write

Well, no, the library author writes it. And the library author also gets to detect whether you pass a Template instance as expected, or (erroneously) a string created by whatever formatting method you choose. Having to use `safe(name)` within the f-string loses type information, and risks a greater variety of errors.

131. pphysch ◴[] No.43756918{5}[source]
Yeah, it's a real bummer when that happens. I wish JSON never tried to do types.
132. zahlman ◴[] No.43756932{5}[source]
No, it gives an error because `nonlocal foo` requests that the name `foo` be looked up in a closure, but `f` doesn't have such a closure (the `foo` defined outside the function is global instead). `nonlocal` is the same sort of keyword as `global` but for enclosing functions instead of the global namespace; see also https://stackoverflow.com/questions/1261875 .
replies(1): >>43764621 #
133. jyounker ◴[] No.43756970{6}[source]
It has become successful largely because it has always had really good foreign function interface. If you have a scientific or mathematical library laying around in C, then you could wire it up to Python, and then suddenly you have all the flexibility of a (fairly clean) scripting language to orchestrate your high speed C.

Good examples of this are numpy and tensorflow.

replies(1): >>43759107 #
134. psunavy03 ◴[] No.43757280{6}[source]
If someone is challenged figuring out a venv and they're not an absolute beginner, perhaps they aren't cut out to work in technology. There are countless subjects in the field more challenging and complicated to wrap one's brain around.

Also, in 2025, just use uv.

replies(1): >>43761676 #
135. angra_mainyu ◴[] No.43757638{4}[source]
momentum + ecosystem often play a much larger role than actual language merits.
replies(1): >>43758679 #
136. trealira ◴[] No.43757779{7}[source]
> In my mind, Python has all the pedagogical advantages of Lisp, plus enough syntactic cues to prevent getting "lost in a sea of parentheses". (Of course, it lacks plenty of other nice Lisp-family features.)

What you say here reminds me of something Peter Norvig said 15 years ago on this site: https://news.ycombinator.com/item?id=1803815

> Peter Norvig here. I came to Python not because I thought it was a better/acceptable/pragmatic Lisp, but because it was better pseudocode. Several students claimed that they had a hard time mapping from the pseudocode in my AI textbook to the Lisp code that Russell and I had online. So I looked for the language that was most like our pseudocode, and found that Python was the best match. Then I had to teach myself enough Python to implement the examples from the textbook. I found that Python was very nice for certain types of small problems, and had the libraries I needed to integrate with lots of other stuff, at Google and elsewhere on the net.

Basically, that it's better pedagogically because it looks like pseudo-code and it's easy to get up and running quickly.

replies(1): >>43759112 #
137. Izkata ◴[] No.43758123{8}[source]
In this particular instance it can't, because there are 3 ways in question here, and it can't distinguish between correct intentional usage and accidental usage of an f-string instead of a t-string:

  db.execute("SELECT foo FROM bar;")
  db.execute(f"SELECT foo FROM bar WHERE id = {foo_id};")
  db.execute(t"SELECT foo FROM bar WHERE id = {foo_id};")
The first and second look identical to execute() because all it sees is a string. But the second one is wrong, a hard-to-see typo of the third.

If f-strings didn't exist there'd be no issue because it could distinguish by type as you say. But we have an incorrect SQL-injection-prone usage here that can't be distinguished by type from the correct plain string usage.

replies(1): >>43764565 #
138. skeledrew ◴[] No.43758584{6}[source]
Let me introduce you to uv[0]. And yes it does say something that this tool isn't written in Python, but I'd say there's even more to be said that so many are trying to support Python.

[0] https://docs.astral.sh/uv/

replies(1): >>43759086 #
139. skeledrew ◴[] No.43758679{5}[source]
And yet that momentum and ecosystem wouldn't have been achieved in the first place if there weren't enough merits in the language to trigger and maintain that interest.
replies(1): >>43765036 #
140. skeledrew ◴[] No.43758699{5}[source]
No, people aren't dumb. They're practical. And so they choose to do what is practical, which in this case is to choose Python. And, to me, that makes it the better language.
141. notatoad ◴[] No.43758950{4}[source]
>yes but the bug is writing f instead of t and I assume f will just work

but it will not. f-strings and t-strings are not compatible types, they will not "just work". not unless somebody changes a library to make it just work. as long as nobody does that, it's not an issue.

142. throwawaymaths ◴[] No.43759086{7}[source]
yeah the reason why that's not an answer is because another half of users will say use poetry. if you want to do bioinformatics, people will insist on conda. then your team will say "use rye" and these strategies are somewhat compatible but ultimately mutually incompatible in ways that will drive you mad every time you hit a tiny snag that nonetheless grinds your attempt to just fucking run code to a halt.
143. throwawaymaths ◴[] No.43759089{7}[source]
> You don’t need to teach it to a beginner.

gp's claim was:

> Python is better because it's easy for people to learn,

i believe then we agree: it is not.

144. throwawaymaths ◴[] No.43759107{7}[source]
tensorflow is atrocious, which is why it's basically dead in favor of (py)torch.
145. nothrabannosir ◴[] No.43759112{8}[source]
Which is valid, but frustrating to see it lead to actual adoption outside of pedagogy. That property is entirely orthogonal to, almost at odds with, what makes a good programming language for medium to large production quality applications.

If we used that logic elsewhere in life we’d all be playing the flute and cycling around on tricycles and balance bikes. But for some reason in tech it’s all about Hello World.

replies(1): >>43760207 #
146. throwawaymaths ◴[] No.43759118{7}[source]
> You don't have to explain jargon-y keywords like "public" or "static" up front.

patently not true. you dont get too far into python -- especially if you are reading (or copypastaing) other People's code -- before you see if __name__ == "__main__" and any potential future programmer will rightfully ask "what the absolute fuck is this"

even "def" is kind of a weird fucking keyword.

Don't get me started about teaching beginners which datatypes are pass by reference and which are pass by value.

try explaining to an elementary school student why

    def foo(a):
       a = a + 1
doesn't change the caller's variable but

    def bar(a):
       a.append(1)
does.
replies(2): >>43763638 #>>43770561 #
147. quinnirill ◴[] No.43759309{8}[source]
What does db.safe do though? How does it know what is the safe way of escaping at that point of the SQL? It will have no idea whether it’s going inside a string, if it’s in a field name position, denotes a value or a table name.

To illustrate the question further, consider a similar html.safe: f"<a href={html.safe(url)}>{html.safe(desc)</a>" - the two calls to html.safe require completely different escaping, how does it know which to apply?

148. pauleveritt ◴[] No.43759637[source]
Do t-strings miss something that f-strings provides for format_spec etc.?

FWIW, format_spec is available in the template structure, so the function writer could at least do a runtime check.

149. ubercore ◴[] No.43759869{12}[source]
Then make `db.unsafe_execute` take a string.
replies(1): >>43761306 #
150. polotics ◴[] No.43760207{9}[source]
The story of the winner being scrapy market entrants that are lower-cost (...of learning, in the case of python) and good-enough-quality (...than OCaml, Lisps, Haskel, definitely not JS or Java) is not a new one. I don't subscribe to your analogies.
151. eska ◴[] No.43761005{7}[source]
In my experience people have to first figure out what the hell numpy is and how to get it (venv, conda, pip, uv, uvx, …) because python arrays are shit, and so people fix that wart with an external C library. Then they notice that some other dependency requires a previous python version, but their python is installed globally and other dependencies were installed for that. These are uniquely python-specific problems. Lisp doesn’t have those problems
replies(1): >>43770626 #
152. Flimm ◴[] No.43761190{3}[source]
PEP 787 – Safer subprocess usage using t-strings https://peps.python.org/pep-0787/
153. thunky ◴[] No.43761306{13}[source]
Yeah, you could. I'm just saying that by doing this you're breaking `db.execute` by not allowing it to take it string like it does now. Libraries may not want to add a breaking change for this.
154. throwawaymaths ◴[] No.43761676{7}[source]
> they're not an absolute beginner

gp's claim is not "its easy to learn". It's not just the concept -- it's the ergonomics, absolutely terrible footguns (especially when dealing with global wheels that can screw up your running system), and the hidden state.

replies(1): >>43764144 #
155. throwawaymaths ◴[] No.43761701{7}[source]
> perfectly fine

It's not, because you can fuck up system/unrelated app python dependencies and in extreme cases have to reinstall OS. Thankfully as developers migrate away from python/adopt strategies like flatpak this is less of a problem.

Other PLs do not have this problem.

156. jimwhite ◴[] No.43763190[source]
Yes and your example is the hero case because it isn't just sugar. A t-string implementation for SQL will of course escape the values which is a common security issue.

https://xkcd.com/327/

replies(1): >>43763414 #
157. hombre_fatal ◴[] No.43763414[source]
No, a t-string returns a Template which is basically { strings: str[], values: any[] }.

So you would write db.execute(template) to turn template t"... where id = {id}" into a parameterized structure like ("... where id = ?", id).

158. dragonwriter ◴[] No.43763638{8}[source]
> Don't get me started about teaching beginners which datatypes are pass by reference and which are pass by value.

If they are beginners to programming, you wouldn't teach them those terms in the context of Python, because neither of those terms map to Python argument passing; Python has one form of argument passing, and it doesn't map closely to the intuition that experienced programmers in languages that have pass by reference and pass by value have about those things. Likewise, the only thing you'd teach someone new to Python that is experienced in languages where those terms are useful is that that distinction is irrelevant in Python, which is pass by assignment (sometimes also called pass by object reference, but pass by assignment is a much more useful description IMO, because argument passing works exactly like assignment to a new variable name.)

> try explaining to an elementary school student why

    def foo(a):
       a = a + 1
> doesn't change the caller's variable but

    def bar(a):
       a.append(1)
> does.

But, that's easy, if you've first taught them how variables, assignment, and mutation work in Python without getting function calls in the way, because it is exactly the same as this

  a = 1
  b = a
  b = a + 1
  print(f"{a=}, {b=}")
vs.

  a = [1]
  b = a
  b.append[1]
  print(f"{a=}, {b=}")
Argument passing is just assignment to a new variable that exists in the scope of the function. Methods which mutate an object affect the object no matter what variable you access it from, assignment operations affect only the variable they assign to. That's exactly the same behavior in one scope as it is between the function and caller scopes.

And this distinction has nothing to do with data types, but only with the operations performed (the only connection to data types is that immutable types have no mutation operations in the first place.) You can tell its not about data types because you can use the same types as the second excerpt, and the operations of the first, and get the same results as the first (which shares operations) and not the second (which shares datatypes):

  a = [1]
  b = a
  b = b + [1]
  print(f"{a=}, {b=}")
If you understand how assignment and mutation works in one scope, you understand how argument passing works. Trying to teach a distinction that exists between how different operations affect variables that initially reference the same object as a distinction about how datatypes are passed as arguments is confusing, because you as a teacher are presenting the source of the difference in behavior as originating in a completely different place than where it actually comes from. That's not a problem with Python being complex, it is a problem with you taking a very simple thing and making it complex by ascribing it to a source that is completely irrelevant to what is actually going on.
replies(1): >>43768300 #
159. psunavy03 ◴[] No.43764144{8}[source]
When would you want to interface between a specific project and the global Python environment running your system? If there's ever a time when "lock the project into a venv and don't cross-contaminate its dependencies with the global Python environment" isn't the answer, that sounds like a corner case. Let each project be its thing by itself.
160. HackerThemAll ◴[] No.43764175{3}[source]
Yeah that would be a backward compatible way to do stuff.
161. Timon3 ◴[] No.43764565{9}[source]
There is no reason to support the first or second usage. It's totally fine to always require a t-string:

    db.execute(t"SELECT foo FROM bar;")
See? No reason to accept strings, it's absolutely fine to always error if a string is passed.
replies(1): >>43765327 #
162. nomel ◴[] No.43764621{6}[source]
Here's the statement checking code, which I believe is pre-AST [1]. I would have to dig more to see if that check is there to prevent invalid AST or to just "help the user" (would depend on how they reference the original variable I suppose).

But wow, that's the first time I've seen "nonlocal". In the ~100 packages I have installed, I see 0 usages!

[1] https://github.com/python/cpython/blob/a6a3dbb7db0516a72c5ef...

replies(1): >>43770427 #
163. angra_mainyu ◴[] No.43765036{6}[source]
I think the take should be a bit more nuanced.

Some languages definitely had people gravitate towards them due to being innovative in a given space, but in many of those cases, the comparative advantage was lost to other languages/techs/frameworks that simply failed to gain a market share "equal to their innovative contribution" due to the first comer's advantage.

164. Izkata ◴[] No.43765327{10}[source]
My (and their) point is that's the already existing API. You're proposing a big breaking change, with how many frameworks and tutorials are built on top of that.
replies(1): >>43765451 #
165. Timon3 ◴[] No.43765451{11}[source]
It's not like this is the first time APIs have been improved. There are many tools (e.g. deprecation warnings & hints in editors, linter rules) that can help bridge the gap - even if t-strings are only used for new or refactored code, it's still a big improvement!

There's also simply no hard requirement to overload an `execute` function. We have options beyond "no templates at all" and "execute takes templates and strings", for example by introducing a separate function. Why does perfect have to be the enemy of good here?

166. dragonwriter ◴[] No.43769181{7}[source]
> It would have to be the other way around or be a (possibly major) breaking change.

If it is going to reject the currently-accepted unsafe usage, its going to be a major breaking change in any case, so I don't see the problem. I mean, if you are lamenting it can't reject the currently-accepted SQL-interpolated-via-f-string because it can't distinguish it by type from plain strings with no interpolation, you are already saying that you want a major breaking change but are upset because the particular implementation you want is not possible. So you can't turn around and dismiss an alternative solution because it would be a major breaking change, that's what was asked for!

167. dragonwriter ◴[] No.43769208{6}[source]
You add a new API that takes templates only leaving the existing API in place. You (some releases later) deprecate the string API. You (some releases later, with clear advance warning of when it is coming) actually remove the deprecated API. "It's a big breaking change around a brand new feature", yeah, so you don't break anything around a brand new feature, it's not like this kind of transition is a new concept.
168. dragonwriter ◴[] No.43769225{4}[source]
> Such library functions tend to also accept a string as a valid input.

Also? They tend only to accept a string (possibly with some additional arguments, if there is an in-library way to handle parameterization) as input, because Template literally hasn't been an option. New APIs designed with Template available will look different.

169. PennRobotics ◴[] No.43769324{4}[source]
and syntax highlighting
170. zahlman ◴[] No.43770427{7}[source]
Well, yes, not a lot of people write closures except perhaps when they implement decorators. So there's ordinarily no non-local scope to worry about. People tend to write classes instead, because that's what's familiar.
171. zahlman ◴[] No.43770561{8}[source]
> you dont get too far into python -- especially if you are reading (or copypastaing) other People's code -- before you see if __name__ == "__main__"

First off, if you are teaching someone, you are showing that person the code, and not allowing copy-and-paste.

Second, no, that comes up much less often than you'd expect.

Third, it's the same as `if value == 'example':`. Underscores are not jargon.

Fourth, it's trivially searchable. That's the part where you can copy and paste - into a search engine, which will immediately find you several competent explanations such as https://stackoverflow.com/questions/419163 .

> even "def" is kind of a weird fucking keyword.

Admittedly a poor choice, but not a deal breaker. You need the concept of functions to do programming. But you don't need the concept of data hiding, nor do you need any of the multiple, barely-related concepts described by the term "static".

> Don't get me started about teaching beginners which datatypes are pass by reference and which are pass by value.

There's nothing to explain. They are all pass by value, per the strict meaning of those terms.

Those terms have been widely seen as less than ideal for decades, however, because they fail to account for variables with reference semantics (i.e., what Python uses - which are sometimes called "names"). A more modern term is "pass by assignment", which correctly describes all variable passing in Python: passing an argument to a parameter is a form of assignment, and works the same way as assigning a value to a name.

This is far less complex than C#, in which user-defined types may have either value semantics or reference semantics, and which supports both pass by assignment and two separate, true forms of pass by reference (for initialization and for modifying an existing object). And certainly it's less complex than whatever C++ is doing (see e.g. https://langdev.stackexchange.com/questions/3798 ).

> try explaining to an elementary school student why

First: if someone gives you a bag with three apples in it, you can put another apple in the bag and give it back, and the other person will have a bag with four apples in it. But if you add 3 + 1, that doesn't change the meaning of 3. These are simple ideas that an elementary school student already understands.

Second: from extensive experience teaching beginners (never mind that you are moving the goalposts now), it makes no sense to get into the theory. It's not helpful. A student who can ask about this has already lost the plot, because the two examples use completely different syntax (a method call versus an assignment) so they shouldn't be expected to work similarly. You avoid this problem by using more precise language early on. "Change" is not an appropriate word here.

Third: you give this example because you think that `bar` (and you imply by your naming that a list is being passed) demonstrates pass by reference. This is simply incorrect. Please read https://nedbatchelder.com/text/names1.html.

Fourth: your use of profanity and the overall tone of your writing suggests that you simply don't like the fact that Python works the way that it does. This is not a good look IMO.

Just for the record, I've been in variations of this discussion countless times. I know what I'm talking about. All links above are in my bookmarks.

172. zahlman ◴[] No.43770626{8}[source]
> what the hell numpy is

Did they try using a search engine? But more to the point, if they don't understand what it is, how did they find out it exists?

> how to get it (venv, conda, pip, uv, uvx, …)

uvx is a command from the same program as uv; venv is not a way to obtain packages; and the choice here isn't a real stumbling block.

> because python arrays are shit

I can't say I've seen many people complain about the standard library `array` module; indeed it doesn't seem like many people are aware it exists in the first place.

If you're talking about lists then they serve a completely different purpose. But your use of profanity suggests to me that you don't have any actual concrete criticism here.

> Then they notice that some other dependency requires a previous python version

Where did this other dependency come from in the first place? How did they get here from a starting point of dissatisfaction with the Python standard library?

> but their python is installed globally and other dependencies were installed for that.

... and that's where actual environment management comes in, yes. Sometimes you have to do that. But this has nothing to do with teaching Python. You have necessarily learned quite a bit by the time this is a real concern, and if you were taught properly then you can self-study everything else you need.

> These are uniquely python-specific problems.

No other languages ever require environment management?

> Lisp doesn’t have those problems

Please tell me about your experience using Qi.