Преобразование int в байты в Python 3

178

Я пытался построить этот объект байтов в Python 3:

b'3\r\n'

поэтому я попробовал очевидное (для меня) и обнаружил странное поведение:

>>> bytes(3) + b'\r\n'
b'\x00\x00\x00\r\n'

По-видимому:

>>> bytes(10)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

Я не смог увидеть никаких указателей на то, почему преобразование байтов работает таким образом, читая документацию. Однако в этой проблеме Python я обнаружил несколько неожиданных сообщений о добавлении formatв байты (см. Также форматирование байтов в Python 3 ):

http://bugs.python.org/issue3982

Это еще хуже взаимодействует со странностями, такими как bytes (int), теперь возвращая нули

и:

Для меня было бы намного удобнее, если бы bytes (int) возвращал ASCIIfication этого int; но, честно говоря, даже ошибка будет лучше, чем это поведение. (Если бы я хотел такого поведения - которого у меня никогда не было - я бы предпочел, чтобы это был метод класса, вызываемый как «bytes.zeroes (n)».)

Может кто-нибудь объяснить мне, откуда это поведение?

astrojuanlu
источник
1
связанные с названием:3 .to_bytes
JFS
2
Из вашего вопроса неясно, хотите ли вы целочисленное значение 3 или значение символа ASCII, представляющего число три (целое значение 51). Первый - это байты ([3]) == b '\ x03'. Последний является байтами ([ord ('3')]) == b'3 '.
Флорисла,

Ответы:

177

Это так, как это было задумано - и это имеет смысл, потому что обычно вы вызываете bytesитерируемое, а не одно целое число:

>>> bytes([3])
b'\x03'

В документах заявить об этом , а также для строке документации bytes:

 >>> help(bytes)
 ...
 bytes(int) -> bytes object of size given by the parameter initialized with null bytes
Тим Питцкер
источник
25
Помните, что вышесказанное работает только с Python 3. В Python 2 bytesэто просто псевдоним для str, что означает bytes([3])дает вам '[3]'.
Botchniaque
9
Обратите внимание, что в Python 3 он bytes([n])работает только для int n от 0 до 255. Для всего остального он поднимается ValueError.
Acumenus
8
@ABB: Не удивительно, поскольку байт может хранить значения только от 0 до 255.
Тим Пицкер
7
Следует также отметить, что bytes([3])он по-прежнему отличается от того, что хотел OP, а именно - значение байта, используемое для кодирования цифры «3» в ASCII, т.е. bytes([51])то есть b'3'нет b'\x03'.
17
2
bytes(500)создает строку байтов w / len == 500. Она не создает строку байтов, которая кодирует целое число 500. И я согласен, что bytes([500])это не может работать, поэтому это тоже неправильный ответ. Вероятно, правильный ответ int.to_bytes()для версий> = 3.1.
weberc2
200

С Python 3.2 вы можете сделать

>>> (1024).to_bytes(2, byteorder='big')
b'\x04\x00'

https://docs.python.org/3/library/stdtypes.html#int.to_bytes

def int_to_bytes(x: int) -> bytes:
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')

def int_from_bytes(xbytes: bytes) -> int:
    return int.from_bytes(xbytes, 'big')

Соответственно x == int_from_bytes(int_to_bytes(x)). Обратите внимание, что эта кодировка работает только для беззнаковых (неотрицательных) целых чисел.

brunsgaard
источник
4
Хотя этот ответ хорош, он работает только для целых чисел без знака (неотрицательных). Я приспособил это написать ответ, который также работает для целых чисел со знаком.
Acumenus
1
Это не помогает получить b"3"от 3, как спрашивает вопрос. (Это даст b"\x03".)
gsnedders
41

Вы можете использовать пакет struct :

In [11]: struct.pack(">I", 1)
Out[11]: '\x00\x00\x00\x01'

«>» - это порядок байтов (big-endian), а «I» - символ формата . Так что вы можете быть конкретны, если хотите сделать что-то еще:

In [12]: struct.pack("<H", 1)
Out[12]: '\x01\x00'

In [13]: struct.pack("B", 1)
Out[13]: '\x01'

Это работает одинаково как на python 2, так и на python 3 .

Примечание: обратная операция (bytes to int) может быть выполнена с помощью unpack .

Энди Хейден
источник
2
@AndyHayden Чтобы уточнить, так как структура имеет стандартный размер , независимо от входных данных, I, Hи Bработают до 2**k - 1где к 32, 16 и 8 соответственно. Для больших входов они повышают struct.error.
Acumenus
Предположительно, голосовали против, поскольку не отвечают на вопрос: ОП хочет знать, как генерировать b'3\r\n', то есть байтовую строку, содержащую символ ASCII "3", а не символ ASCII "\ x03"
Дейв Джонс
1
@DaveJones Что заставляет вас думать, что именно этого хочет ОП? В принятом ответе возвращается \x03, и решение , если вы просто хотите b'3'тривиальны. Причина, на которую ссылается АББ, гораздо более вероятна ... или, по крайней мере, понятна.
Энди Хейден
@DaveJones Кроме того, причина, по которой я добавил этот ответ, заключалась в том, что Google приводит вас сюда при поиске, чтобы сделать именно это. Вот почему это здесь.
Энди Хейден
5
Мало того, что эту работу такой же , в 2 и 3, но это быстрее , чем как bytes([x])и (x).to_bytes()методы в Python 3.5. Это было неожиданно.
Марк Рэнсом
25

В Python 3.5+ введена% -интерполяция (-стилевое printfформатирование) для байтов :

>>> b'%d\r\n' % 3
b'3\r\n'

См. PEP 0461 - Добавление% форматирования в байты и байтовый массив .

На более ранних версиях вы могли использовать strи .encode('ascii')результат:

>>> s = '%d\r\n' % 3
>>> s.encode('ascii')
b'3\r\n'

Примечание: это отличается от того, что int.to_bytesпроизводит :

>>> n = 3
>>> n.to_bytes((n.bit_length() + 7) // 8, 'big') or b'\0'
b'\x03'
>>> b'3' == b'\x33' != '\x03'
True
JFS
источник
11

Документация гласит:

bytes(int) -> bytes object of size given by the parameter
              initialized with null bytes

Последовательность:

b'3\r\n'

Это символ '3' (десятичный 51), символ '\ r' (13) и '\ n' (10).

Поэтому способ будет относиться к нему как таковой, например:

>>> bytes([51, 13, 10])
b'3\r\n'

>>> bytes('3', 'utf8') + b'\r\n'
b'3\r\n'

>>> n = 3
>>> bytes(str(n), 'ascii') + b'\r\n'
b'3\r\n'

Протестировано на IPython 1.1.0 и Python 3.2.3

Schcriher
источник
1
Я закончил тем, что делал bytes(str(n), 'ascii') + b'\r\n'или str(n).encode('ascii') + b'\r\n'. Спасибо! :)
astrojuanlu
1
@ Juanlu001, также "{}\r\n".format(n).encode()я не думаю, что использование кодировки utf8 по умолчанию приносит вред
John La Rooy
6

ASCIIfication 3 "\x33"не является "\x03"!

Это то, что делает Python, str(3)но это будет совершенно неправильно для байтов, поскольку они должны рассматриваться как массивы двоичных данных и не должны использоваться как строки.

Самый простой способ достичь желаемого - bytes((3,))лучше, чем bytes([3])инициализация списка, это намного дороже, поэтому никогда не используйте списки, когда вы можете использовать кортежи. Вы можете конвертировать большие целые числа, используяint.to_bytes(3, "little") .

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

Bachsau
источник
1
У этого ответа есть несколько проблем: (а) escape-нотация b'3'есть b'\x33', а не b'\x32'. (б) (3)не кортеж - вы должны добавить запятую. (c) Сценарий инициализации последовательности с нулями не применяется к bytesобъектам, поскольку они неизменны ( bytearrayхотя для s это имеет смысл ).
17
Спасибо за ваш комментарий. Я исправил эти две очевидные ошибки. В случае bytesи bytearray, я думаю, это в основном вопрос последовательности. Но это также полезно, если вы хотите поместить некоторые нули в буфер или файл, и в этом случае он используется только как источник данных.
Бахсау,
5

int(включая Python2 long) можно преобразовать в bytesследующую функцию:

import codecs

def int2bytes(i):
    hex_value = '{0:x}'.format(i)
    # make length of hex_value a multiple of two
    hex_value = '0' * (len(hex_value) % 2) + hex_value
    return codecs.decode(hex_value, 'hex_codec')

Обратное преобразование может быть сделано другим:

import codecs
import six  # should be installed via 'pip install six'

long = six.integer_types[-1]

def bytes2int(b):
    return long(codecs.encode(b, 'hex_codec'), 16)

Обе функции работают как на Python2, так и на Python3.

renskiy
источник
'hex_value ='% x '% i' не будет работать в Python 3.4. Вы получаете TypeError, поэтому вам придется использовать hex () вместо этого.
BJMC
@bjmc заменяется на str.format. Это должно работать на Python 2.6+.
Ренский
Спасибо, @renskiy. Вы можете использовать «hex_codec» вместо «гекса» , потому что кажется , как «шестнадцатеричный» псевдоним не доступен на всех Python 3 релизы см stackoverflow.com/a/12917604/845210
bjmc
@bjmc исправлено. Спасибо
Ренский
Это терпит неудачу на отрицательных целых числах на питоне 3.6
Berserker
4

Мне было любопытно о производительности различных методов для одного int в диапазоне [0, 255] , поэтому я решил провести некоторые временные тесты.

На основании приведенных ниже тайминги, а также от общей тенденции я наблюдал от попыток много различных значений и конфигураций, struct.packкажется, самым быстрым, а затем int.to_bytes, bytesи с str.encode(неудивительно) является самым медленным. Обратите внимание, что результаты показывают несколько больше вариаций, чем представлено, int.to_bytesи bytesиногда переключаются ранжирование скорости во время тестирования, ноstruct.pack , очевидно, является самым быстрым.

Результаты в CPython 3.7 для Windows:

Testing with 63:
bytes_: 100000 loops, best of 5: 3.3 usec per loop
to_bytes: 100000 loops, best of 5: 2.72 usec per loop
struct_pack: 100000 loops, best of 5: 2.32 usec per loop
chr_encode: 50000 loops, best of 5: 3.66 usec per loop

Тестовый модуль (названный int_to_byte.py):

"""Functions for converting a single int to a bytes object with that int's value."""

import random
import shlex
import struct
import timeit

def bytes_(i):
    """From Tim Pietzcker's answer:
    https://stackoverflow.com/a/21017834/8117067
    """
    return bytes([i])

def to_bytes(i):
    """From brunsgaard's answer:
    https://stackoverflow.com/a/30375198/8117067
    """
    return i.to_bytes(1, byteorder='big')

def struct_pack(i):
    """From Andy Hayden's answer:
    https://stackoverflow.com/a/26920966/8117067
    """
    return struct.pack('B', i)

# Originally, jfs's answer was considered for testing,
# but the result is not identical to the other methods
# https://stackoverflow.com/a/31761722/8117067

def chr_encode(i):
    """Another method, from Quuxplusone's answer here:
    https://codereview.stackexchange.com/a/210789/140921

    Similar to g10guang's answer:
    https://stackoverflow.com/a/51558790/8117067
    """
    return chr(i).encode('latin1')

converters = [bytes_, to_bytes, struct_pack, chr_encode]

def one_byte_equality_test():
    """Test that results are identical for ints in the range [0, 255]."""
    for i in range(256):
        results = [c(i) for c in converters]
        # Test that all results are equal
        start = results[0]
        if any(start != b for b in results):
            raise ValueError(results)

def timing_tests(value=None):
    """Test each of the functions with a random int."""
    if value is None:
        # random.randint takes more time than int to byte conversion
        # so it can't be a part of the timeit call
        value = random.randint(0, 255)
    print(f'Testing with {value}:')
    for c in converters:
        print(f'{c.__name__}: ', end='')
        # Uses technique borrowed from https://stackoverflow.com/q/19062202/8117067
        timeit.main(args=shlex.split(
            f"-s 'from int_to_byte import {c.__name__}; value = {value}' " +
            f"'{c.__name__}(value)'"
        ))
Грэхем
источник
1
@ABB Как уже упоминалось в моем первом предложении, я измеряю это только для одного целого в диапазоне [0, 255]. Я предполагаю, что под «неправильным индикатором» вы подразумеваете, что мои измерения не были достаточно общими, чтобы соответствовать большинству ситуаций? Или моя методология измерения была плохой? Если последнее, мне было бы интересно услышать, что вы хотите сказать, но если первое, я никогда не утверждал, что мои измерения были общими для всех вариантов использования. В моей (возможно, нишевой) ситуации я имею дело только с целыми рядами в диапазоне [0, 255], и именно к этой аудитории я собирался обратиться с этим ответом. Был ли мой ответ неясным? Я могу отредактировать это для ясности ...
Грэм
1
Как насчет техники простого индексирования предварительно вычисленного кодирования для диапазона? Предварительное вычисление не будет зависеть от времени, будет только индексация.
Acumenus
@ABB Это хорошая идея. Похоже, это будет быстрее, чем все остальное. Я сделаю немного времени и добавлю его к этому ответу, когда у меня будет время.
Грэм
3
Если вы действительно хотите рассчитать время байтов из итерируемой вещи, вы должны использовать ее bytes((i,))вместо того, bytes([i])потому что список более сложный, использует больше памяти и занимает много времени для инициализации. В этом случае даром.
Бахсау
4

Хотя предыдущий ответ brunsgaard является эффективной кодировкой, он работает только для целых чисел без знака. Это основано на том, что оно работает для целых чисел как со знаком, так и без знака.

def int_to_bytes(i: int, *, signed: bool = False) -> bytes:
    length = ((i + ((i * signed) < 0)).bit_length() + 7 + signed) // 8
    return i.to_bytes(length, byteorder='big', signed=signed)

def bytes_to_int(b: bytes, *, signed: bool = False) -> int:
    return int.from_bytes(b, byteorder='big', signed=signed)

# Test unsigned:
for i in range(1025):
    assert i == bytes_to_int(int_to_bytes(i))

# Test signed:
for i in range(-1024, 1025):
    assert i == bytes_to_int(int_to_bytes(i, signed=True), signed=True)

Для кодера (i + ((i * signed) < 0)).bit_length()используется, а не только i.bit_length()потому, что последний приводит к неэффективному кодированию -128, -32768 и т. Д.


Кредит: CervEd для устранения незначительной неэффективности.

Акаменус
источник
int_to_bytes(-128, signed=True) == (-128).to_bytes(1, byteorder="big", signed=True)этоFalse
CervEd
Вы не используете длину 2, вы вычисляете битовую длину целого числа со знаком, добавляя 7, а затем 1, если это целое число со знаком. Наконец, вы конвертируете это в длину в байтах. Это дает неожиданные результаты -128, и -32768т.д.
CervEd
Вот как это исправить(i+(signed*i<0)).bit_length()
CervEd
3

Поведение происходит из-за того, что в Python до версии 3 bytesбыл просто псевдоним для str. В Python3.x bytesесть неизменяемая версия bytearray- совершенно новый тип, не обратно совместимый.

капризный
источник
3

Из байтов документов :

Соответственно, аргументы конструктора интерпретируются как для bytearray ().

Затем из документа bytearray :

Необязательный параметр source можно использовать для инициализации массива несколькими различными способами:

  • Если это целое число, массив будет иметь такой размер и будет инициализирован нулевыми байтами.

Обратите внимание, что отличается от поведения 2.x (где x> = 6), где bytesпросто str:

>>> bytes is str
True

ОПТОСОЗ 3112 :

2.6 str отличается от типа 3.0 байтов по-разному; в частности, конструктор совершенно другой.

ALKO
источник
0

Некоторые ответы не работают с большими числами.

Преобразовать целое число в шестнадцатеричное представление, затем преобразовать его в байты:

def int_to_bytes(number):
    hrepr = hex(number).replace('0x', '')
    if len(hrepr) % 2 == 1:
        hrepr = '0' + hrepr
    return bytes.fromhex(hrepr)

Результат:

>>> int_to_bytes(2**256 - 1)
b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
Макс Малыш
источник
1
«Все остальные методы не работают с большими числами». Это не так, int.to_bytesработает с любым целым числом.
juanpa.arrivillaga
@ juanpa.arrivillaga да, мой плохой. Я отредактировал свой ответ.
Макс Малыш
-1

Если вопрос заключается в том, как преобразовать само целое число (не его строковый эквивалент) в байты, я думаю, надежный ответ:

>>> i = 5
>>> i.to_bytes(2, 'big')
b'\x00\x05'
>>> int.from_bytes(i.to_bytes(2, 'big'), byteorder='big')
5

Больше информации об этих методах здесь:

  1. https://docs.python.org/3.8/library/stdtypes.html#int.to_bytes
  2. https://docs.python.org/3.8/library/stdtypes.html#int.from_bytes
Нилашиш С
источник
1
Как это отличается от ответа brunsgaard, опубликованного 5 лет назад и в настоящее время получившего наибольшее количество голосов?
Артур Такка