2.6. Szöveg vs. bájtok

A Pythonban két szekvenciatípus van a nyers karakteradatokhoz:

  • str – Unicode kódpontok sorozata. Minden ember által olvasható szöveghez használatos: fájlútvonalak, naplóüzenetek, JSON-adatok.

  • bytes – 0 – 255 tartományba eső egész számok sorozata. Nyers bináris adatokhoz használatos: UART-keretek, képpufferek, hálózati csomagok, regiszterértékek.

Explicit konverzió nélkül nem keverhetők. Ha egy str objektumot adsz át egy hardveres write metódusnak, TypeError kivétel keletkezik, és a fordítottja sem megengedett.

Egy Unicode kódpontokból álló str a bal oldalon, egy nyers oktettekből álló bytes szekvencia a jobb oldalon, közöttük encode és decode nyilakkal.

Egy str Unicode karaktereket tárol; egy bytes nyers oktetteket tárol. A kettő közötti átmenet a kódolás (str → bytes) és a dekódolás (bytes → str).

2.6.1. bytes literálok

A bytes literál egy karakterlánchoz hasonló literál, b előtaggal:

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

Egy bytes literálon belül közvetlenül csak ASCII karakterek engedélyezettek; a nem ASCII értékeket \xHH hexadecimális escape-ként kell írni.

2.6.2. Kódolás és dekódolás

  • A str.encode() egy karakterláncot bájtokká alakít egy megnevezett kódolás (alapértelmezetten "utf-8") használatával.

  • A bytes.decode() ennek az ellentéte.

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

Az UTF-8 az alapértelmezett, és a helyes választás bármihez, ami nem ASCII karaktereket tartalmazhat. Az "ascii" kódolást csak akkor használd, ha az adat garantáltan sima ASCII; így egy elkóborolt nem ASCII bájt UnicodeError kivételt vált ki, ahelyett, hogy csendben átmenne.

2.6.3. Indexelés és szeletelés

Egy bytes érték indexeléskor egész számok sorozataként viselkedik, nem pedig egybájtos karakterláncok sorozataként:

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

Gyakori hiba a data[0] == "a" összehasonlítása, és a meglepődés azon, hogy az eredmény False – a data[0] egy egész szám, nem pedig egykarakteres karakterlánc, így a két érték soha nem egyezhet.

2.6.4. ord és chr – híd a karakterek és az egész számok között

Mivel egy bytes indexelése egész számot ad vissza, de a program többi része valószínűleg karakterekben gondolkodik, a Python két beépített függvényt biztosít a köztük való váltáshoz:

  • ord() – egy egykarakteres karakterláncot vesz, és visszaadja annak egész kódpontját.

  • chr() – ennek az ellentéte: egy egész számot kapva visszaadja az adott kódponthoz tartozó egykarakteres karakterláncot.

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

ASCII karakterek esetén a kódpont megegyezik a bájtértékkel, így az ord("a") és a b"a"[0] egyaránt 97-et ad. Ettől a bájt-összehasonlítások annak a karakternek a szempontjából olvashatók, amely valójában érdekel:

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

A chr() pedig hasznos naplózáshoz vagy hibakereséshez, amikor egy bájt nyomtatható alakját szeretnéd látni:

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

Nem ASCII karakterek esetén a ord() a Unicode kódpontot adja vissza, ami nem azonos a kódolt forma egyetlen bájtjával sem; a bájtábrázolás a kódolástól függ.

2.6.5. bytearray a módosítható pufferekhez

A bytes megváltoztathatatlan – minden „módosítás” egy új objektumot ad vissza, és az eredetit érintetlenül hagyja. Olyan adatokhoz, amelyeket módosítani, hozzáfűzni vagy darabonként feltölteni szeretnél, használd a bytearray típust. Ugyanazt a tartalmat tárolja, mint a bytes, de támogatja a helyben történő módosítá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. bytearray létrehozása

A bytearray konstruktor többféle bemenetet fogad el:

  • bytearray(8) – egy 8 nullbájtból álló puffer.

  • bytearray(b"hello") – egy bytes érték módosítható másolata.

  • bytearray("hello", "utf-8") – egy bytearray egy karakterláncból, az adott kódolás használatával.

  • bytearray([72, 73, 74]) – egy bytearray a 0 – 255 tartományba eső egész számok sorozatából (itt 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 módosítása

Az indexelt és szeletelt értékadás pontosan úgy működik, mint egy list esetén:

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

Az egyes bájtoknak 0 – 255 tartományba eső egész számoknak kell lenniük; bármely más típus hozzárendelése TypeError vagy ValueError kivételt vált ki.

A szeletes értékadás megváltoztathatja a puffer hosszát. Egy szelet hosszabb értékkel való helyettesítése növeli a bytearray méretét; rövidebb értékkel való helyettesítése csökkenti azt. A b"" értékkel való helyettesítés teljesen törli a szeletet:

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

A bytearray.append() és bytearray.extend() metódusok bájtokat adnak hozzá a végéhez anélkül, hogy minden alkalommal újrafoglalnák a teljes puffert:

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

2.6.5.3. Olvasás egy bytearray-ből

Az indexelés, a szeletelés, az iterálás és a bytes vizsgálati metódusai (bytes.startswith(), bytes.find(), bytes.strip() stb.) mind ugyanúgy működnek, mint egy bytes érték esetén. Az indexelés egy egész számot ad vissza; a szeletelés egy újabb bytearray-t ad vissza:

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

2.6.5.4. Konverzió a bytes és a bytearray között

A bytes és a bytearray a konstruktoraikkal alakíthatók át egymásba. Ezt akkor használd, amikor egy API kifejezetten az egyik formát igényli:

>>> 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 a másolatmentes szeleteléshez

Egy bytes vagy bytearray szeletelése normál esetben a bájtokat egy új pufferbe másolja. A memoryview ugyanazokat a bájtokat teszi elérhetővé másolás nélkül:

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

Egy bytearray feletti nézet írható is – a nézet módosítása módosítja az alapul szolgáló puffert:

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

Akkor nyúlj a memoryview típushoz, amikor egy szelet másolása pazarló lenne – jellemzően amikor ugyanazt a nagy puffert adogatják körbe vagy darabonként dolgozzák fel. A kis bájtokon végzett mindennapi, karakterlánc-stílusú munkához a sima szeletelés megfelelő.