Como converter uma sequência de bytes em um int?

162

Como posso converter uma sequência de bytes em um int em python?

Diga assim: 'y\xcc\xa6\xbb'

Eu vim com uma maneira inteligente / estúpida de fazer isso:

sum(ord(c) << (i * 8) for i, c in enumerate('y\xcc\xa6\xbb'[::-1]))

Eu sei que tem que haver algo embutido ou na biblioteca padrão que faça isso de forma mais simples ...

Isso é diferente de converter uma sequência de dígitos hexadecimais para a qual você pode usar int (xxx, 16), mas, em vez disso, desejo converter uma sequência de valores reais de bytes.

ATUALIZAR:

Eu meio que gosto da resposta de James um pouco melhor porque não requer a importação de outro módulo, mas o método de Greg é mais rápido:

>>> from timeit import Timer
>>> Timer('struct.unpack("<L", "y\xcc\xa6\xbb")[0]', 'import struct').timeit()
0.36242198944091797
>>> Timer("int('y\xcc\xa6\xbb'.encode('hex'), 16)").timeit()
1.1432669162750244

Meu método hacky:

>>> Timer("sum(ord(c) << (i * 8) for i, c in enumerate('y\xcc\xa6\xbb'[::-1]))").timeit()
2.8819329738616943

ATUALIZAÇÃO ATUALIZADA:

Alguém perguntou nos comentários qual é o problema com a importação de outro módulo. Bem, importar um módulo não é necessariamente barato, dê uma olhada:

>>> Timer("""import struct\nstruct.unpack(">L", "y\xcc\xa6\xbb")[0]""").timeit()
0.98822188377380371

A inclusão do custo de importação do módulo nega quase toda a vantagem que esse método possui. Acredito que isso incluirá apenas as despesas de importação uma vez durante toda a execução do benchmark; veja o que acontece quando eu o forço a recarregar toda vez:

>>> Timer("""reload(struct)\nstruct.unpack(">L", "y\xcc\xa6\xbb")[0]""", 'import struct').timeit()
68.474128007888794

Desnecessário dizer que, se você estiver executando muitas execuções desse método por importação, isso se tornará proporcionalmente menos problemático. Provavelmente, também é um custo de E / S em vez de CPU, por isso pode depender das características de capacidade e carga de uma máquina específica.

ʞɔıu
fonte
e importar algo da lib padrão é ruim, por quê?
26
sua "atualização adicional" é estranha ... por que você importaria o módulo com tanta frequência?
5
Eu sei que essa é uma pergunta antiga. Mas se você quiser manter sua comparação atualizada para outras pessoas: a resposta do caracol mecânico ( int.from_bytes) foi superada struct.unpackno meu computador. Além de ser mais legível, imo.
magu_

Respostas:

110

Você também pode usar o módulo struct para fazer isso:

>>> struct.unpack("<L", "y\xcc\xa6\xbb")[0]
3148270713L
Greg Hewgill
fonte
3
Aviso: "L" é na verdade 8 bytes (não 4) em versões Python de 64 bits; portanto, isso pode falhar.
Rafał Dowgird 15/01/09
12
Rafał: Na verdade, como Greg estava usando <, de acordo com os documentos L é o tamanho padrão (4) "quando a sequência de formatação começa com um de '<', '>', '!' ou '='. " docs.python.org/library/struct.html#format-characters
André Laszlo
59
Esta resposta não funciona para cadeias binárias de comprimento arbitrário.
Amcnabb
4
Os tipos têm tamanhos específicos, nunca funcionarão para cadeias binárias de comprimento arbitrário. Você pode configurar um loop for para lidar com isso se souber o tipo de cada item.
Joshua Olson
2
"L" é realmente uint32 (4 bytes). Se, como no meu caso, você precisar de 8 bytes, use "Q" -> uint64. Observe também que "l" -> int32 e q -> int64
ntg
319

No Python 3.2 e posterior, use

>>> int.from_bytes(b'y\xcc\xa6\xbb', byteorder='big')
2043455163

ou

>>> int.from_bytes(b'y\xcc\xa6\xbb', byteorder='little')
3148270713

de acordo com o endianness da sua string de bytes.

Isso também funciona para bytes inteiros de comprimento arbitrário e para números inteiros assinados com complemento de dois, especificando signed=True. Veja os documentos parafrom_bytes .

Caracol mecânico
fonte
@eri quanto mais lento? Eu costumava usar struct, mas converti para int.from_bytes quando fui para py3. Estou chamando esse método a cada ms, pois estou recebendo dados seriais, para que qualquer aceleração seja bem-vinda. Eu estive olhando para isto
Naib
@ Naib, para os.urandom(4)bytes ** 1,4 µs ** (struct) vs ** 2,3 µs ** (int.from_bytes) no meu processador. python 3.5.2
eri
5
@eri Ressuscitei um script timeit que usei para avaliar alguns métodos CRC. Quatro execuções 1) struct 2) int.from_bytes 3) como # 1, mas compilado cython, 4) como # 2, mas compilado cython. 330ns para struct, 1.14us para int (o cython deu talvez 20ns para aceleração em ambos ...) parece que estou voltando :) essa não é uma otimização prematura, tenho atingido alguns gargalos desagradáveis, especialmente com um milhão de amostras para postar -processo e foram derrubando peças.
Naib
66

Como Greg disse, você pode usar struct se estiver lidando com valores binários, mas se você tiver apenas um "número hexadecimal", mas no formato de byte, poderá convertê-lo como:

s = 'y\xcc\xa6\xbb'
num = int(s.encode('hex'), 16)

... é o mesmo que:

num = struct.unpack(">L", s)[0]

... exceto que funcionará para qualquer número de bytes.

James Antill
fonte
3
qual é exatamente a diferença entre "valores binários" e um "número hexadecimal", mas no formato de bytes "???????
Veja "estrutura de ajuda". Por exemplo. "001122334455" .decode ('hex') não pode ser convertido em um número usando struct.
James Antill
3
A propósito, esta resposta assume que o número inteiro é codificado na ordem de bytes big-endian. Por ordem little-endian, faça:int(''.join(reversed(s)).encode('hex'), 16)
amcnabb 4/13/13
1
bom, mas isso vai ser lento! Acho que isso realmente não importa se você está codificando em Python.
MattCochrane
8

Eu uso a seguinte função para converter dados entre int, hex e bytes.

def bytes2int(str):
 return int(str.encode('hex'), 16)

def bytes2hex(str):
 return '0x'+str.encode('hex')

def int2bytes(i):
 h = int2hex(i)
 return hex2bytes(h)

def int2hex(i):
 return hex(i)

def hex2int(h):
 if len(h) > 1 and h[0:2] == '0x':
  h = h[2:]

 if len(h) % 2:
  h = "0" + h

 return int(h, 16)

def hex2bytes(h):
 if len(h) > 1 and h[0:2] == '0x':
  h = h[2:]

 if len(h) % 2:
  h = "0" + h

 return h.decode('hex')

Fonte: http://opentechnotes.blogspot.com.au/2014/04/convert-values-to-from-integer-hex.html

Jrm
fonte
6
import array
integerValue = array.array("I", 'y\xcc\xa6\xbb')[0]

Aviso: o acima é fortemente específico da plataforma. Tanto o especificador "I" quanto a resistência da conversão string-> int dependem da sua implementação específica do Python. Mas se você deseja converter muitos números inteiros / seqüências de uma vez, o módulo array faz isso rapidamente.

Rafał Dowgird
fonte
5

No Python 2.x, você pode usar os especificadores de formato <Bpara bytes não assinados e <bpara bytes assinados com struct.unpack/ struct.pack.

Por exemplo:

Let x='\xff\x10\x11'

data_ints = struct.unpack('<' + 'B'*len(x), x) # [255, 16, 17]

E:

data_bytes = struct.pack('<' + 'B'*len(data_ints), *data_ints) # '\xff\x10\x11'

Isso *é necessário!

Vejo https://docs.python.org/2/library/struct.html#format-characters para obter uma lista dos especificadores de formato.

Tetralux
fonte
3
>>> reduce(lambda s, x: s*256 + x, bytearray("y\xcc\xa6\xbb"))
2043455163

Teste 1: inverso:

>>> hex(2043455163)
'0x79cca6bb'

Teste 2: Número de bytes> 8:

>>> reduce(lambda s, x: s*256 + x, bytearray("AAAAAAAAAAAAAAA"))
338822822454978555838225329091068225L

Teste 3: incremento de um:

>>> reduce(lambda s, x: s*256 + x, bytearray("AAAAAAAAAAAAAAB"))
338822822454978555838225329091068226L

Teste 4: acrescente um byte, diga 'A':

>>> reduce(lambda s, x: s*256 + x, bytearray("AAAAAAAAAAAAAABA"))
86738642548474510294585684247313465921L

Teste 5: Divida por 256:

>>> reduce(lambda s, x: s*256 + x, bytearray("AAAAAAAAAAAAAABA"))/256
338822822454978555838225329091068226L

Resultado é igual ao resultado do Teste 4, conforme o esperado.

user3076105
fonte
1

Eu estava lutando para encontrar uma solução para seqüências arbitrárias de bytes de comprimento que funcionassem no Python 2.x. Finalmente eu escrevi este, é um pouco hacky porque realiza uma conversão de string, mas funciona.

Função para Python 2.x, tamanho arbitrário

def signedbytes(data):
    """Convert a bytearray into an integer, considering the first bit as
    sign. The data must be big-endian."""
    negative = data[0] & 0x80 > 0

    if negative:
        inverted = bytearray(~d % 256 for d in data)
        return -signedbytes(inverted) - 1

    encoded = str(data).encode('hex')
    return int(encoded, 16)

Esta função possui dois requisitos:

  • A entrada dataprecisa ser a bytearray. Você pode chamar a função assim:

    s = 'y\xcc\xa6\xbb'
    n = signedbytes(s)
  • Os dados precisam ser big-endian. Caso você tenha um valor little-endian, você deve revertê-lo primeiro:

    n = signedbytes(s[::-1])

Obviamente, isso deve ser usado apenas se for necessário um tamanho arbitrário. Caso contrário, atenha-se a formas mais padrão (por exemplo struct).

Andrea Lazzarotto
fonte
1

int.from_bytes é a melhor solução se você estiver na versão> = 3.2. A solução "struct.unpack" requer uma string para que não se aplique a matrizes de bytes. Aqui está outra solução:

def bytes2int( tb, order='big'):
    if order == 'big': seq=[0,1,2,3]
    elif order == 'little': seq=[3,2,1,0]
    i = 0
    for j in seq: i = (i<<8)+tb[j]
    return i

hex (bytes2int ([0x87, 0x65, 0x43, 0x21])) retorna '0x87654321'.

Ele lida com endianness grande e pequeno e é facilmente modificável por 8 bytes

user3435121
fonte
1

Como mencionado acima, usar a unpackfunção de struct é uma boa maneira. Se você deseja implementar sua própria função, existe outra solução:

def bytes_to_int(bytes):
    result = 0
    for b in bytes:
        result = result * 256 + int(b)
return result
abdullahselek
fonte
Isso não funciona para o número negativo que foi convertido em bytes.
Maria
1

No python 3, você pode converter facilmente uma sequência de bytes em uma lista de números inteiros (0..255)

>>> list(b'y\xcc\xa6\xbb')
[121, 204, 166, 187]
fhgd
fonte
0

Um método decentemente rápido que utiliza array.array que venho usando há algum tempo:

variáveis ​​predefinidas:

offset = 0
size = 4
big = True # endian
arr = array('B')
arr.fromstring("\x00\x00\xff\x00") # 5 bytes (encoding issues) [0, 0, 195, 191, 0]

para int: (leia)

val = 0
for v in arr[offset:offset+size][::pow(-1,not big)]: val = (val<<8)|v

from int: (gravação)

val = 16384
arr[offset:offset+size] = \
    array('B',((val>>(i<<3))&255 for i in range(size)))[::pow(-1,not big)]

É possível que eles possam ser mais rápidos.

EDIT:
Para alguns números, aqui está um teste de desempenho (Anaconda 2.3.0) mostrando médias estáveis ​​na leitura em comparação com reduce():

========================= byte array to int.py =========================
5000 iterations; threshold of min + 5000ns:
______________________________________code___|_______min______|_______max______|_______avg______|_efficiency
⣿⠀⠀⠀⠀⡇⢀⡀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⡀⠀⢰⠀⠀⠀⢰⠀⠀⠀⢸⠀⠀⢀⡇⠀⢀⠀⠀⠀⠀⢠⠀⠀⠀⠀⢰⠀⠀⠀⢸⡀⠀⠀⠀⢸⠀⡇⠀⠀⢠⠀⢰⠀⢸⠀
⣿⣦⣴⣰⣦⣿⣾⣧⣤⣷⣦⣤⣶⣾⣿⣦⣼⣶⣷⣶⣸⣴⣤⣀⣾⣾⣄⣤⣾⡆⣾⣿⣿⣶⣾⣾⣶⣿⣤⣾⣤⣤⣴⣼⣾⣼⣴⣤⣼⣷⣆⣴⣴⣿⣾⣷⣧⣶⣼⣴⣿⣶⣿⣶
    val = 0 \nfor v in arr: val = (val<<8)|v |     5373.848ns |   850009.965ns |     ~8649.64ns |  62.128%
⡇⠀⠀⢀⠀⠀⠀⡇⠀⡇⠀⠀⣠⠀⣿⠀⠀⠀⠀⡀⠀⠀⡆⠀⡆⢰⠀⠀⡆⠀⡄⠀⠀⠀⢠⢀⣼⠀⠀⡇⣠⣸⣤⡇⠀⡆⢸⠀⠀⠀⠀⢠⠀⢠⣿⠀⠀⢠⠀⠀⢸⢠⠀⡀
⣧⣶⣶⣾⣶⣷⣴⣿⣾⡇⣤⣶⣿⣸⣿⣶⣶⣶⣶⣧⣷⣼⣷⣷⣷⣿⣦⣴⣧⣄⣷⣠⣷⣶⣾⣸⣿⣶⣶⣷⣿⣿⣿⣷⣧⣷⣼⣦⣶⣾⣿⣾⣼⣿⣿⣶⣶⣼⣦⣼⣾⣿⣶⣷
                  val = reduce( shift, arr ) |     6489.921ns |  5094212.014ns |   ~12040.269ns |  53.902%

Este é um teste de desempenho bruto, então o endian pow-flip é deixado de fora.
A shiftfunção mostrada aplica a mesma operação shift-oring que o loop arrfor e array.array('B',[0,0,255,0])apresenta o desempenho iterativo mais rápido ao lado dict.

Provavelmente também devo observar que a eficiência é medida pela precisão do tempo médio.

Tcll
fonte