2010-08-23 4 views
5

Voglio convertire un numero intero (int o long) una stringa di byte big-endian. La stringa di byte deve essere di lunghezza variabile, in modo che venga utilizzato solo il numero minimo di byte (la lunghezza totale dei dati precedenti è nota, quindi è possibile dedurre la lunghezza variabile).Come convertire un numero intero in una stringa di byte di lunghezza variabile?

mia soluzione attuale è

import bitstring 

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

che dipende ovviamente dalla endianness della macchina e dà risultati falsi, perché 0 bit sono aggiungono e non anteporre.

Qualcuno conosce un modo per farlo senza fare alcuna ipotesi sulla lunghezza o l'endianess di un int?

+0

Questo ha solo bisogno di funzionare per un 'int', o ha bisogno di lavorare anche per un' lungo? – jchl

+0

Anche per 'long' ho dimenticato questo. Modificherò la domanda. –

+0

Questo può essere fatto semplicemente in qualsiasi versione di Python senza dipendenze esterne - in ogni caso, si desidera una stringa BYTE, non una BITstring. –

risposta

0

Se stai usando Python 2.7 o successiva, è possibile utilizzare il metodo bit_length per arrotondare la lunghezza fino al prossimo byte:

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

altrimenti si può solo provare per tutto il byteness e pad con un bocconcino zero all'inizio, se necessario:

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

'bit_length' sembra essere una soluzione pulita (anche se sono su Python 2.6 su Debian). '(i.bit_length() + 7)/8 * 8' arrotonda la lunghezza a una lunghezza divisibile per 8, ho ragione? Anche il problema di endianità esiste ancora. –

+0

Ho trovato una [spiegazione per l'arrotondamento] (http://stackoverflow.com/questions/2403631/how-do-i-find-the-next-multiple-of-10-of-any-integer). Quindi rimane solo il problema di endianness. –

+0

'uint' è un alias per' uintbe', quindi viene risolto anche il problema di endianess. –

6

Qualcosa di simile. Non testato (fino alla prossima modifica). Per Python 2.x. Assume n> 0.

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

Modifica: testato.

Se non leggere i manuali, ma amo bitbashing, al posto del divmod cappero, provate questo:

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

Edit 2: Se i numeri sono relativamente piccole, possono essere più veloce:

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

Modifica 3: il secondo metodo non presuppone che l'int sia già bigendiano. Ecco cosa succede in 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

Ciò presuppone che 1 byte sia uguale a 8 bit. Non so se puoi fare questa ipotesi riguardo alla semantica di Python. Il secondo metodo presuppone che il numero intero sia già in big-endian. –

+1

@ott: è abbastanza sicuro dire che 1 byte equivale a 8 bit, e gli stessi integer Python non hanno endianness - è solo un problema nel modo in cui sono memorizzati o trasmessi (ovvero è un problema solo se hai spacchettato in modo errato ' n' da qualche parte prima di arrivare così lontano). Entrambi i metodi mi stanno bene. –

+0

In realtà, si presuppone semplicemente che un byte sia * minimo * 8 bit, che è garantito dallo standard C, e quindi dal tipo C PyBytes. – dan04

1

Una soluzione che utilizza struct e itertools:

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

Possiamo cadere itertools utilizzando una semplice striscia di stringa:

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

O anche rilasciare struct utilizzando una funzione ricorsiva:

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

Il metodo 'struct.pack' non funziona, perché' struct.unpack' richiede una lunghezza fissa. Per gli altri metodi avresti anche bisogno di una funzione inversa (banale). –

0

ho riformulato John Machins seconda risposta in una sola riga per l'uso sul mio server:

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

Ho trovato che il secondo metodo, utilizzando lo spostamento di bit, era più veloce sia per numeri grandi che piccoli, e non solo per numeri piccoli.

+0

Ricevo un errore utilizzando questo con numeri interi grandi. per esempio. big = 2442323423424323434242335353 => TypeError: l'oggetto 'float' non può essere interpretato come un numero intero – bjmc