2.6. Текст і байти¶
Python має два типи послідовностей для символьних даних:
str– послідовність кодових точок Unicode. Використовується для всього людиночитабельного тексту: шляхів до файлів, повідомлень журналу, JSON-даних.bytes– послідовність цілих чисел у діапазоні 0 – 255. Використовується для сирих бінарних даних: кадрів UART, буферів зображень, мережевих пакетів, значень регістрів.
Їх не можна змішувати без явного перетворення. Передача str до апаратного методу write викидає TypeError, а зворотне також відхиляється.
str зберігає символи Unicode; bytes зберігає сирі октети. Перехід між ними — це кодування (str → bytes) і декодування (bytes → str).¶
2.6.1. Літерали bytes¶
Літерал bytes — це рядкоподібний літерал із префіксом b:
header = b"OMV"
crlf = b"\r\n"
payload = b"\x01\x02\x03"
Усередині літерала bytes допускаються лише символи ASCII; значення не-ASCII мають бути записані як шістнадцяткові послідовності \xHH.
2.6.2. Кодування та декодування¶
str.encode()перетворює рядок на байти, використовуючи вказане кодування (за замовчуванням"utf-8").bytes.decode()виконує зворотню операцію.
>>> "hello".encode()
b'hello'
>>> "héllo".encode()
b'h\xc3\xa9llo' # é is two bytes in UTF-8
>>> b"hello".decode()
'hello'
UTF-8 є типовим і правильним вибором для всього, що може містити символи не-ASCII. Використовуйте "ascii" лише тоді, коли дані гарантовано є чистим ASCII; тоді несподіваний байт не-ASCII викине UnicodeError замість того, щоб мовчки пройти.
2.6.3. Індексація та зрізи¶
Значення bytes поводиться як послідовність цілих чисел при індексації, а не як послідовність однобайтових рядків:
>>> data = b"abc"
>>> data[0]
97 # the int 97, not 'a'
>>> data[0:1]
b'a' # slicing returns bytes
Поширена помилка — порівняння data[0] == "a" і подив від того, що результат False – data[0] є цілим числом, а не однобуквеним рядком, тому ці два значення ніколи не збігаються.
2.6.4. ord і chr – перехід між символами та цілими числами¶
Оскільки індексація bytes повертає ціле число, а решта програми, швидше за все, оперує символами, Python надає два вбудовані засоби для переходу між ними:
ord()– приймає однобуквений рядок і повертає його цілочисельну кодову точку.chr()– зворотна операція: за цілим числом повертає однобуквений рядок для цієї кодової точки.
>>> ord("a")
97
>>> chr(97)
'a'
>>> ord("A"), chr(0x41)
(65, 'A')
Для символів ASCII кодова точка збігається зі значенням байта, тому ord("a") і b"a"[0] обидва дають 97. Це дозволяє читати порівняння байтів у термінах потрібного символу:
>>> data = b"abc"
>>> data[0] == ord("a") # instead of the magic number 97
True
chr() зручний для журналювання або налагодження, коли потрібно побачити друкований вигляд байта:
>>> chr(data[0])
'a'
Для символів не-ASCII ord() повертає кодову точку Unicode, яка не збігається з жодним окремим байтом у закодованому вигляді; байтове представлення залежить від кодування.
2.6.5. bytearray для змінюваних буферів¶
bytes є незмінним — кожна «зміна» повертає новий об’єкт і не зачіпає оригінал. Для даних, які ви плануєте змінювати, доповнювати або заповнювати частинами, використовуйте bytearray. Він містить той самий вміст, що й bytes, але підтримує зміну на місці:
>>> s = b"hello"
>>> s[0] = ord("H")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
>>> s = bytearray(b"hello")
>>> s[0] = ord("H")
>>> s
bytearray(b'Hello')
2.6.5.1. Створення bytearray¶
Конструктор bytearray приймає кілька типів вхідних даних:
bytearray(8)– буфер із 8 нульових байтів.bytearray(b"hello")– змінна копія значення bytes.bytearray("hello", "utf-8")– bytearray із рядка з використанням вказаного кодування.bytearray([72, 73, 74])– bytearray із послідовності цілих чисел у діапазоні 0 – 255 (тутb"HIJ").
>>> bytearray(4)
bytearray(b'\x00\x00\x00\x00')
>>> bytearray(b"abc")
bytearray(b'abc')
>>> bytearray("café", "utf-8")
bytearray(b'caf\xc3\xa9')
2.6.5.2. Зміна bytearray¶
Індексоване та зрізне присвоєння працюють так само, як у list:
>>> buf = bytearray(8) # 8 zero bytes
>>> buf[0] = 0xFF # one byte at a time
>>> buf[1:4] = b"ABC" # replace a slice
>>> buf
bytearray(b'\xffABC\x00\x00\x00\x00')
Окремі байти мають бути цілими числами у діапазоні 0 – 255; призначення будь-якого іншого типу викидає TypeError або ValueError.
Присвоєння зрізу може змінити довжину буфера. Заміна зрізу довшим значенням збільшує bytearray; заміна коротшим зменшує. Заміна на b"" повністю видаляє зріз:
>>> buf = bytearray(b"abcdef")
>>> buf[1:3] = b"XYZ" # 2 bytes replaced with 3
>>> buf
bytearray(b'aXYZdef')
>>> buf[1:4] = b"" # delete the inserted run
>>> buf
bytearray(b'adef')
Методи bytearray.append() і bytearray.extend() додають байти в кінець без перерозподілу всього буфера щоразу:
>>> buf = bytearray()
>>> buf.append(0x01)
>>> buf.extend(b"abc")
>>> buf
bytearray(b'\x01abc')
2.6.5.3. Читання з bytearray¶
Індексація, зрізи, ітерація та методи перевірки bytes (bytes.startswith(), bytes.find(), bytes.strip() тощо) працюють так само, як і для bytes. Індексація повертає ціле число; зріз повертає інший bytearray:
>>> buf = bytearray(b"OpenMV")
>>> buf[0]
79
>>> buf[0:4]
bytearray(b'Open')
>>> buf.startswith(b"Open")
True
2.6.5.4. Перетворення між bytes і bytearray¶
bytes і bytearray перетворюються одне на одне за допомогою конструкторів. Використовуйте це, коли API вимагає конкретно одну з форм:
>>> ba = bytearray(b"hello")
>>> snapshot = bytes(ba) # immutable copy
>>> ba[0] = ord("H")
>>> ba, snapshot
(bytearray(b'Hello'), b'hello')
2.6.5.5. memoryview для зрізів без копіювання¶
Зріз bytes або bytearray зазвичай копіює байти в новий буфер. memoryview надає доступ до тих самих байтів без копіювання:
>>> buf = bytearray(b"OpenMV Cam")
>>> view = memoryview(buf)
>>> view[0:6] # shares storage with buf
<memoryview ...>
>>> bytes(view[0:6]) # materialise as bytes when needed
b'OpenMV'
Представлення поверх bytearray також є записуваним — зміна представлення змінює базовий буфер:
>>> view[0] = ord("o")
>>> buf
bytearray(b'openMV Cam')
Вдавайтеся до memoryview, коли копіювання зрізу було б марнотратством — зазвичай, коли один великий буфер передається або обробляється по частинах. Для повсякденної роботи зі рядками на малих bytes звичайні зрізи цілком підходять.