Совместное использование в коде строк и байтообразных объектов для запуска в Python 2 и 3
Я пытаюсь изменить код, показанный далеко ниже, который работает в Python 2.7.x, поэтому он также будет работать без изменений в Python 3.x. Однако я сталкиваюсь со следующей проблемой, которую не могу решить в первой функции: bin_to_float()
как показано в результате ниже:
float_to_bin(0.000000): '0'
Traceback (most recent call last):
File "binary-to-a-float-number.py", line 36, in <module>
float = bin_to_float(binary)
File "binary-to-a-float-number.py", line 9, in bin_to_float
return struct.unpack('>d', bf)[0]
TypeError: a bytes-like object is required, not 'str'
Я пытался это исправить, добавив bf = bytes(bf)
прямо перед звонком struct.unpack()
, но это произвело свое собственное TypeError
:
TypeError: string argument without an encoding
Итак, мои вопросы: можно ли исправить эту проблему и достичь моей цели? И если да, то как? Желательно, чтобы это работало в обеих версиях Python.
Вот код, который работает в Python 2:
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('>d', bf)[0]
def int_to_bytes(n, minlen=0): # helper function
""" Int/long to byte string. """
nbits = n.bit_length() + (1 if n < 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 & 0xff))
n >>= 8
if minlen > 0 and len(bytes) < 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('>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('')
3 ответа
У меня был другой подход от ответа @metatoaster. Я только что модифицировал int_to_bytes
использовать и вернуть bytearray
:
def int_to_bytes(n, minlen=0): # helper function
""" Int/long to byte string. """
nbits = n.bit_length() + (1 if n < 0 else 0) # plus one for any sign bit
nbytes = (nbits+7) // 8 # number of whole bytes
b = bytearray()
for _ in range(nbytes):
b.append(n & 0xff)
n >>= 8
if minlen > 0 and len(b) < minlen: # zero pad?
b.extend([0] * (minlen-len(b)))
return bytearray(reversed(b)) # high bytes at beginning
Похоже, что это работает без каких-либо других модификаций под Python 2.7.11 и Python 3.5.1.
Обратите внимание, что я обнуляю 0
вместо '0'
, Я не делал много испытаний, но, конечно, это то, что вы имели в виду?
Чтобы создать переносимый код, который работает с байтами как в Python 2, так и в 3, с использованием библиотек, которые буквально используют разные типы данных между ними, вам нужно явно объявить их, используя соответствующую буквенную метку для каждой строки (или добавить from __future__ import unicode_literals
к вершине каждого модуля, делающего это). Этот шаг должен гарантировать, что ваши типы данных внутренне правильны в вашем коде.
Во-вторых, примите решение о поддержке Python 3 в будущем, с откатами, специфичными для Python 2. Это означает переопределение str
с unicode
и выяснить методы / функции, которые не возвращают одинаковые типы в обеих версиях Python, следует изменить и заменить, чтобы они возвращали правильный тип (являющийся версией Python 3). Обратите внимание, что bytes
тоже зарезервированное слово, так что не используйте его.
Соединив это, ваш код будет выглядеть примерно так:
import struct
import sys
if sys.version_info < (3, 0):
str = unicode
chr = unichr
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(b'>d', bf)[0]
def int_to_bytes(n, minlen=0): # helper function
""" Int/long to byte string. """
nbits = n.bit_length() + (1 if n < 0 else 0) # plus one for any sign bit
nbytes = (nbits+7) // 8 # number of whole bytes
ba = bytearray(b'')
for _ in range(nbytes):
ba.append(n & 0xff)
n >>= 8
if minlen > 0 and len(ba) < minlen: # zero pad?
ba.extend((minlen-len(ba)) * b'0')
return u''.join(str(chr(b)) for b in reversed(ba)).encode('latin1') # high bytes at beginning
# tests
def float_to_bin(f):
""" Convert a float into a binary string. """
ba = struct.pack(b'>d', f)
ba = bytearray(ba)
s = u''.join(u'{:08b}'.format(b) for b in ba)
s = s.lstrip(u'0') # strip leading zeros
return (s if s else u'0').encode('latin1') # but leave at least one
for f in 0.0, 1.0, -14.0, 12.546, 3.141593:
binary = float_to_bin(f)
print(u'float_to_bin(%f): %r' % (f, binary))
float = bin_to_float(binary)
print(u'bin_to_float(%r): %f' % (binary, float))
print(u'')
Я использовал latin1
кодек просто потому, что это то, что байтовые сопоставления изначально определены, и, кажется, работает
$ python2 foo.py
float_to_bin(0.000000): '0'
bin_to_float('0'): 0.000000
float_to_bin(1.000000): '11111111110000000000000000000000000000000000000000000000000000'
bin_to_float('11111111110000000000000000000000000000000000000000000000000000'): 1.000000
float_to_bin(-14.000000): '1100000000101100000000000000000000000000000000000000000000000000'
bin_to_float('1100000000101100000000000000000000000000000000000000000000000000'): -14.000000
float_to_bin(12.546000): '100000000101001000101111000110101001111110111110011101101100100'
bin_to_float('100000000101001000101111000110101001111110111110011101101100100'): 12.546000
float_to_bin(3.141593): '100000000001001001000011111101110000010110000101011110101111111'
bin_to_float('100000000001001001000011111101110000010110000101011110101111111'): 3.141593
Опять же, но на этот раз под Python 3.5)
$ python3 foo.py
float_to_bin(0.000000): b'0'
bin_to_float(b'0'): 0.000000
float_to_bin(1.000000): b'11111111110000000000000000000000000000000000000000000000000000'
bin_to_float(b'11111111110000000000000000000000000000000000000000000000000000'): 1.000000
float_to_bin(-14.000000): b'1100000000101100000000000000000000000000000000000000000000000000'
bin_to_float(b'1100000000101100000000000000000000000000000000000000000000000000'): -14.000000
float_to_bin(12.546000): b'100000000101001000101111000110101001111110111110011101101100100'
bin_to_float(b'100000000101001000101111000110101001111110111110011101101100100'): 12.546000
float_to_bin(3.141593): b'100000000001001001000011111101110000010110000101011110101111111'
bin_to_float(b'100000000001001001000011111101110000010110000101011110101111111'): 3.141593
Это намного больше работы, но в Python3 вы можете более четко видеть, что типы выполняются как правильные байты. Я также изменил ваш bytes = []
к байту, чтобы более четко выразить то, что вы пытались сделать.
В Python 3 целые числа имеют to_bytes()
метод, который может выполнить преобразование в одном вызове. Однако, поскольку вы запросили решение, которое работает на Python 2 и 3 без изменений, вот альтернативный подход.
Если вы берете объезд через шестнадцатеричное представление, функция int_to_bytes()
становится очень просто:
import codecs
def int_to_bytes(n, minlen=0):
hex_str = format(n, "0{}x".format(2 * minlen))
return codecs.decode(hex_str, "hex")
Вам может потребоваться некоторая особая обработка случая, чтобы иметь дело со случаем, когда шестнадцатеричная строка получает нечетное количество символов.
Обратите внимание, что я не уверен, что это работает со всеми версиями Python 3. Я помню, что псевдокодировки не поддерживались в некоторых версиях 3.x, но я не помню деталей. Я тестировал код с Python 3.5.