Bikeshedding:
1) I forgot to mention whether the '&' operator should apply to byte strings as well as to strings.
    I propose that it should (some supporting examples below).
2) Joao suggested spelling the operator '|'.
    But for me: '|' suggests "or" while '&' suggests "and" and is a more intuitive choice for some kind of concatenation.

On 06/03/2023 10:33, Steven D'Aprano wrote:
I find the concept is very easy to understand: "concat with exactly one
space between the operands".  But I must admit I'm struggling to think
of cases where I would use it.
Well, there are use cases for print() separating its operands with single spaces (by default).  So one might guess that something similar would be useful when constructing strings.  But read on, Gentle Reader ...

I like the look of the & operator for concatenation, so I want to like
this proposal. But I think I will need to see real world code to
understand when it would be useful.


Quite right, Steven.  (By the way, thanks for reading my suggestion carefully, as you obviously did.) Below I list some examples from the 3.8.3 stdlib and how (I think) they could be rewritten. (Disclosure: I have selected the examples I found which I think are most convincing.  Then again, I may have missed some.) I say "I think" because to be 100% sure (rather than 90%+ sure) in all cases would sometimes need a thorough analysis of the values that parts of an expression could (reasonably) take: specifically, whether some could be an empty string or all whitespace and what would happen if they were.  Often in practice these cases can be disregarded or are unimportant.  And of course the code author should  know all about these cases. Arguably, good examples are *under-represented* in the stdlib, because '&' will more often be useful in throw-away diagnostic code (which is all about human-readable text) than in production code.

Lib\site-packages\numpy\distutils\system_info.py:2677:
        cmd = config_exe + ' ' + self.append_config_exe + ' ' + option
        cmd = config_exe & self.append_config_exe & option
Lib\site-packages\wx\lib\masked\maskededit.py:3592:
        newstr = value[:self._signpos] + ' ' + value[self._signpos+1:-1] + ' '
        newstr = value[:self._signpos] & value[self._signpos+1:-1] + ' '
Lib\site-packages\wx\lib\masked\maskededit.py:4056:
        text = text[:self._signpos] + ' ' + text[self._signpos+1:self._right_signpos] + ' ' + text[self._right_signpos+1:]         text = text[:self._signpos] & text[self._signpos+1:self._right_signpos] & text[self._right_signpos+1:]
Lib\distutils\sysconfig.py:212:
        ldshared = ldshared + ' ' + os.environ['LDFLAGS']
        ldshared = ldshared & os.environ['LDFLAGS']
    There are 6 more similar examples in the same module.
    The same 7 are in Lib\site-packages\setuptools\_distutils\sysconfig.py.
Lib\site-packages\numpy\lib\tests\test_io.py:328-330:
        assert_equal(c.readlines(),
                     [asbytes((fmt + ' ' + fmt + '\n') % (1, 2)),
                      asbytes((fmt + ' ' + fmt + '\n') % (3, 4))])
        assert_equal(c.readlines(),
                     [asbytes((fmt & fmt + '\n') % (1, 2)), 2)),
                      asbytes((fmt & fmt + '\n') % (3, 4))])
Lib\site-packages\numpy\f2py\crackfortran.py:1068:
        t = typespattern[0].match(m.group('before') + ' ' + name)
        t = typespattern[0].match(m.group('before') & name)
Lib\site-packages\twisted\mail\imap4.py:3606:
        raise IllegalServerResponse('(' + k + ' '+ status[k] + '): ' + str(e))
        raise IllegalServerResponse('(' + k & status[k] + '):' & str(e))
Lib\site-packages\twisted\runner\procmon.py:424-426:
        return ('<' + self.__class__.__name__ + ' '
                + ' '.join(l)
                + '>')
        return ('<' + self.__class__.__name__
                & ' '.join(l)
                + '>')
Lib\site-packages\wx\lib\pydocview.py:3028:
        label = '&' + str(i + 1) + ' ' + frame.GetTitle()
        label = '&' + str(i + 1) & frame.GetTitle()
Lib\test\test_pyexpat.py:90-91:
        self.out.append('Start element: ' + repr(name) + ' ' +
                        sortdict(attrs))
        self.out.append('Start element:' & repr(name) &
                        sortdict(attrs))
Lib\test\test_pyexpat.py:102:
        self.out.append('PI: ' + repr(target) + ' ' + repr(data))
        self.out.append('PI:' & repr(target) & repr(data))
Lib\test\test_pyexpat.py:105:
        self.out.append('NS decl: ' + repr(prefix) + ' ' + repr(uri))
        self.out.append('NS decl:' & repr(prefix) & repr(uri))

In the above 3 examples, it is not necessary to replace the first '+' by '&',
but I think it reads better to use the same operator both times.
(And of course it saves 1 (space) character. 😁)
Similarly in some other examples.

Lib\site-packages\win32\Demos\security\sspi\fetch_url.py:57:
        h.putheader('Authorization', auth_scheme + ' ' + auth)
        h.putheader('Authorization', auth_scheme & auth)
Tools\scripts\which.py:51:
        sts = os.system('ls ' + longlist + ' ' + filename)
        sts = os.system('ls' & longlist & filename)

Less obviously:
Lib\site-packages\pycparser\c_generator.py:406-407:
        nstr = '* %s%s' % (' '.join(modifier.quals),
                           ' ' + nstr if nstr else '')
        nstr = '*' & ' '.join(modifier.quals) & nstr

Examples where (I think) '&' could be applied to byte strings:

Lib\site-packages\twisted\conch\ssh\keys.py:1279:
        return (self.sshType() + b' ' + b64Data + b' ' + comment).strip()
        return (self.sshType() & b64Data & comment).strip()
Lib\site-packages\reportlab\pdfbase\pdfmetrics.py:391:
        text = text + b' ' + bytes(str(self.widths[i]),'utf8')
        text = text & + bytes(str(self.widths[i]),'utf8')
Lib\site-packages\twisted\mail\pop3.py:326:
        yield intToBytes(i) + b' ' + intToBytes(size) + b'\r\n'
        yield intToBytes(i) & intToBytes(size) + b'\r\n'
Lib\site-packages\twisted\mail\pop3.py:367:
        yield intToBytes(i + 1) + b' ' + uid + b'\r\n'
        yield intToBytes(i + 1) & uid + b'\r\n'
Lib\site-packages\twisted\mail\pop3client.py:929:
        return self.sendShort(b'APOP', username + b' ' + digest)
        return self.sendShort(b'APOP', username & + digest)
Lib\site-packages\twisted\mail\pop3client.py:1193-1194:
        return self._consumeOrAppend(b'TOP', idx + b' ' + intToBytes(lines),
                                     consumer, _dotUnquoter)
        return self._consumeOrAppend(b'TOP', idx & intToBytes(lines),
                                     consumer, _dotUnquoter)
Lib\site-packages\twisted\mail\smtp.py:1647:
        r.append(c + b' ' +  b' '.join(v))
        r.append(c & b' '.join(v))
Lib\site-packages\twisted\mail\_cred.py:33:
        return self.user + b' ' + response.encode('ascii')
        return self.user & response.encode('ascii')
Lib\site-packages\twisted\web\test\test_proxy.py:233:
        lines = [b"HTTP/1.0 " + str(code).encode('ascii') + b' ' + message]
        lines = [b"HTTP/1.0" & str(code).encode('ascii') & message]

There are many examples (too many to list) where '&' could be used but would not add a great deal of value and its use or non-use would be largely a matter of taste. (It would be nice if there were always "One Obvious Way To Do It", but in the real world different tools sometimes overlap in their areas of application.)
Some would doubtless find it:
    Pointless
    Obscure
Of course (like any new feature) it *would* be more obscure - until we got used to it! 😁
Others might welcome (in appropriate use cases):
    The guarantee that the result string contains one and only one space between its textual parts,       even when the first/second operand  of '&' contains (unexpectedly?) trailing/leading whitespace.
    This guarantee making the code, in a way, *more* explicit.
    An ampersand being more visible than a leading/trailing space inside string quotes
        (as I am finding out the hard way, proof-reading this e-mail!🙁).
    (or even) Saving a few characters.  (Wash your mouth out with soap, Rob! 😬)
A few reasonably representative examples:

Lib\cgitb.py:106:
        pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
        pyver = 'Python' & sys.version.split()[0] + ':' & sys.executable
Lib\ftplib.py:289:
    cmd = 'PORT ' + ','.join(bytes)
    cmd = 'PORT' & ','.join(bytes)
Lib\site-packages\pythonwin\pywin\tools\browser.py:182:
        return str(self.name) + ' (Instance of class ' + str(self.myobject.__class__.__name__) + ')'         return str(self.name) & '(Instance of class' & str(self.myobject.__class__.__name__) + ')'
Lib\site-packages\pythonwin\pywin\framework\scriptutils.py:564:
        win32ui.SetStatusText('Failed to ' + what + ' - ' + str(details) )
        win32ui.SetStatusText('Failed to' & what & '-' & str(details) )

Personally, I think that examples such as the last, where multiple components are all joined with '&', are ones that particularly gain from increased clarity and reduced clutter.  YMMV.

Finally: how I might rewrite in Python a sample fragment from my own code in a different language:     VehDesc.SetLabel(Vehicle.Reg_No & Vehicle.Make & Vehicle.Type & Vehicle.Colour)

Best wishes
Rob Cliffe
_______________________________________________
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/7E7HIK3BJIH3YFKE37OKZCAWMBXZIK3T/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to