Лучший способ преобразовать строку в байты в Python 3?

Похоже, есть два разных способа преобразования строки в байты, как видно из ответов на TypeError: 'str' не поддерживает интерфейс буфера

Какой из этих методов будет лучше или больше Pythonic? Или это просто вопрос личных предпочтений?

b = bytes(mystring, 'utf-8')

b = mystring.encode('utf-8')

7 ответов

Решение

Если вы посмотрите на документы для bytes, это указывает на bytearray:

bytearray ([источник [, кодировка [, ошибки]]])

Вернуть новый массив байтов. Тип bytearray - это изменяемая последовательность целых чисел в диапазоне 0 <= x < 256. Он имеет большинство обычных методов изменяемых последовательностей, описанных в Mutable Sequence Types, а также большинство методов, которые имеет тип bytes, см. Байты и Методы массива байтов.

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

Если это строка, вы также должны указать параметры кодирования (и, возможно, ошибки);Затем bytearray() преобразует строку в байты, используя str.encode().

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

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

Если это итерация, это должна быть итерация целых чисел в диапазоне 0 <= x < 256, которые используются в качестве начального содержимого массива.

Без аргумента создается массив размером 0.

Так bytes может сделать гораздо больше, чем просто кодировать строку. Это Pythonic, что позволит вам вызывать конструктор с любым типом исходного параметра, который имеет смысл.

Для кодирования строки, я думаю, что some_string.encode(encoding) является более Pythonic, чем использование конструктора, потому что это наиболее самодокументируемый - "взять эту строку и кодировать ее с этой кодировкой" яснее, чем bytes(some_string, encoding) - нет явного глагола при использовании конструктора.

Изменить: я проверил источник Python. Если вы передаете строку Unicode в bytes используя CPython, он вызывает PyUnicode_AsEncodedString, который является реализацией encode; так что вы просто пропускаете уровень косвенности, если вы звоните encode сам.

Также см. Комментарий Сердалиса - unicode_string.encode(encoding) также более Pythonic, потому что его обратное byte_string.decode(encoding) и симметрия это хорошо.

Это проще, чем кажется

my_str = "hello world"
my_str_as_bytes = str.encode(my_str)
type(my_str_as_bytes) # ensure it is byte representation
my_decoded_str = my_str_as_bytes.decode()
type(my_decoded_str) # ensure it is string representation

Абсолютно лучшим способом является не 2, а 3-й. Первый параметр для encode по умолчанию 'utf-8' начиная с Python 3.0. Таким образом, лучший способ

b = mystring.encode()

Это также будет быстрее, потому что аргумент по умолчанию не в строке "utf-8" в коде C, но NULL, что гораздо быстрее проверить!

Вот некоторые моменты:

In [1]: %timeit -r 10 'abc'.encode('utf-8')
The slowest run took 38.07 times longer than the fastest. 
This could mean that an intermediate result is being cached.
10000000 loops, best of 10: 183 ns per loop

In [2]: %timeit -r 10 'abc'.encode()
The slowest run took 27.34 times longer than the fastest. 
This could mean that an intermediate result is being cached.
10000000 loops, best of 10: 137 ns per loop

Несмотря на предупреждение, времена были очень стабильными после повторных прогонов - отклонение составляло всего ~2%.


С помощью encode() без аргумента не совместим с Python 2, так как в Python 2 кодировка символов по умолчанию - ASCII.

>>> 'äöä'.encode()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)

Ответ на несколько иную проблему:

У вас есть последовательность необработанного юникода, которая была сохранена в переменной str:

      s_str: str = "\x00\x01\x00\xc0\x01\x00\x00\x00\x04"

Вам необходимо получить байтовый литерал этого юникода (для struct.unpack () и т. Д.)

      s_bytes: bytes = b'\x00\x01\x00\xc0\x01\x00\x00\x00\x04'

Решение:

      s_new: bytes = bytes(s, encoding="raw_unicode_escape")

Справка (прокрутите вверх, чтобы увидеть стандартные кодировки):

Специальные кодировки Python

Вы можете просто преобразовать строку в байты, используя:

a_string.encode()

и вы можете просто конвертировать байты в строку, используя:

some_bytes.decode()

bytes.decode а также str.encode иметь encoding='utf-8' как значение по умолчанию.

Следующие функции (взятые из Effective Python) могут быть полезны для преобразования str в bytes а также bytes в str:

def to_bytes(bytes_or_str):
    if isinstance(bytes_or_str, str):
        value = bytes_or_str.encode() # uses 'utf-8' for encoding
    else:
        value = bytes_or_str
    return value # Instance of bytes


def to_str(bytes_or_str):
    if isinstance(bytes_or_str, bytes):
        value = bytes_or_str.decode() # uses 'utf-8' for encoding
    else:
        value = bytes_or_str
    return value # Instance of str
so_string = 'stackru'
so_bytes = so_string.encode( )

Как насчет пути Python 3 'memoryview' .

Memoryview — это своего рода мешанина модулей byte/bytearray и struct с рядом преимуществ.

  • Не ограничивается только текстом и байтами, также обрабатывает 16- и 32-битные слова.
  • Справляется с порядком байтов
  • Обеспечивает интерфейс с очень низкими издержками для связанных функций и данных C/C++.

Самый простой пример для массива байтов:

      memoryview(b"some bytes").tolist()

[115, 111, 109, 101, 32, 98, 121, 116, 101, 115]

Или для строки юникода (которая преобразуется в массив байтов)

      memoryview(bytes("\u0075\u006e\u0069\u0063\u006f\u0064\u0065\u0020", "UTF-16")).tolist()

[255, 254, 117, 0, 110, 0, 105, 0, 99, 0, 111, 0, 100, 0, 101, 0, 32, 0]

#Another way to do the same
memoryview("\u0075\u006e\u0069\u0063\u006f\u0064\u0065\u0020".encode("UTF-16")).tolist()

[255, 254, 117, 0, 110, 0, 105, 0, 99, 0, 111, 0, 100, 0, 101, 0, 32, 0]

Возможно, вам нужны слова, а не байты?

      memoryview(bytes("\u0075\u006e\u0069\u0063\u006f\u0064\u0065\u0020", "UTF-16")).cast("H").tolist()

[65279, 117, 110, 105, 99, 111, 100, 101, 32]

memoryview(b"some  more  data").cast("L").tolist()

[1701670771, 1869422624, 538994034, 1635017060]

Слово предостережения. Будьте осторожны с несколькими интерпретациями порядка байтов с данными из более чем одного байта:

      txt = "\u0075\u006e\u0069\u0063\u006f\u0064\u0065\u0020"
for order in ("", "BE", "LE"):
    mv = memoryview(bytes(txt, f"UTF-16{order}"))
    print(mv.cast("H").tolist())

[65279, 117, 110, 105, 99, 111, 100, 101, 32]
[29952, 28160, 26880, 25344, 28416, 25600, 25856, 8192]
[117, 110, 105, 99, 111, 100, 101, 32]

Не уверен, что это сделано намеренно или это ошибка, но это меня зацепило!!

В примере используется UTF-16, полный список кодеков см. в реестре кодеков в Python 3.10 .

Другие вопросы по тегам