Ответ 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
Итак, ваш метод все еще довольно быстро...