Having had my last proposal shot down in flames, up I bob with another. 😁
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/

Reply via email to