2.6. Text versus bajty

Python má dva sekvenční typy pro surová znaková data:

  • str – posloupnost kódových bodů Unicode. Používá se pro veškerý lidsky čitelný text: cesty k souborům, logovací zprávy, JSON data.

  • bytes – posloupnost celých čísel v rozsahu 0 – 255. Používá se pro surová binární data: UART rámce, obrazové buffery, síťové pakety, hodnoty registrů.

Nelze je míchat bez explicitní konverze. Předání str hardwarové metodě write vyvolá TypeError a obrácený případ je rovněž odmítnut.

Vlevo str s kódovými body Unicode a vpravo bytes posloupnost surových oktetů, mezi nimi šipky encode a decode.

str ukládá znaky Unicode; bytes ukládá surové oktety. Přechod mezi nimi je kódování (str → bytes) a dekódování (bytes → str).

2.6.1. literály bytes

Literál bytes je řetězci podobný literál s předponou b:

header  = b"OMV"
crlf    = b"\r\n"
payload = b"\x01\x02\x03"

Přímo uvnitř literálu bytes jsou povoleny pouze znaky ASCII; hodnoty mimo ASCII musí být zapsány jako hexadecimální escape sekvence \xHH.

2.6.2. Kódování a dekódování

  • str.encode() převádí řetězec na bytes pomocí pojmenovaného kódování (výchozí "utf-8").

  • bytes.decode() provádí opak.

>>> "hello".encode()
b'hello'
>>> "héllo".encode()
b'h\xc3\xa9llo'              # é is two bytes in UTF-8
>>> b"hello".decode()
'hello'

UTF-8 je výchozí a správná volba pro cokoli, co může obsahovat znaky mimo ASCII. Použijte "ascii" pouze tehdy, když je zaručeno, že data jsou čistě ASCII; tak zatoulaný bajt mimo ASCII vyvolá UnicodeError, místo aby tiše prošel.

2.6.3. Indexování a řezy

Hodnota bytes se při indexování chová jako posloupnost celých čísel, nikoli jako posloupnost jednobajtových řetězců:

>>> data = b"abc"
>>> data[0]
97                           # the int 97, not 'a'
>>> data[0:1]
b'a'                         # slicing returns bytes

Častou chybou je porovnávání data[0] == "a" a překvapení, že je výsledkem Falsedata[0] je celé číslo, nikoli jednoznakový řetězec, takže se tyto dvě hodnoty nikdy nemohou shodovat.

2.6.4. ord a chr – most mezi znaky a celými čísly

Protože indexování bytes vrací celé číslo, ale zbytek programu pravděpodobně uvažuje ve znacích, poskytuje Python dvě vestavěné funkce pro přechod mezi nimi:

  • ord() – přijímá jednoznakový řetězec a vrací jeho celočíselný kódový bod.

  • chr() – opak: pro dané celé číslo vrací jednoznakový řetězec odpovídající danému kódovému bodu.

>>> ord("a")
97
>>> chr(97)
'a'
>>> ord("A"), chr(0x41)
(65, 'A')

Pro znaky ASCII se kódový bod rovná hodnotě bajtu, takže ord("a") i b"a"[0] dávají 97. Díky tomu lze porovnávání bajtů číst ve smyslu znaku, který vás skutečně zajímá:

>>> data = b"abc"
>>> data[0] == ord("a")          # instead of the magic number 97
True

A chr() se hodí pro logování či ladění, když chcete vidět tisknutelnou podobu bajtu:

>>> chr(data[0])
'a'

Pro znaky mimo ASCII vrací ord() kódový bod Unicode, který není totožný s žádným jednotlivým bajtem v zakódované podobě; bajtová reprezentace závisí na kódování.

2.6.5. bytearray pro měnitelné buffery

bytes je neměnný – každá „úprava“ vrací nový objekt a původní nechává beze změny. Pro data, která hodláte upravovat, doplňovat nebo plnit po částech, použijte bytearray. Drží stejný obsah jako bytes, ale podporuje úpravy na místě:

>>> 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. Vytvoření bytearray

Konstruktor bytearray přijímá několik vstupů:

  • bytearray(8) – buffer o 8 nulových bajtech.

  • bytearray(b"hello") – měnitelná kopie hodnoty bytes.

  • bytearray("hello", "utf-8") – bytearray z řetězce za použití daného kódování.

  • bytearray([72, 73, 74]) – bytearray z posloupnosti celých čísel v rozsahu 0 – 255 (zde 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. Úprava bytearray

Přiřazení přes index a řez fungují stejně jako u 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')

Jednotlivé bajty musí být celá čísla v rozsahu 0 – 255; přiřazení jakéhokoli jiného typu vyvolá TypeError nebo ValueError.

Přiřazení řezu může změnit délku bufferu. Nahrazení řezu delší hodnotou bytearray zvětší; nahrazení kratší hodnotou jej zmenší. Nahrazení hodnotou b"" řez zcela smaže:

>>> 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')

Metody bytearray.append() a bytearray.extend() přidávají bajty na konec, aniž by pokaždé znovu alokovaly celý buffer:

>>> buf = bytearray()
>>> buf.append(0x01)
>>> buf.extend(b"abc")
>>> buf
bytearray(b'\x01abc')

2.6.5.3. Čtení z bytearray

Indexování, řezy, iterace a inspekční metody bytes (bytes.startswith(), bytes.find(), bytes.strip() atd.) fungují stejně jako u hodnoty bytes. Indexování vrací celé číslo; řez vrací další bytearray:

>>> buf = bytearray(b"OpenMV")
>>> buf[0]
79
>>> buf[0:4]
bytearray(b'Open')
>>> buf.startswith(b"Open")
True

2.6.5.4. Konverze mezi bytes a bytearray

bytes a bytearray se mezi sebou převádějí pomocí svých konstruktorů. Použijte to, když API vyžaduje konkrétně jednu z forem:

>>> 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 pro řezy bez kopírování

Řez bytes nebo bytearray obvykle zkopíruje bajty do nového bufferu. memoryview zpřístupňuje tytéž bajty bez kopírování:

>>> 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'

Pohled (view) nad bytearray je rovněž zapisovatelný – úprava pohledu upraví podkladový buffer:

>>> view[0] = ord("o")
>>> buf
bytearray(b'openMV Cam')

Sáhněte po memoryview, když by kopírování řezu bylo plýtváním – typicky když se tentýž velký buffer předává okolo nebo zpracovává po částech. Pro běžnou práci s malými bytes ve stylu řetězců je prostý řez v pořádku.