Лучший способ преобразовать строку в байты в 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")
Справка (прокрутите вверх, чтобы увидеть стандартные кодировки):
Вы можете просто преобразовать строку в байты, используя:
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
Как насчет пути 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 .