Using strings and byte-like objects compatibly in code to run in both Python 2 & 3
<p>I'm trying to modify the code shown far below, which works in Python 2.7.x, so it will also work unchanged in Python 3.x. However I'm encountering the following problem I can't solve in the first function, <code>bin_to_float()</code> as shown by the output below:</p>

<pre class="lang-none prettyprint-override"><code>float_to_bin(0.000000): '0'
Traceback (most recent call last):
  File "binary-to-a-float-number.py", line 36, in &lt;module&gt;
    float = bin_to_float(binary)
  File "binary-to-a-float-number.py", line 9, in bin_to_float
    return struct.unpack('&gt;d', bf)[0]
TypeError: a bytes-like object is required, not 'str'
</code></pre>

<p>I tried to fix that by adding a <code>bf = bytes(bf)</code> right before the call to <code>struct.unpack()</code>, but doing so produced its own <code>TypeError</code>:</p>

<pre><code>TypeError: string argument without an encoding
</code></pre>

<p>So my questions are is it possible to fix this issue and achieve my goal? And if so, how? Preferably in a way that would work in both versions of Python. </p>

<p>Here's the code that works in Python 2:</p>

<pre><code>import struct

def bin_to_float(b):
    """ Convert binary string to a float. """
    bf = int_to_bytes(int(b, 2), 8)  # 8 bytes needed for IEEE 754 binary64
    return struct.unpack('&gt;d', bf)[0]

def int_to_bytes(n, minlen=0):  # helper function
    """ Int/long to byte string. """
    nbits = n.bit_length() + (1 if n &lt; 0 else 0)  # plus one for any sign bit
    nbytes = (nbits+7) // 8  # number of whole bytes
    bytes = []
    for _ in range(nbytes):
        bytes.append(chr(n &amp; 0xff))
        n &gt;&gt;= 8
    if minlen &gt; 0 and len(bytes) &lt; minlen:  # zero pad?
        bytes.extend((minlen-len(bytes)) * '0')
    return ''.join(reversed(bytes))  # high bytes at beginning

# tests

def float_to_bin(f):
    """ Convert a float into a binary string. """
    ba = struct.pack('&gt;d', f)
    ba = bytearray(ba)
    s = ''.join('{:08b}'.format(b) for b in ba)
    s = s.lstrip('0')  # strip leading zeros
    return s if s else '0'  # but leave at least one

for f in 0.0, 1.0, -14.0, 12.546, 3.141593:
    binary = float_to_bin(f)
    print('float_to_bin(%f): %r' % (f, binary))
    float = bin_to_float(binary)
    print('bin_to_float(%r): %f' % (binary, float))
    print('')
</code></pre>

<p>In Python 3, integers have a <code>to_bytes()</code> method that can perform the conversion in a single call.  However, since you asked for a solution that works on Python 2 and 3 unmodified, here's an alternative approach.</p>

<p>If you take a detour via hexadecimal representation, the function <code>int_to_bytes()</code> becomes very simple:</p>

<pre><code>import codecs

def int_to_bytes(n, minlen=0):
    hex_str = format(n, "0{}x".format(2 * minlen))
    return codecs.decode(hex_str, "hex")
</code></pre>

<p>You might need some special case handling to deal with the case when the hex string gets an odd number of characters.</p>

<p>Note that I'm not sure this works with all versions of Python 3.  I remember that pseudo-encodings weren't supported in some 3.x version, but I don't remember the details.  I tested the code with Python 3.5.</p>

python string python-2.7 python-3.x bytestring