> On 9 Mar 2023, at 00:37, Rob Cliffe via Python-ideas > <python-ideas@python.org> wrote: > > Having had my last proposal shot down in flames, up I bob with another. 😁
See this discussion that has a nice solution proposed with the concat function. Barry > It seems to me that it would be useful to be able to make the str.join() > function put separators, not only between the items of its operand, but also > optionally at the beginning or end. > E.g. '|'.join(('Spam', 'Ham', 'Eggs')) returns > 'Spam|Ham|Eggs' > but it might be useful to make it return one of > '|Spam|Ham|Eggs' > 'Spam|Ham|Eggs|' > '|Spam|Ham|Eggs|' > Again, I suggest that this apply to byte strings as well as strings. > Going through the 3.8.3 stdlib I have found > 24 examples where the separator needs to be added at the beginning > 52 where the separator needs to be added at the end > 4 where the separator needs to be added at the both ends > I list these examples below. Apologies if there are any mistakes. > > While guessing is no substitute for measurement, it seems plausible that > using this feature > where appropriate would increase runtime performance by avoiding 1 (or 2) > calls of str.__add__. > This is perhaps more relevant when the separator is not a short constant > string, > as in this example: > Lib\email\_header_value_parser.py:2854: return > policy.linesep.join(lines) + policy.linesep > Note also this example: > Lib\site-packages\setuptools\command\build_ext.py:221: pkg = > '.'.join(ext._full_name.split('.')[:-1] + ['']) > where the author has used the unintuitive device of appending an empty string > to a list to force join() to add an extra final dot, thereby avoiding 1 call > of str.__add__ > at the cost of 1 call of list.append. > > What I cannot decide is what the best API would be. > str.join() currently takes only 1 parameter, so it would be possible to add > an extra parameter or two. > One scheme would be to have an atEnds parameter which could take values such > as > 0=default behaviour 1=add sep at start 2=add sep at end 3=add sep at > both ends > or > 's'=add sep at start 'e'=add sep at end 'b'=add sep at both ends > (some) other=default behaviour > Another would be to have 2 parameters, atStart and atEnd, which would both > default to False or 0. E.g. > '|'.join(('Spam', 'Ham', 'Eggs'), 1) == '|Spam|Ham|Eggs' > '|'.join(('Spam', 'Ham', 'Eggs'), 0, 1) == 'Spam|Ham|Eggs|' > Neither scheme results in particularly transparent usage, though no worse than > s.splitlines(True) # What on earth is this parameter??? > > Corner case: > What if join() is passed an empty sequence? This is debatable, > but I think it should return the separator if requested to add it > at the beginning or end, and double it up if both are requested. > This would preserve identities such as > sep.join(seq, <PleaseAddSeparatorAtBeginning>) == sep + sep.join(seq) > > Best wishes > Rob Cliffe > > EXAMPLES WHERE SEPARATOR ADDED AT START: > > Lib\http\server.py:933: splitpath = ('/' + '/'.join(head_parts), tail_part) > Lib\site-packages\numpy\ctypeslib.py:333: name += "_"+"_".join(flags) > Lib\site-packages\numpy\testing\_private\utils.py:842: err_msg += '\n' + > '\n'.join(remarks) > Lib\site-packages\pip\_vendor\pyparsing\core.py:2092-2095: > out = [ > "\n" + "\n".join(comments) if comments else "", > pyparsing_test.with_line_numbers(t) if with_line_numbers else > t, > ] > Lib\site-packages\pip\_vendor\requests\status_codes.py:121-125: > __doc__ = ( > __doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes)) > if __doc__ is not None > else None > ) > Lib\site-packages\reportlab\lib\utils.py:1093: self._writeln(' '+' > '.join(A.__self__)) > Lib\site-packages\reportlab\platypus\flowables.py:708: L = > "\n"+"\n".join(L) > Lib\site-packages\twisted\mail\smtp.py:1647: r.append(c + b' ' + b' > '.join(v)) > Lib\site-packages\twisted\protocols\ftp.py:1203: return (PWD_REPLY, > '/' + '/'.join(self.workingDirectory)) > Lib\site-packages\twisted\runner\procmon.py:424-426: > return ('<' + self.__class__.__name__ + ' ' > + ' '.join(l) > + '>') > Lib\site-packages\twisted\web\rewrite.py:34: request.path = > '/'+'/'.join(request.prepath+request.postpath) > Lib\site-packages\twisted\web\rewrite.py:51: request.path = > '/'+'/'.join(request.prepath+request.postpath) > Lib\site-packages\twisted\web\twcgi.py:78: scriptName = b"/" + > b"/".join(request.prepath) > Lib\site-packages\twisted\web\twcgi.py:95: env["PATH_INFO"] = "/" + > "/".join(pp) > Lib\site-packages\twisted\web\vhost.py:115: request.path = b'/' + > b'/'.join(request.postpath) > Lib\site-packages\twisted\web\wsgi.py:283: scriptName = b'/' + > b'/'.join(request.prepath) > Lib\site-packages\twisted\web\wsgi.py:288: pathInfo = b'/' + > b'/'.join(request.postpath) > Lib\site-packages\twisted\web\test\test_wsgi.py:272: uri = '/' + > '/'.join([urlquote(seg, safe) for seg in requestSegments]) > Lib\site-packages\wx\py\magic.py:55: command = > 'sx("'+aliasDict[c[0]]+' '+' '.join(c[1:])+'")' > Lib\site-packages\zope\interface\exceptions.py:257-260: > return '\n ' + '\n '.join( > x._str_details.strip() if isinstance(x, _TargetInvalid) else > str(x) > for x in self.exceptions > ) > Lib\smtplib.py:537 and 545: optionlist = ' ' + ' '.join(options) > Lib\unittest\case.py:1094-1096: > diffMsg = '\n' + '\n'.join( > difflib.ndiff(pprint.pformat(seq1).splitlines(), > pprint.pformat(seq2).splitlines())) > Lib\unittest\case.py:1207-1209: > diff = ('\n' + '\n'.join(difflib.ndiff( > pprint.pformat(d1).splitlines(), > pprint.pformat(d2).splitlines()))) > > SEPARATOR ADDED AT END: > > Lib\distutils\command\config.py:303: body = "\n".join(body) + "\n" > Lib\email\contentmanager.py:145: def embedded_body(lines): return > linesep.join(lines) + linesep > Lib\email\contentmanager.py:146: def normal_body(lines): return > b'\n'.join(lines) + b'\n' > Lib\email\policy.py:215: return name + ': ' + self.linesep.join(lines) > + self.linesep > Lib\email\_header_value_parser.py:2854: return policy.linesep.join(lines) > + policy.linesep > Lib\site-packages\numpy\distutils\command\config.py:346: body = > '\n'.join(body) + "\n" > Lib\site-packages\numpy\distutils\command\config.py:407: body = > '\n'.join(body) + "\n" > Lib\site-packages\oauthlib\oauth2\rfc6749\tokens.py:158: base_string = > '\n'.join(base) + '\n' > Lib\site-packages\PIL\ImageCms.py:770: return "\r\n\r\n".join(arr) + > "\r\n\r\n" > Lib\site-packages\pip\_internal\operations\freeze.py:254: return > "\n".join(list(self.comments) + [str(req)]) + "\n" > Lib\site-packages\pip\_internal\operations\install\legacy.py:54: > f.write("\n".join(new_lines) + "\n") > Lib\site-packages\pip\_vendor\pyparsing\testing.py:323-331: > return ( > header1 > + header2 > + "\n".join( > "{:{}d}:{}{}".format(i, lineno_width, line, eol_mark) > for i, line in enumerate(s_lines, start=start_line) > ) > + "\n" > ) > Lib\site-packages\pycparser\c_generator.py:117: if n.storage: s += ' > '.join(n.storage) + ' ' > Lib\site-packages\pycparser\c_generator.py:366: if n.funcspec: s = ' > '.join(n.funcspec) + ' ' > Lib\site-packages\pycparser\c_generator.py:367: if n.storage: s += ' > '.join(n.storage) + ' ' > Lib\site-packages\pycparser\c_generator.py:382: if n.quals: s += ' > '.join(n.quals) + ' ' > Lib\site-packages\pycparser\c_generator.py:397: nstr += ' > '.join(modifier.dim_quals) + ' ' > Lib\site-packages\pycparser\c_generator.py:417: return ' > '.join(n.names) + ' ' > Lib\site-packages\pythonwin\pywin\framework\scriptutils.py:109: return > ".".join(modBits) + "." + fname, newPathReturn > Lib\site-packages\reportlab\pdfbase\pdfdoc.py:1118: code = > '\n'.join(code)+'\n' > Lib\site-packages\reportlab\pdfbase\pdfutils.py:102: > f.write('\r\n'.join(code)+'\r\n') > Lib\site-packages\reportlab\pdfbase\_can_cmap_data.py:54: src = > '\n'.join(buf) + '\n' > Lib\site-packages\reportlab\pdfgen\pdfimages.py:203: content = > '\n'.join(self.imageData[3:-1]) + '\n' > Lib\site-packages\setuptools\command\build_ext.py:221: pkg = > '.'.join(ext._full_name.split('.')[:-1] + ['']) > Lib\site-packages\setuptools\command\easy_install.py:1056: > f.write('\n'.join(locals()[name]) + '\n') > Lib\site-packages\setuptools\command\easy_install.py:1606: data = > '\n'.join(lines) + '\n' > Lib\site-packages\setuptools\command\egg_info.py:672: > cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n') > Lib\site-packages\setuptools\command\egg_info.py:683: value = > '\n'.join(value) + '\n' > Lib\site-packages\setuptools\_distutils\command\config.py:303: body = > "\n".join(body) + "\n" > Lib\site-packages\twisted\conch\manhole.py:360-362: > return (b'\n'.join(self.interpreter.buffer) + > b'\n' + > b''.join(self.lineBuffer)) > Lib\site-packages\twisted\conch\client\knownhosts.py:547-549: > hostsFileObj.write( > b"\n".join([entry.toString() for entry in self._added]) + > b"\n") > Lib\site-packages\twisted\conch\ssh\keys.py:1340: return > b'\n'.join(lines) + b'\n' > Lib\site-packages\twisted\conch\test\test_conch.py:556: expectedResult = > '\n'.join(['line #%02d' % (i,) for i in range(60)]) + '\n' > Lib\site-packages\twisted\conch\test\test_helper.py:360: > self.term.write(b'\n'.join((s1, s2, s3)) + b'\n') > Lib\site-packages\twisted\internet\test\test_process.py:769: > scriptFile.write(os.linesep.join(sourceLines) + os.linesep) > Lib\site-packages\twisted\mail\imap4.py:5713: hdrs = '\r\n'.join(hdrs) + > '\r\n' > Lib\site-packages\twisted\mail\imap4.py:5952: base = > b'.'.join([(x + 1).__bytes__() for x in self.part]) + b'.' + base > Lib\site-packages\twisted\mail\test\test_pop3.py:312: self.message = > b'\n'.join(self.lines) + b'\n' > Lib\site-packages\twisted\mail\test\test_pop3.py:376: output = > b'\r\n'.join(client.response) + b'\r\n' > Lib\site-packages\twisted\mail\test\test_smtp.py:100: message = > b'\n'.join(self.buffer) + b'\n' > Lib\site-packages\twisted\mail\test\test_smtp.py:344: message = > b'\n'.join(self.buffer) + b'\n' > Lib\site-packages\twisted\python\text.py:146: return '\n'.join(lines)+'\n' > Lib\site-packages\twisted\test\test_iutils.py:40: > scriptFile.write(os.linesep.join(sourceLines) + os.linesep) > Lib\site-packages\win32comext\adsi\demos\scp.py:350: description = __doc__ > + "\ncommands:\n" + "\n".join(arg_descs) + "\n" > Lib\site-packages\wx\py\crust.py:259: self.SetValue('\n'.join(hist) + '\n') > Lib\site-packages\wx\py\introspect.py:342: command = > terminator.join(pieces[:-1]) + terminator > Lib\site-packages\zope\interface\document.py:78: return "\n\n".join(r) + > "\n\n" > Lib\test\test_nntplib.py:495: lit = "\r\n".join(lit.splitlines()) + > "\r\n" > Lib\test\test_univnewlines.py:24:DATA_LF = "\n".join(DATA_TEMPLATE) + "\n" > Lib\test\test_univnewlines.py:25:DATA_CR = "\r".join(DATA_TEMPLATE) + "\r" > Lib\test\test_univnewlines.py:26:DATA_CRLF = "\r\n".join(DATA_TEMPLATE) + > "\r\n" > Lib\test\test_tools\test_pindent.py:33: return '\n'.join(line.lstrip() > for line in data.splitlines()) + '\n' > > SEPARATOR ADDED AT BOTH ENDS: > > Lib\pydoc.py:1582: sys.stdout.write('\n' + > '\n'.join(lines[r:r+inc]) + '\n') > Lib\site-packages\office365\runtime\odata\odata_batch_request.py:129: buffer > = eol + eol.join(lines) + eol > Lib\test\test_generators.py:1424: print("|" + "|".join(squares) + > "|") > Lib\test\test_generators.py:1620: print("|" + "|".join(row) + "|") > _______________________________________________ > Python-ideas mailing list -- python-ideas@python.org > To unsubscribe send an email to python-ideas-le...@python.org > https://mail.python.org/mailman3/lists/python-ideas.python.org/ > Message archived at > https://mail.python.org/archives/list/python-ideas@python.org/message/LP56JVRSDPKCMS56A7ZAGAZWTKCXN3DM/ > Code of Conduct: http://python.org/psf/codeofconduct/ _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/RNQPVM4CEQZJSBO7DMIWUJIRYAG6ANRI/ Code of Conduct: http://python.org/psf/codeofconduct/