
On Apr 1, 2020, at 20:59, Steven D'Aprano <steve@pearwood.info> wrote:
Nevertheless, you do make a good point. It may be that StringIO is not the right place for this. That can be debated without dismissing the entire idea.
I think it’s worth separating the two. There is a legitimate desire for a “better” way to build strings than str.join. And a string builder type is one obvious solution, as used in many other languages, like Java. To be worth adding, this string builder type should be more discoverable than str.join, and at least as readable, and close to as efficient in both time and space. Ideally, it should be more readable to people with a visceral dislike of str.join, and be significantly better in space (by not retaining all of the input strings for the entire length of the building process) on all Python implementations, and there should also be an easy to use backport. Almost none of this is true for StringIO.__iadd__, but all of it could easily be true for a new string.StringBuilder (using the name I think Stephen suggested). It’s an obvious name, that will probably be the first thing anyone finds in any reasonable search for how to do a string builder in Python. Once found, the meaning is pretty clear. And its help or docs will be useful, concise, and to the point instead of being all about irrelevant and confusing file stuff with += buried somewhere in the middle as a synonym for write. It can’t be easily misused (e.g., even people who know StringIO enough to suggest using it as a string builder mistakenly think `sb = StringIO('abc'): sb.write('def')` will append rather than overwrite). It can be documented to be roughly as fast as str.join but without retaining all of the input strings, on all implementations, which isn’t true for StringIO (which saves no space on some implementations, like CPython, and saves space at the cost of wasting a lot of time on others). It can have a better API—take an initial string on construction, give a useful repr for debugging, etc. Once there are pure-Python and C implementations, backporting them should be trivial—and, because it’s a new class rather than a change to an existing one, using the backport will be trivial too: try: from string import StringBuilder except ImportError: from stringbuilder import StringBuilder # pip install this sb = StringBuilder() for thing in things: sb += make_a_string(thing) s = str(sb) And of course people who only need to support 3.10+ could just import directly without the backport. This would still violate TOOWDTI. And that koan really isn’t just a joke; it’s a serious guideline, just not an absolute one. Adding a second way to do something faces an extra hurdle that has to be overcome, beyond the usual hurdle of conservativeness, but plenty of proposals have overcome that hurdle—str.format, for example, had obvious huge costs from two ways to do something so basic, but its benefits (especially in extensibility) were even huger. Here, the benefits aren’t as large, but the cost isn’t either. So it’s at least arguable that it’s worth doing—not because we should ignore TOOWTDI, but because the benefits in discoverability, readability, and maybe space efficiency outweigh the cost of two ways to do it. If someone wants to propose a string builder that meets that burden (and write the Python and C implementations), I’d be +0.5. But StringIO.__iadd__ does not, which is why I’m -1 on it. (For a third-party library, I’m not sure I’d bother with a C implementation—it can just check if CPython and if so use a str as its buffer… but for a builtin member of a stdlib module, that’s probably not acceptable.)