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.


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

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 .


@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.


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


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.


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.


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.


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


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


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

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]

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.