Comment puis-je convertir un tableau d'octets binaires de taille variable (big endian) en un entier (non signé)/long? Par exemple, '\x11\x34'
, qui représente 4404
En ce moment, j'utilise
def bytes_to_int(bytes):
return int(bytes.encode('hex'), 16)
Ce qui est petit et assez lisible, mais probablement pas très efficace. Y a-t-il un meilleur moyen (plus évident)?
Traditionnellement, Python n’a pas beaucoup d’utilisation pour les "nombres dans la disposition C en big-endian" qui sont trop gros pour C. (Si vous avez affaire à des nombres de 2 octets, 4 octets ou 8 octets, alors struct.unpack
Est la réponse.)
Mais assez de gens en ont eu marre de ne pas trouver un moyen évident de faire cela Python 3.2 a ajouté une méthode int.from_bytes
) qui fait exactement ce que vous voulez:
int.from_bytes(b, byteorder='big', signed=False)
Malheureusement, si vous utilisez une version plus ancienne de Python, vous ne l'avez pas. Alors, quelles options avez-vous? (Outre l'évident: mise à jour vers 3.2, ou mieux, 3.4…)
Tout d'abord, il y a votre code. Je pense que binascii.hexlify
Est un meilleur moyen de l'épeler que .encode('hex')
, parce que "encoder" a toujours semblé un peu bizarre pour une méthode sur les chaînes d'octets (par opposition aux chaînes Unicode), et c'est en fait été banni dans Python 3. Mais sinon, cela me semble assez lisible et évident. Et ça devrait être assez rapide - oui, il faut créer une chaîne intermédiaire, mais tout est fait le bouclage et l'arithmétique en C (au moins en CPython), qui est généralement un ordre de grandeur ou deux fois plus rapide qu'en Python. À moins que votre bytearray
ne soit si gros que l'attribution de la chaîne sera coûteuse, je ne le ferais pas. Ne vous inquiétez pas de la performance ici.
Alternativement, vous pouvez le faire en boucle. Mais cela sera plus bavard et, du moins dans CPython, beaucoup plus lent.
Vous pouvez essayer d'éliminer la boucle explicite d'une boucle implicite, mais la fonction évidente à faire est reduce
, ce qui est considéré comme non-Pythonique par une partie de la communauté - et bien sûr, il faudra appeler une fonction. pour chaque octet.
Vous pouvez dérouler la boucle ou reduce
en la découpant en morceaux de 8 octets et en passant en boucle sur struct.unpack_from
, Ou simplement en faisant une grosse struct.unpack('Q'*len(b)//8 + 'B' * len(b)%8)
et une boucle dessus, mais le rend beaucoup moins lisible et probablement pas beaucoup plus rapidement.
Vous pouvez utiliser NumPy… mais si vous utilisez une taille supérieure à 64 ou 128 bits, la conversion sera finalement convertie en objets Python de toute façon.
Donc, je pense que votre réponse est la meilleure option.
Voici quelques comparaisons avec la conversion manuelle la plus évidente:
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
Pour comparaison dans 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
Donc, votre méthode est encore assez rapide…
La fonction struct.unpack (...) fait ce dont vous avez besoin.