Comments on optimizations around string concatenation
gist.github.com
gist.github.com
The big advantage is that there is just a single "string" type. Compare this with Java or Rust where you have separate types for mutable strings (StringBuilder in Java; String in Rust) and immutable strings (String in Java; str in Rust). This is nice both for newcomers (which don't have to learn about all the details about how to optimize concatenation) and if you're just writing code which is never going to be in the hot path anyway.
I understand that in some settings (when you really care about performance) it's good to have an explicit difference between these, but I quite like the reduced complexity.
I would have thought if the performance of anything in a language like Python mattered, it'd be how quickly you can build up a text response, like a JSON document or rendered HTML?
In my implementation of Ruby I use immutable ropes for strings - so instead of mutable arrays of characters I have persistent trees of immutable arrays of characters. This makes a massive literal 10x impact on real-world code like template rendering.
Not to detract from your point (which is broadly correct) but this isn't exactly right for Rust. Or at least isn't the right way to phrase it.
Rust has one string type, `str`, which can be referenced immutably (`&str`) or mutably `&mut str`. What `str` can't do is manage memory, which means it can't grow a string. `String` is more correctly a `StringBuffer` (technically a `Vec<u8>` albeit with additional checks). It can manage memory and therefore grow the size of the string, either by using spare capacity or by reallocating the memory. It is not really a string type in itself; it's just a memory buffer for holding a `str`.
This is all a long winded way of saying Rust separates managing memory from operating on the memory. This isn't the same as an mutable/immutable distinction although it is in some ways similar to growable/un-growable.
That said, there are many use cases where performance does not matter as long as it is not "too much", for different values of "too much" depending on context.
Your typical assembly programmer may be far more aware of their obligation to do so than your typical Python programmer, so in my mind it's more important that languages occupying Python's ecological niche behave predictably. It can be challenging to balance that need against other constraints like limiting the number of abstractions someone needs to master in order to be productive.
Sometimes raw performance does save developer time, because you don't have to worry that much about algorithm. :)
So, this is a lesson you just have to learn at some time.