Jak przekonwertować ciąg bajtów na int?

162

Jak mogę przekonwertować ciąg bajtów na int w Pythonie?

Powiedz tak: 'y\xcc\xa6\xbb'

Wymyśliłem sprytny / głupi sposób na zrobienie tego:

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

Wiem, że musi być coś wbudowanego lub w standardowej bibliotece, które robi to prościej ...

Różni się to od konwersji ciągu cyfr szesnastkowych, dla których można użyć int (xxx, 16), ale zamiast tego chcę przekonwertować ciąg rzeczywistych wartości bajtów.

AKTUALIZACJA:

Odpowiedź Jamesa podoba mi się trochę lepiej, ponieważ nie wymaga importowania innego modułu, ale metoda Grega jest szybsza:

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

Moja zhackowana metoda:

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

DALSZA AKTUALIZACJA:

Ktoś w komentarzach zapytał, jaki jest problem z importem innego modułu. Cóż, importowanie modułu niekoniecznie jest tanie, spójrz:

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

Uwzględnienie kosztu importu modułu neguje prawie wszystkie zalety tej metody. Uważam, że będzie to obejmować koszt importu tylko raz na cały przebieg benchmarku; zobacz, co się dzieje, gdy za każdym razem zmuszam go do ponownego ładowania:

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

Nie trzeba dodawać, że jeśli wykonujesz wiele wykonań tej metody na jeden import, to staje się to proporcjonalnie mniejszym problemem. Prawdopodobnie jest to również koszt wejścia / wyjścia, a nie procesora, więc może to zależeć od pojemności i charakterystyki obciążenia konkretnej maszyny.

ʞɔıu
źródło
a importowanie czegoś ze standardowej biblioteki jest złe, dlaczego?
26
twoja „dalsza aktualizacja” jest dziwna… dlaczego tak często importujesz moduł?
5
Wiem, że to stare pytanie. Ale jeśli chcesz, aby Twoje porównanie było aktualne dla innych osób: odpowiedź mechanicznego ślimaka ( int.from_bytes) jest lepsza niż struct.unpackna moim komputerze. Oprócz większej czytelności imo.
magu_

Odpowiedzi:

110

Możesz również użyć modułu struct , aby to zrobić:

>>> struct.unpack("<L", "y\xcc\xa6\xbb")[0]
3148270713L
Greg Hewgill
źródło
3
Ostrzeżenie: "L" to w rzeczywistości 8 bajtów (nie 4) w 64-bitowych kompilacjach Pythona, więc może to zawieść.
Rafał Dowgird
12
Rafał: Nie bardzo, ponieważ Greg używał <, zgodnie z dokumentacją L jest standardowym rozmiarem (4) ", gdy łańcuch formatu zaczyna się od jednego z '<', '>', '!' lub „=”. " docs.python.org/library/struct.html#format-characters
André Laszlo,
59
Ta odpowiedź nie działa w przypadku ciągów binarnych o dowolnej długości.
amcnabb
4
Typy mają określone rozmiary, nigdy nie będą działać dla ciągów binarnych o dowolnej długości. Możesz skonfigurować pętlę for, aby sobie z tym poradzić, jeśli znasz typ każdego elementu.
Joshua Olson
2
„L” to właściwie uint32 (4 bajty). Jeśli tak jak w moim przypadku potrzebujesz 8 bajtów, użyj "Q" -> uint64. Należy również pamiętać, że "L" -> Int32 i q -> Int64
NTG
319

W Pythonie 3.2 i nowszych wersjach użyj

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

lub

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

zgodnie z endianness twojego ciągu bajtów.

Działa to również w przypadku liczb całkowitych z bajtowania o dowolnej długości i liczb całkowitych ze znakiem z uzupełnieniem do dwóch przez określenie signed=True. Zobacz dokumentację dlafrom_bytes .

Ślimak mechaniczny
źródło
@eri o ile wolniej? Kiedyś użyłem struct, ale przekonwertowałem go na int.from_bytes, kiedy przeszedłem do py3. Wzywam tę metodę co ms, ponieważ otrzymuję dane szeregowe, więc wszelkie przyspieszenie jest mile widziane.
Patrzyłem
@Naib, dla os.urandom(4)bajtów ** 1,4 µs ** (struct) vs ** 2,3 µs ** (int.from_bytes) na moim procesorze. python 3.5.2
eri
5
@eri Wskrzesiłem skrypt timeit, którego użyłem do oceny kilku metod CRC. Cztery przebiegi 1) struct 2) int.from_bytes 3) jako # 1, ale cython skompilowany, 4) jako # 2, ale cython skompilowany. 330ns dla struct, 1,14us dla int (cython dał może 20ns przyspieszenia w obu ...) wygląda na to, że przełączam się z powrotem :) to nie jest przedwczesna optymalizacja, natrafiłem na paskudne wąskie gardła, szczególnie z milionem próbek do wysłania -procesować i odpychać części.
Naib,
66

Jak powiedział Greg, możesz użyć struct, jeśli masz do czynienia z wartościami binarnymi, ale jeśli masz tylko „liczbę szesnastkową”, ale w formacie bajtowym, możesz po prostu przekonwertować ją na przykład:

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

... to jest to samo co:

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

... z wyjątkiem tego, że będzie działać dla dowolnej liczby bajtów.

James Antill
źródło
3
jaka dokładnie jest różnica między „wartościami binarnymi” a „liczbą szesnastkową”, ale w formacie bajtowym „???????
Zobacz „help struct”. Na przykład. „001122334455” .decode („hex”) nie może zostać przekonwertowane na liczbę przy użyciu struct.
James Antill
3
Nawiasem mówiąc, ta odpowiedź zakłada, że ​​liczba całkowita jest zakodowana w kolejności bajtów big-endian. Aby uzyskać zamówienie little-endian, zrób:int(''.join(reversed(s)).encode('hex'), 16)
amcnabb
1
dobrze, ale to będzie powolne! Zgadnij, że to nie ma znaczenia, jeśli programujesz w Pythonie.
MattCochrane
8

Używam następującej funkcji do konwersji danych między int, hex i bajtami.

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

Źródło: http://opentechnotes.blogspot.com.au/2014/04/convert-values-to-from-integer-hex.html

Jrm
źródło
6
import array
integerValue = array.array("I", 'y\xcc\xa6\xbb')[0]

Ostrzeżenie: powyższe informacje są ściśle związane z platformą. Zarówno specyfikator "I", jak i endianness konwersji string-> int są zależne od konkretnej implementacji Pythona. Ale jeśli chcesz przekonwertować wiele liczb całkowitych / ciągów na raz, moduł tablicy robi to szybko.

Rafał Dowgird
źródło
5

W Pythonie 2.x można użyć specyfikatorów formatu <Bdla bajtów bez znaku i <bbajtów ze znakiem z struct.unpack/ struct.pack.

Na przykład:

Niech x='\xff\x10\x11'

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

I:

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

To *jest wymagane!

Widzieć https://docs.python.org/2/library/struct.html#format-characters, aby uzyskać listę specyfikatorów formatu.

Tetralux
źródło
3
>>> reduce(lambda s, x: s*256 + x, bytearray("y\xcc\xa6\xbb"))
2043455163

Test 1: odwrotny:

>>> hex(2043455163)
'0x79cca6bb'

Test 2: Liczba bajtów> 8:

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

Test 3: Przyrost o jeden:

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

Test 4: Dołącz jeden bajt, powiedz „A”:

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

Test 5: Podziel przez 256:

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

Wynik jest równy wynikowi testu 4, zgodnie z oczekiwaniami.

user3076105
źródło
1

Starałem się znaleźć rozwiązanie dla sekwencji bajtów o dowolnej długości, które działałyby pod Pythonem 2.x. Wreszcie napisałem ten, jest trochę hacky, ponieważ wykonuje konwersję ciągów, ale działa.

Funkcja dla Pythona 2.x, dowolna długość

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)

Ta funkcja ma dwa wymagania:

  • Dane wejściowe datamuszą mieć format bytearray. Możesz wywołać tę funkcję w ten sposób:

    s = 'y\xcc\xa6\xbb'
    n = signedbytes(s)
  • Dane muszą być typu big-endian. Jeśli masz wartość little-endian, powinieneś najpierw ją odwrócić:

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

Oczywiście powinno to być używane tylko wtedy, gdy potrzebna jest dowolna długość. W przeciwnym razie trzymaj się bardziej standardowych sposobów (np struct.).

Andrea Lazzarotto
źródło
1

int.from_bytes to najlepsze rozwiązanie, jeśli używasz wersji> = 3.2. Rozwiązanie „struct.unpack” wymaga łańcucha, więc nie będzie miało zastosowania do tablic bajtów. Oto inne rozwiązanie:

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])) zwraca „0x87654321”.

Obsługuje duże i małe endianness i jest łatwo modyfikowalny do 8 bajtów

user3435121
źródło
1

Jak wspomniano powyżej, użycie unpackfunkcji struct jest dobrym sposobem. Jeśli chcesz zaimplementować własną funkcję to jest inne rozwiązanie:

def bytes_to_int(bytes):
    result = 0
    for b in bytes:
        result = result * 256 + int(b)
return result
abdullahselek
źródło
To nie działa dla liczby ujemnej, która została przekonwertowana na bajty.
Maria
1

W Pythonie 3 można łatwo przekonwertować ciąg bajtów na listę liczb całkowitych (0..255) za pomocą

>>> list(b'y\xcc\xa6\xbb')
[121, 204, 166, 187]
fhgd
źródło
0

Przyzwoicie szybka metoda wykorzystująca array.array, której używam od jakiegoś czasu:

predefiniowane zmienne:

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]

to int: (czytaj)

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

from int: (napisz)

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

Możliwe, że te mogą być szybsze.

EDYCJA:
W przypadku niektórych liczb, oto test wydajności (Anaconda 2.3.0) pokazujący stabilne średnie podczas odczytu w porównaniu z 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%

To jest surowy test wydajności, więc endian pow-flip jest pomijany. Funkcja pokazano stosuje tę samą operację shift-oring jak dla pętli, i to tylko jak to ma najszybszy iteracyjny wydajność obok .
shiftarrarray.array('B',[0,0,255,0])dict

Powinienem chyba również zauważyć, że efektywność mierzy się dokładnością do średniego czasu.

Tcll
źródło