python - example - Convertir una matriz de bytes de tamaño variable en un entero/largo
python struct example (2)
La función docs.python.org/2/library/struct.html hace lo que necesita.
¿Cómo puedo convertir una matriz de bytes binarios de tamaño variable (big endian) en un entero / sin signo (sin signo)? Como ejemplo, ''/x11/x34''
, que representa 4404
En este momento, estoy usando
def bytes_to_int(bytes):
return int(bytes.encode(''hex''), 16)
Que es pequeño y algo legible, pero probablemente no muy eficiente. ¿Hay alguna forma mejor (más obvia)?
Python tradicionalmente no tiene mucho uso para los "números en el diseño de big-endian C" que son demasiado grandes para C. (Si se trata de números de 2 bytes, 4 bytes u 8 bytes, struct.unpack
es la respuesta.)
Pero hay suficiente gente que se int.from_bytes
que no hubiera una forma obvia de hacer esto, por lo que Python 3.2 agregó un método int.from_bytes
que hace exactamente lo que usted quiere:
int.from_bytes(b, byteorder=''big'', signed=False)
Desafortunadamente, si está utilizando una versión anterior de Python, no tiene esta. Entonces, ¿qué opciones tienes? (Además de lo obvio: actualización a 3.2, o, mejor, 3.4 ...)
Primero, ahí está tu código. Creo que binascii.hexlify
es una mejor manera de deletrearlo que .encode(''hex'')
, porque "codificar" siempre ha sido un poco extraño para un método en cadenas de bytes (en oposición a cadenas de Unicode), y de hecho ha sido desterrado en Python 3. Pero por lo demás, me parece bastante legible y obvio. Y debería ser bastante rápido; sí, tiene que crear una cadena intermedia, pero está haciendo todo el ciclo y la aritmética en C (al menos en CPython), que generalmente es un orden de magnitud o dos más rápido que en Python. A menos que su bytearray
sea tan grande que la asignación de la cadena sea costosa, no me preocuparía el rendimiento aquí.
Alternativamente, podrías hacerlo en un bucle. Pero eso será más detallado y, al menos en CPython, mucho más lento.
Podría intentar eliminar el bucle explícito de uno implícito, pero la función obvia para hacerlo es reduce
, lo que se considera no pitónico por parte de la comunidad, y por supuesto requerirá llamar una función para cada byte.
Podría desenrollar el bucle o reduce
struct.unpack_from
en trozos de 8 bytes y struct.unpack_from
en struct.unpack_from
, o simplemente haciendo un gran struct.unpack(''Q''*len(b)//8 + ''B'' * len(b)%8)
y repitiendo eso, pero eso lo hace mucho menos legible y probablemente no mucho más rápido.
Podría usar NumPy ... pero si va a ser más grande que 64 o quizás 128 bits, de todos modos va a terminar convirtiendo todo en objetos Python.
Entonces, creo que tu respuesta es la mejor opción.
Aquí hay algunos tiempos que lo comparan con la conversión manual más obvia:
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
Para comparación en 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
Entonces, tu método sigue siendo bastante rápido ...