2010-08-23 9 views
5

Quiero convertir un número entero (int o long) una cadena de bytes big-endian. La cadena de bytes tiene que ser de longitud variable, de modo que solo se utiliza el número mínimo de bytes (se conoce la longitud de longitud total de los datos anteriores, por lo que se puede inferir la longitud variable).Cómo convertir un entero a una cadena de bytes de longitud variable?

Mi solución actual es

import bitstring 

bitstring.BitString(hex=hex(456)).tobytes() 

que depende obviamente de la orden de bits de la máquina y da resultados falsos, debido a 0 bits se anexan y sin antepuesto.

¿Alguien sabe una manera de hacer esto sin hacer ninguna suposición acerca de la longitud o endianess de un int?

+0

¿Esto solo tiene que funcionar para un 'int', o necesita trabajar también durante un' long'? – jchl

+0

Por 'long' también, me olvidé de esto. Editaré la pregunta. –

+0

Esto se puede hacer simplemente en cualquier versión de Python sin dependencias externas; en cualquier caso, desea una cadena BYTE, no una BITstring. –

Respuesta

0

Si está utilizando Python 2.7 o posterior, puede utilizar el método bit_length para redondear la longitud hasta el byte siguiente:

>>> i = 456 
>>> bitstring.BitString(uint=i, length=(i.bit_length()+7)/8*8).bytes 
'\x01\xc8' 

lo contrario se puede probar simplemente para toda-byteness y la almohadilla con un mordisco cero al principio si es necesario:

>>> s = bitstring.BitString(hex=hex(i)) 
>>> ('0x0' + s if s.len%8 else s).bytes 
'\x01\xc8' 
+0

'bit_length' parece ser una solución limpia (aunque estoy en Python 2.6 en Debian). '(i.bit_length() + 7)/8 * 8' redondea la longitud a una longitud divisible por 8, ¿estoy en lo cierto? El problema de endianness también todavía existe. –

+0

Encontré una [explicación del redondeo] (http://stackoverflow.com/questions/2403631/how-do-i-find-the-next-multiple-of-10-of-any-integer). Entonces solo el problema de endianness permanece. –

+0

'uint' es un alias para' uintbe', por lo que el problema de endianess también se resuelve. –

6

Algo como esto. No probado (hasta la próxima edición). Para Python 2.x. Asume n> 0.

tmp = [] 
while n: 
    n, d = divmod(n, 256) 
    tmp.append(chr(d)) 
result = ''.join(tmp[::-1]) 

Editar: probado.

Si usted no lee los manuales pero como bitbashing, en lugar de la divmod alcaparra, intenta esto:

d = n & 0xFF; n >>= 8 

Edición 2: Si los números son relativamente pequeñas, lo siguiente puede ser más rápido:

result = '' 
while n: 
    result = chr(n & 0xFF) + result 
    n >>= 8 

Edición 3: El segundo método no supone que el int ya es bigendio. Esto es lo que sucede en un ambiente notoriamente littleEndian:

Python 2.7 (r27:82525, Jul 4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32 
Type "help", "copyright", "credits" or "license" for more information. 
>>> n = 65539 
>>> result = '' 
>>> while n: 
...  result = chr(n & 0xFF) + result 
...  n >>= 8 
... 
>>> result 
'\x01\x00\x03' 
>>> import sys; sys.byteorder 
'little' 
>>> 
+0

Esto supone que 1 byte equivale a 8 bits. No sé si puedes hacer esta suposición con respecto a la semántica de Python. El segundo método supone que el entero ya está en big-endian. –

+1

@ott: es bastante seguro decir que 1 byte equivale a 8 bits, y los enteros de Python no tienen endianness, es solo un problema en la forma en que se almacenan o transmiten (es decir, es solo un problema si has descomprimido incorrectamente ' n' de algún lugar antes de llegar tan lejos). Ambos métodos se ven bien para mí. –

+0

En realidad, simplemente asume que un byte tiene al menos * 8 bits, lo que está garantizado por el estándar C y, por lo tanto, por el tipo C PyBytes. – dan04

1

Una solución usando struct y itertools:

>>> import itertools, struct 
>>> "".join(itertools.dropwhile(lambda c: not(ord(c)), struct.pack(">i", 456))) or chr(0) 
'\x01\xc8' 

Podemos caer itertools mediante el uso de un simple tira de la cadena:

>>> struct.pack(">i", 456).lstrip(chr(0)) or chr(0) 
'\x01\xc8' 

O incluso soltar struct usando una función recursiva:

def to_bytes(n): 
    return ([chr(n & 255)] + to_bytes(n >> 8) if n > 0 else []) 

"".join(reversed(to_bytes(456))) or chr(0) 
+0

El método 'struct.pack' no funciona, porque' struct.unpack' requiere una longitud fija. Para los otros métodos también necesitarías una función inversa (trivial). –

0

que reformuló segunda respuesta John Machins en una línea para su uso en mi servidor:

def bytestring(n): 
    return ''.join([chr((n>>(i*8))&0xFF) for i in range(n.bit_length()/8,-1,-1)]) 

He encontrado que el segundo método, usando el cambio de bit, fue más rápido para números grandes y pequeños, y no solo para números pequeños.

+0

Me sale un error al usar esto con enteros grandes. p.ej. big = 2442323423424323434242335353 => TypeError: el objeto 'float' no se puede interpretar como un entero – bjmc

Cuestiones relacionadas