<div dir="ltr"><br><div class="gmail_extra"><br><br><div class="gmail_quote">On Sat, Jan 11, 2014 at 8:20 PM, Terry Reedy <span dir="ltr"><<a href="mailto:tjreedy@udel.edu" target="_blank">tjreedy@udel.edu</a>></span> wrote:<br>

<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">The following function interpolates bytes, bytearrays, and formatted strings, the latter two auto-converted to bytes, into a bytes (or auto-converted bytearray) format. This function automates much of what some people have recommended for combining ascii text and binary blogs. The test passes on 2.7.6 as well as 3.3.3, though a 2.7-only version would be simpler.<br>


===============<br>
<br>
# bf.py -- Terry Jan Reedy, 2014 Jan 11<br>
"Define byteformat(): a bytes version of str.format as a function."<br>
import re<br>
<br>
def byteformat(form, obs):<br>
    '''Return bytes-formated objects interpolated into bytes format.<br>
<br>
    The bytes or bytearray format has two types of replacement fields.<br>
    b'{}' and b'{:}': The object can be any raw bytes or bytearray object.<br>
    b'{:<format_spec>}: The object can by any object ob that can be<br>
    string-formated with <format_spec>. Bytearray are converted to bytes.<br>
<br>
    The text encoding is the default (encoding="utf-8", errors="strict").<br>
    Users should be explicitly encode to bytes for any other encoding.<br>
    The struct module can by used to produce bytes, such as binary-formated<br>
    integers, that are not encoded text.<br>
<br>
    Test passes on both 2.7.6 and 3.3.3.<br>
    '''<br>
<br>
    if isinstance(form, bytearray):<br>
        form = bytes(form)<br>
    fields = re.split(b'{:?([^}]*)}', form)<br>
    # print(fields)<br>
    if len(fields) != 2*len(obs)+1:<br>
        raise ValueError('Number of replacement fields not same as len(obs)')<br>
    j = 1 # index into fields<br>
    for ob in obs:<br>
        if isinstance(ob, bytearray):<br>
            ob = bytes(ob)<br>
        field = fields[j]<br>
        fields[j] = format(ob, field.decode()).encode() if field else ob<br>
        j += 2<br>
    return b''.join(fields)<br>
<br>
# test code<br>
bformat = b"bytes: {}; bytearray: {:}; unicode: {:s}; int: {:5d}; float: {:7.2f}; end"<br>
objects = (b'abc', bytearray(b'def'), u'ghi', 123, 12.3)<br>
result = byteformat(bformat, objects)<br>
result2 = byteformat(bytearray(bformat), objects)<br>
strings = (ob.decode()  if isinstance(ob, (bytes, bytearray)) else ob<br>
               for ob in objects)<br>
expect = bformat.decode().format(*<u></u>strings).encode()<br>
<br>
#print(result)<br>
#print(result2)<br>
print(expect)<br>
assert result == result2 == expect<br>
<br>
=====<br>
This has been edited from what I posted to issue 3982 to expand the docstrings and to work the same with both bytes and bytearrays on both 2.7 and 3.3. When I posted before, I though of it merely as a proof-of-concept prototype. After reading the seemingly endless discussion of possible variations of byte formatting with % and .format, I now present it as a real, concrete, proposal.<br>


<br>
There are, of course, details that could be tweaked. The encoding uses the default, which on 3.x is (encoding='utf-8', errors='strict').  This could be changed to an explicit encoding='ascii'. If that were done, the encoding could be made a parameter that defaults to 'ascii'. The joiner could be defined as type(form)() so the output type matches the input form type. I did not do that because it complicates the test.<br>

</blockquote><div><br></div><div>With that flexibility this matches what I have been mulling in the back of my head all day. Basically everything that goes in is assumed to be bytes unless {:s} says to expect something which can be passed to str() and then use some specified encoding in all instances (stupid example following as it might be easier with bytes.join, but it gets the point across)::</div>

<div><br></div><div>  formatter = format_bytes('latin1', 'strict')</div><div>  http_response = formatter(b'Content-Type: {:s}\r\n\r\nContent-Length: {:s}\r\n\r\n{}', 'image/jpeg', len(data), data)</div>

<div><br></div><div>Nothing fancy, just an easy way to handle having to call str.encode() on every text argument that is to end up as bytes as Terry is proposing (and I'm fine with defaulting to ASCII/strict with no arguments). Otherwise you do what R. David Murray suggested and just have people rely on their own API which accepts what they want and then spits out what they want behind the scenes.</div>

<div><br></div><div>It basically comes down to how much tweaking of existing Python 2.7 %/.format() calls people will be expected to make. I'm fine with asking people to call a function like what Terry is proposing as it can do away with baking in that ASCII is reasonable as well as not require a bunch of work without us having to argue over what bytes.format() should or should not do. Personally I say bytes.format() is fine but it shouldn't do any text encoding which makes its usefulness rather minor (much like the other text-like methods that got carried forward in hopes that they would be useful to people porting code; maybe we should consider taking them out in Python 4 or something if we find out no one is using them).</div>

<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
The coercion of interpolated bytearray objects to bytes is needed for 2.7 because in 2.7, str/bytes.join raises TypeError for bytearrays in the input sequence. A 3.x-only version could drop this.<br>
<br>
One objection to the function is that it is neither % or .format. To me, this is an advantage in that a new function will not be expected to exactly match the % or .format behavior in either 2.x or 3.x. It eliminates the 'matching the old' arguments so we can focus on what actual functionality is needed. </blockquote>

<div><br></div><div>Agreed.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">There is no need to convert true binary bytes to text with either latin-1 or surrogates. There is no need to add anything to bytes. The code above uses the built-in facilities that we already have, which to me should be the first thing to try, not the last.<br>

</blockquote><div><br></div><div>I think we are all losing sight of the fact that we are talking about Python 3.5 here. Even with an accelerated release schedule of a year that is still a year away! I think any proposal being made should be prototyped in pure Python and tried on a handful or real world examples to see how the results end up looking like to measure how useful they are on their own and how much work it is to port to using it. I think the goal should be a balance and not going to an extreme to minimize porting work from Python 2.7 at the cost of polluting the bytes/string separation and letting people entirely ignore encoding of strings.</div>

<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
One new feature that does not match old behavior is that {} and {:} are changed (in 3.x) to indicate bytes whereas {:s} continues to indicate (in 3.x) unicode text. ({:s} might be changed to mean unicode for 2.7 also, but I did not explore that idea.) Similarly, a new function is free to borrow only the format_spec part of replace of replacement fields and use format(ob, format_spec) to format each object. Anyone who needs the full power of str.format is free to use it explicitly. I think format_specs cover most of what people have asked for.<br>


<br>
For future releases, the function could go in the string module. It could otherwise be added to existing or future 2&3 porting packages.</blockquote><div><br></div><div>I don't think the string module is the right place since this is meant to operate on bytes, but then again I don't know where it would end up if it went into the stdlib. If we have it take the string encoding arguments it could be a method on the bytes type by being a factory method::</div>

<div><br></div><div>  formatter = bytes.formatter('latin1', 'strict')</div><div>  ...</div><div><br></div><div>I would be willing to go as far as making 'strict' the default 'error' argument, but I would say it's still go to make people specify even 'ascii', otherwise people lose sight that bytes([ord(1)]) == b'1' == '1'.encode('ascii') != 1 .to_bytes(1, 'big') and that is a key thing to grasp.</div>

</div></div></div>