Как преобразовать строку байтов в int?

162

Как я могу преобразовать строку байтов в int в Python?

Скажи так: 'y\xcc\xa6\xbb'

Я придумал умный / глупый способ сделать это:

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

Я знаю, что должно быть что-то встроенное или в стандартной библиотеке, которая делает это проще ...

Это отличается от преобразования строки шестнадцатеричных цифр, для которой вы можете использовать int (xxx, 16), но вместо этого я хочу преобразовать строку фактических значений байтов.

ОБНОВИТЬ:

Мне нравится ответ Джеймса немного лучше, потому что он не требует импорта другого модуля, но метод Грега быстрее:

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

Мой хакерский метод:

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

ДОПОЛНИТЕЛЬНОЕ ОБНОВЛЕНИЕ:

Кто-то спросил в комментариях, в чем проблема с импортом другого модуля. Что ж, импорт модуля не обязательно дешев, посмотрите:

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

В том числе стоимость импорта модуля сводит на нет практически все преимущества этого метода. Я полагаю, что это будет включать только затраты на его импорт один раз для всего теста; посмотрите, что происходит, когда я заставляю его каждый раз перезагружаться

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

Излишне говорить, что если вы выполняете много операций этого метода для одного импорта, это становится пропорционально меньшей проблемой. Это также, вероятно, стоимость ввода-вывода, а не процессора, поэтому она может зависеть от производительности и характеристик нагрузки конкретной машины.

ʞɔıu
источник
и импортировать что-то из стандартной библиотеки плохо, почему?
andyway, дубликат: stackoverflow.com/questions/5415/…
26
ваше "дальнейшее обновление" странное ... зачем вам импортировать модуль так часто?
5
Я знаю, что это старый вопрос. Но если вы хотите, чтобы ваше сравнение было актуальным для других людей: ответ «Механическая улитка» ( int.from_bytes) не выполняется struct.unpackна моем компьютере. Рядом с более читабельным ИМО.
magu_

Ответы:

110

Вы также можете использовать модуль struct для этого:

>>> struct.unpack("<L", "y\xcc\xa6\xbb")[0]
3148270713L
Грег Хьюгилл
источник
3
Предупреждение: «L» на самом деле составляет 8 байтов (не 4) в 64-битных сборках Python, поэтому это может привести к сбою.
Рафал Доугирд
12
Рафал: Не совсем, так как Грег использовал <, в соответствии с документацией L - стандартный размер (4) ", когда строка формата начинается с одного из '<', '>', '!' или '='. " docs.python.org/library/struct.html#format-characters
Андре Ласло
59
Этот ответ не работает для двоичных строк произвольной длины.
amcnabb
4
Типы имеют определенный размер, он никогда не будет работать для двоичных строк произвольной длины. Вы можете настроить цикл for для этого, если знаете тип каждого элемента.
Джошуа Олсон
2
«L» - это фактически uint32 (4 байта). Если, как и в моем случае, вам нужно 8 байтов, используйте «Q» -> uint64. Также обратите внимание, что "l" -> int32 и q -> int64
ntg
319

В Python 3.2 и позже используйте

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

или

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

в соответствии с порядком байтов вашей строки.

Это также работает для целых чисел bytestring произвольной длины и для целых чисел со знаком с двумя дополнительными числами путем указания signed=True. Смотрите документы дляfrom_bytes .

Механическая улитка
источник
@eri насколько медленнее? Я имел обыкновение использовать struct, но преобразовал в int.from_bytes, когда перешел на py3. Я вызываю этот метод каждую мс, так как я получаю последовательные данные, поэтому любое ускорение приветствуется. Я смотрел на это
Наиб
@Naib, для os.urandom(4)байтов ** 1,4 мкс ** (структура) против ** 2,3 мкс ** (int.from_bytes) в моем процессоре. питон 3.5.2
eri
5
@eri Я воскресил скрипт timeit, который использовал для оценки нескольких методов CRC. Четыре запуска: 1) struct 2) int.from_bytes 3) как # 1, но Cython скомпилирован, 4) как # 2, но Cython скомпилирован. 330 нс для структуры, 1,14 мкс для int (Cython дал, возможно, ускорение на 20 нс в обоих случаях ...) похоже, я переключаюсь назад :) это не преждевременная оптимизация, я столкнулся с некоторыми неприятными узкими местами, особенно с миллионами образцов для публикации -процесс и стук деталей.
Наиб
66

Как сказал Грег, вы можете использовать struct, если вы имеете дело с двоичными значениями, но если у вас просто есть «шестнадцатеричное число», но в байтовом формате, вы можете просто преобразовать его следующим образом:

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

... это так же, как:

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

... за исключением того, что он будет работать для любого количества байтов.

Джеймс Антилл
источник
3
в чем именно разница между "двоичными значениями" и "шестнадцатеричным числом", но в байтовом формате "???????
Смотри "Справочная структура". Например. «001122334455» .decode ('hex') нельзя преобразовать в число с помощью struct.
Джеймс Антилл
3
Кстати, этот ответ предполагает, что целое число закодировано в порядке байтов с прямым порядком байтов. Для порядка с int(''.join(reversed(s)).encode('hex'), 16)
прямым
1
хорошо, но это будет медленно! Думаю, это не имеет значения, если вы пишете код на Python.
MattCochrane
8

Я использую следующую функцию для преобразования данных между int, hex и байтами.

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

Источник: http://opentechnotes.blogspot.com.au/2014/04/convert-values-to-from-integer-hex.html

Jrm
источник
6
import array
integerValue = array.array("I", 'y\xcc\xa6\xbb')[0]

Предупреждение: вышесказанное строго зависит от платформы. И спецификатор "I", и порядковый номер преобразования string-> int зависят от конкретной реализации Python. Но если вы хотите преобразовать много целых чисел / строк одновременно, то модуль массива сделает это быстро.

Рафал Доугирд
источник
5

В Python 2.x вы могли использовать спецификаторы формата <Bдля байтов без знака и <bдля байтов со знаком с struct.unpack/ struct.pack.

Например:

Let x='\xff\x10\x11'

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

И:

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

Это *требуется!

Видеть https://docs.python.org/2/library/struct.html#format-characters для получения списка спецификаторов формата.

Tetralux
источник
3
>>> reduce(lambda s, x: s*256 + x, bytearray("y\xcc\xa6\xbb"))
2043455163

Тест 1: обратный:

>>> hex(2043455163)
'0x79cca6bb'

Тест 2: Количество байтов> 8:

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

Тест 3: увеличение на единицу:

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

Тест 4: добавьте один байт, скажите «A»:

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

Тест 5: разделить на 256:

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

Результат равен результат теста 4, как и ожидалось.

user3076105
источник
1

Я изо всех сил пытался найти решение для последовательности байтов произвольной длины, которая работала бы в Python 2.x. Наконец, я написал этот, он немного хакерский, потому что он выполняет преобразование строк, но он работает.

Функция для Python 2.x, произвольной длины

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)

Эта функция имеет два требования:

  • Вход dataдолжен быть bytearray. Вы можете вызвать функцию следующим образом:

    s = 'y\xcc\xa6\xbb'
    n = signedbytes(s)
  • Данные должны быть порядковыми. Если у вас есть значение с прямым порядком байтов, вы должны сначала изменить его:

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

Конечно, это следует использовать, только если необходима произвольная длина. В противном случае придерживайтесь более стандартных способов (например struct).

Андреа Лаззаротто
источник
1

int.from_bytes - лучшее решение, если вы используете версию> = 3.2. Решение "struct.unpack" требует строку, поэтому оно не будет применяться к массивам байтов. Вот еще одно решение:

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])) возвращает «0x87654321».

Он обрабатывает большие и меньшие числа байтов и легко модифицируется для 8 байтов

user3435121
источник
1

Как уже упоминалось выше, использование unpackфункции struct является хорошим способом. Если вы хотите реализовать свою собственную функцию, есть другое решение:

def bytes_to_int(bytes):
    result = 0
    for b in bytes:
        result = result * 256 + int(b)
return result
abdullahselek
источник
Это не работает для отрицательного числа, которое было преобразовано в байты.
Мария
1

В Python 3 вы можете легко преобразовать байтовую строку в список целых чисел (0..255) с помощью

>>> list(b'y\xcc\xa6\xbb')
[121, 204, 166, 187]
fhgd
источник
0

Достаточно быстрый метод, использующий array.array, который я использовал в течение некоторого времени:

предопределенные переменные:

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]

к int: (читать)

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

из int: (написать)

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

Возможно, они могут быть быстрее, хотя.

РЕДАКТИРОВАТЬ:
Для некоторых чисел, вот тест производительности (Anaconda 2.3.0), показывающий стабильные средние значения при чтении по сравнению с 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%

Это необработанный тест производительности, так что endian pow-flip исключен. Функции показано , применяется один и тот же сдвиг-ORing операцию как для цикла, и это просто , как это имеет быстрый итеративный производительность рядом .
shiftarrarray.array('B',[0,0,255,0])dict

Я должен также отметить, что эффективность измеряется с точностью до среднего времени.

Tcll
источник