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


177

Я намагався створити цей байт-об’єкт у 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

Це ще більше погано взаємодіє з дивацтвами, такими як байти (int), що повертають нулі зараз

і:

Для мене було б набагато зручніше, якби байти (int) повернули ASCII-позначення цього int; але, чесно кажучи, навіть помилка була б кращою, ніж така поведінка. (Якби я хотів, щоб така поведінка - якої я ніколи не була - я вважаю за краще це класний метод, викликаний як "bytes.zeroes (n)".)

Хтось може мені пояснити, звідки ця поведінка?


1
пов'язані з назвою:3 .to_bytes
jfs

2
З вашого питання незрозуміло, чи хочете ви ціле значення 3 або значення символу ASCII, що представляє число три (ціле значення 51). Перший - байти ([3]) == b '\ x03'. Останнє - байти ([ord ('3')]) == b'3 '.
florisla

Відповіді:


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

8
У Python 3 зауважте, що bytes([n])він працює лише для int n від 0 до 255. Для будь-якого іншого він підвищується ValueError.
Акумен

8
@ABB: Не дуже дивно, оскільки байт може зберігати значення лише між 0 і 255.
Тім Піткер

7
Слід також зазначити, що bytes([3])все ще відрізняється від того, чого хотів ОП, а саме - значення байту, яке використовується для кодування цифри "3" в ASCII, тобто. bytes([51]), що є b'3', ні b'\x03'.
lenz

2
bytes(500)створює bytestring w / len == 500. Він не створює байтінгрінг, що кодує ціле число 500. І я погоджуюся, що bytes([500])це не може працювати, і тому це неправильна відповідь. Можливо, правильна відповідь int.to_bytes()стосується версій> = 3.1.
weberc2

199

З 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)). Зауважте, що це кодування працює лише для непідписаних (негативних) цілих чисел.


4
Хоча ця відповідь хороша, вона працює лише для непідписаних (негативних) цілих чисел. Я адаптував це написати відповідь, яка також працює для підписаних цілих чисел.
Акумен

1
Це не допомагає отримати b"3"шлях 3, як задається питанням. (Це дасть b"\x03".)
gsnedders

40

Ви можете використовувати пакет 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 .

Примітка: обернену операцію (байти до int) можна виконати розпакуванням .


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'тривіальні. Причина, яку наводить ABB, набагато правдоподібніша ... або, принаймні, зрозуміла.
Енді Хайден

@DaveJones Крім того, причиною того, що я додав цю відповідь, було те, що Google приймає вас сюди, коли шукає саме це. Тож тому воно тут.
Енді Хайден

4
Це не тільки працює однаково в 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

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


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

Саме для цього і пітон str(3) але це буде абсолютно неправильно для байтів, оскільки їх слід вважати масивами бінарних даних і не зловживати ними як рядки.

Найпростіший спосіб досягти того, що ви хочете bytes((3,)), це краще, ніж bytes([3])через те, що ініціалізація списку набагато дорожча, тому ніколи не використовуйте списки, коли ви можете використовувати кортежі. Ви можете конвертувати великі цілі числа за допомогоюint.to_bytes(3, "little") .

Ініціалізація байтів із заданою довжиною має сенс і є найбільш корисною, оскільки їх часто використовують для створення певного типу буфера, для якого потрібно виділити деяку пам'ять заданого розміру. Я часто використовую це під час ініціалізації масивів або розширення якогось файла, записуючи до нього нулі.


1
Існує кілька проблем з цією відповіддю: (a) Позначення втечі b'3'є b'\x33', ні b'\x32'. (b) (3)не є кортежем - ви повинні додати кому. (c) Сценарій ініціалізації послідовності з нулями не поширюється на bytesоб'єкти, оскільки вони незмінні ( bytearrayхоча для s це має сенс ).
lenz

Дякуємо за ваш коментар Я виправив ці дві очевидні помилки. У випадку 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.


'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 виправлено. Дякую
renskiy

Це не вдається з від'ємними цілими числами на python 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 Як уже згадувалося в моєму першому реченні, я вимірюю це лише для однієї int у діапазоні [0, 255]. Я припускаю, що "неправильним показником" ви маєте на увазі, що мої вимірювання були недостатньо загальними, щоб відповідати більшості ситуацій? Або моя методика вимірювання була поганою? Якщо останнє, мені було б цікаво почути, що ви маєте сказати, але якщо перші, я ніколи не стверджував, що мої вимірювання були загальними для всіх випадків використання. Що стосується моєї (можливо, нішевої) ситуації, я маю справу лише з ints у діапазоні [0, 255], і це аудиторія, яку я мав намір звернутися до цієї відповіді. Чи була моя відповідь незрозумілою? Я можу відредагувати це для наочності ...
Грем

1
Що з технікою просто індексувати попередньо обчислене кодування для діапазону? Попередній обчислення не підлягало б тимчасовому, лише індексація.
Акумен

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

Потім, від dotearray docs :

Необов'язковий параметр джерела може використовуватися для ініціалізації масиву кількома різними способами:

  • Якщо це ціле число, масив матиме цей розмір і буде ініціалізований нульовими байтами.

Зауважимо, що відрізняється від поведінки 2.x (де x> = 6), де bytesпросто str:

>>> bytes is str
True

PEP 3112 :

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


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
Чим це відрізняється від відповіді Брансгаарда, опублікованої 5 років тому і на даний момент найвищою оцінкою?
Артур Такка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.