Как преобразовать целое число в строку байтов переменной длины?

Я хочу преобразовать целое число (int или же long) байтовая строка с прямым порядком байтов. Строка байтов должна иметь переменную длину, чтобы использовалось только минимальное количество байтов (общая длина предыдущих данных известна, поэтому можно определить переменную длину).

Мое текущее решение

import bitstring

bitstring.BitString(hex=hex(456)).tobytes()

Что, очевидно, зависит от порядкового номера машины и дает ложные результаты, потому что 0 бит добавляются, а не добавляются.

Кто-нибудь знает способ сделать это, не делая никаких предположений о длине или бесконечности int?

4 ответа

Решение

Если вы используете Python 2.7 или более поздней версии, вы можете использовать bit_length метод округления длины до следующего байта:

>>> i = 456
>>> bitstring.BitString(uint=i, length=(i.bit_length()+7)/8*8).bytes
'\x01\xc8'

в противном случае вы можете просто проверить целостность байта и заполнить его нулевым кусочком в начале, если это необходимо:

>>> s = bitstring.BitString(hex=hex(i))
>>> ('0x0' + s if s.len%8 else s).bytes
'\x01\xc8'

Что-то вроде этого. Не проверено (до следующего редактирования). Для Python 2.x. Предполагается, что n > 0.

tmp = []
while n:
    n, d = divmod(n, 256)
    tmp.append(chr(d))
result = ''.join(tmp[::-1])

Редактировать: проверено.

Если вы не читаете руководства, но любите битбэшировать, вместо divmod Капер, попробуйте это:

d = n & 0xFF; n >>= 8

Редактировать 2: Если ваши цифры относительно малы, следующее может быть быстрее:

result = ''
while n:
    result = chr(n & 0xFF) + result
    n >>= 8

Редактировать 3: Второй метод не предполагает, что int уже является бигендовым. Вот что происходит в общеизвестно маленькой среде:

Python 2.7 (r27:82525, Jul  4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> n = 65539
>>> result = ''
>>> while n:
...     result = chr(n & 0xFF) + result
...     n >>= 8
...
>>> result
'\x01\x00\x03'
>>> import sys; sys.byteorder
'little'
>>>

Решение с использованием struct а также itertools:

>>> import itertools, struct
>>> "".join(itertools.dropwhile(lambda c: not(ord(c)), struct.pack(">i", 456))) or chr(0)
'\x01\xc8'

Мы можем бросить itertools с помощью простой строки:

>>> struct.pack(">i", 456).lstrip(chr(0)) or chr(0)
'\x01\xc8'

Или даже падение struct используя рекурсивную функцию:

def to_bytes(n): 
    return ([chr(n & 255)] + to_bytes(n >> 8) if n > 0 else [])

"".join(reversed(to_bytes(456))) or chr(0)

Я переформулировал второй ответ Джона Мачинса в одну строку для использования на моем сервере:

def bytestring(n):
    return ''.join([chr((n>>(i*8))&0xFF) for i in range(n.bit_length()/8,-1,-1)])

Я обнаружил, что второй метод, использующий сдвиг битов, был быстрее для больших и малых чисел, а не только для маленьких.

Другие вопросы по тегам