
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