Как преобразовать целое число в строку байтов переменной длины? - PullRequest
5 голосов
/ 23 августа 2010

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

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

import bitstring

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

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

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

Ответы [ 4 ]

6 голосов
/ 23 августа 2010

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

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

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

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

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'
>>>
1 голос
/ 23 августа 2010

Решение с использованием 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)
0 голосов
/ 18 января 2013

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

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

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

0 голосов
/ 23 августа 2010

Если вы используете 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'
...