Подтвердить что ты не робот

Преобразование массив байтов с переменным размером в целое/длинное

Как я могу преобразовать двоичный байтовый массив с переменным размером (big endian) в целое число (без знака)/long? В качестве примера, '\x11\x34', который представляет 4404

Сейчас я использую

def bytes_to_int(bytes):
  return int(bytes.encode('hex'), 16)

Маленький и немного читаемый, но, вероятно, не очень эффективный. Есть ли лучший (более очевидный) способ?

4b9b3361

Ответ 1

Python традиционно не имеет большого смысла для "чисел в макете big-endian C", которые слишком велики для C. (Если вы имеете дело с 2-байтовыми, 4-байтными или 8-байтовыми числами, тогда struct.unpack - ответ.)

Но достаточно людей стало тошнить от того, что не было одного очевидного способа сделать это, чтобы Python 3.2 добавил метод int.from_bytes, который делает именно то, что вы хотите:

int.from_bytes(b, byteorder='big', signed=False)

К сожалению, если вы используете более старую версию Python, у вас ее нет. Итак, какие у вас варианты? (Помимо очевидного: обновление до 3.2, или, лучше, 3.4...)


Во-первых, там ваш код. Я думаю, что binascii.hexlify - лучший способ записать его, чем .encode('hex'), потому что "encode" всегда казался немного странным для метода по байтовым строкам (в отличие от строк Unicode), и на самом деле он был изгнан в Python 3 Но в остальном это кажется мне вполне понятным и понятным. И это должно быть довольно быстро - да, он должен создать промежуточную строку, но он выполняет всю петлю и арифметику в C (по крайней мере, в CPython), что, как правило, на порядок или два быстрее, чем в Python. Если ваш bytearray настолько велик, что выделение строки само по себе будет дорогостоящим, я бы не стал беспокоиться о производительности здесь.

В качестве альтернативы вы можете сделать это в цикле. Но это будет более многословным и, по крайней мере, в CPython, намного медленнее.

Вы можете попытаться устранить явный цикл для неявного, но очевидная функция для этого - reduce, которая считается частью не-Pythonic частью сообщества - и, конечно же, она потребует вызова функции для каждого байта.

Вы можете развернуть цикл или reduce, разбив его на куски по 8 байт и перейдя через struct.unpack_from или просто сделав большой struct.unpack('Q'*len(b)//8 + 'B' * len(b)%8) и перейдя по нему, но это делает его намного менее удобочитаемым и вероятно, не намного быстрее.

Вы можете использовать NumPy... но если вы собираетесь больше, чем 64 или, может быть, 128 бит, он все равно преобразует все объекты Python.

Итак, я думаю, что ваш ответ - лучший вариант.


Ниже приведены некоторые тайминги, сравнивающие его с наиболее очевидным ручным преобразованием:

import binascii
import functools
import numpy as np

def hexint(b):
    return int(binascii.hexlify(b), 16)

def loop1(b):
    def f(x, y): return (x<<8)|y
    return functools.reduce(f, b, 0)

def loop2(b):
    x = 0
    for c in b:
        x <<= 8
        x |= c
    return x

def numpily(b):
    n = np.array(list(b))
    p = 1 << np.arange(len(b)-1, -1, -1, dtype=object)
    return np.sum(n * p)

In [226]: b = bytearray(range(256))

In [227]: %timeit hexint(b)
1000000 loops, best of 3: 1.8 µs per loop

In [228]: %timeit loop1(b)
10000 loops, best of 3: 57.7 µs per loop

In [229]: %timeit loop2(b)
10000 loops, best of 3: 46.4 µs per loop

In [283]: %timeit numpily(b)
10000 loops, best of 3: 88.5 µs per loop

Для сравнения в Python 3.4:

In [17]: %timeit hexint(b)
1000000 loops, best of 3: 1.69 µs per loop

In [17]: %timeit int.from_bytes(b, byteorder='big', signed=False)
1000000 loops, best of 3: 1.42 µs per loop

Итак, ваш метод все еще довольно быстро...

Ответ 2

Функция struct.unpack(...) делает то, что вам нужно.