web-dev-qa-db-fra.com

Convertit un tableau d'octets de taille variable en un entier / long

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)?

34
goncalopp

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…

55
abarnert

La fonction struct.unpack (...) fait ce dont vous avez besoin.

2
Curd