2.6. Text kontra bytes

Python har två sekvenstyper för rådata av tecken:

  • str – en sekvens av Unicode-kodpunkter. Används för all läsbar text: filsökvägar, loggmeddelanden, JSON-nyttolaster.

  • bytes – en sekvens av heltal i intervallet 0 – 255. Används för rå binärdata: UART-bildrutor, bildbuffertar, nätverkspaket, registervärden.

De kan inte blandas utan en explicit konvertering. Att skicka en str till en hårdvarumetod write ger upphov till TypeError, och det omvända avvisas också.

En str av Unicode-kodpunkter till vänster och en bytes- sekvens av rå oktetter till höger, med pilar för encode och decode mellan dem.

En str lagrar Unicode-tecken; en bytes lagrar rå oktetter. Att gå mellan dem är kodning (str → bytes) och avkodning (bytes → str).

2.6.1. bytes-literaler

En bytes-literal är en stränglik literal prefixad med b:

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

Endast ASCII-tecken tillåts direkt inuti en bytes-literal; värden som inte är ASCII måste skrivas som \xHH-hexescaper.

2.6.2. Kodning och avkodning

  • str.encode() konverterar en sträng till bytes med hjälp av en namngiven kodning (standard "utf-8").

  • bytes.decode() gör det omvända.

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

UTF-8 är standarden och rätt val för allt som kan innehålla tecken som inte är ASCII. Använd "ascii" endast när datan garanterat är ren ASCII; på så sätt ger en felaktig icke-ASCII-byte upphov till UnicodeError i stället för att tyst passera igenom.

2.6.3. Indexering och slicing

Ett bytes-värde beter sig som en sekvens av heltal vid indexering, inte som en sekvens av enbytsträngar:

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

Ett vanligt misstag är att jämföra data[0] == "a" och bli förvånad över att det är Falsedata[0] är ett heltal, inte en sträng på ett tecken, så de två värdena kan aldrig matcha.

2.6.4. ord och chr – en brygga mellan tecken och heltal

Eftersom indexering av en bytes returnerar ett heltal men resten av programmet sannolikt tänker i termer av tecken, tillhandahåller Python två inbyggda funktioner för att gå mellan dem:

  • ord() – tar en sträng på ett tecken och returnerar dess heltalskodpunkt.

  • chr() – det omvända: givet ett heltal returneras strängen på ett tecken för den kodpunkten.

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

För ASCII-tecken är kodpunkten lika med byte-värdet, så ord("a") och b"a"[0] ger båda 97. Det gör att byte-jämförelser kan läsas i termer av det tecken du faktiskt bryr dig om:

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

Och chr() är behändig för loggning eller felsökning när du vill se den utskrivbara formen av en byte:

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

För tecken som inte är ASCII returnerar ord() Unicode-kodpunkten, vilken inte är densamma som någon enskild byte i den kodade formen; byte-representationen beror på kodningen.

2.6.5. bytearray för föränderliga buffertar

bytes är oföränderlig – varje ”ändring” returnerar ett nytt objekt och lämnar originalet orört. För data som du avser att ändra, lägga till i eller fylla bit för bit, använd bytearray. Den håller samma innehåll som bytes men stöder förändring på plats:

>>> 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. Skapa en bytearray

Konstruktorn bytearray tar emot flera olika indata:

  • bytearray(8) – en buffert med 8 nollbytes.

  • bytearray(b"hello") – en föränderlig kopia av ett bytes-värde.

  • bytearray("hello", "utf-8") – en bytearray från en sträng, med den angivna kodningen.

  • bytearray([72, 73, 74]) – en bytearray från en sekvens av heltal i 0 – 255 (här 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. Modifiera en bytearray

Indexerad och slicad tilldelning fungerar precis som för en 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')

Enskilda bytes måste vara heltal i 0 – 255; att tilldela någon annan typ ger upphov till TypeError eller ValueError.

Slice-tilldelning kan ändra buffertens längd. Att ersätta en slice med ett längre värde gör att bytearrayen växer; att ersätta med ett kortare värde gör att den krymper. Att ersätta med b"" tar bort slicen helt:

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

Metoderna bytearray.append() och bytearray.extend() lägger till bytes i slutet utan att omallokera hela bufferten varje gång:

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

2.6.5.3. Läsa från en bytearray

Indexering, slicing, iteration och bytes-inspektionsmetoderna (bytes.startswith(), bytes.find(), bytes.strip() osv.) fungerar alla på samma sätt som på ett bytes-värde. Indexering returnerar ett heltal; slicing returnerar en annan bytearray:

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

2.6.5.4. Konvertera mellan bytes och bytearray

bytes och bytearray konverteras till varandra med sina konstruktorer. Använd detta när ett API specifikt kräver en av formerna:

>>> 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 för slicing utan kopiering

Att slica en bytes eller bytearray kopierar normalt sett byten in i en ny buffert. memoryview exponerar samma bytes utan att kopiera:

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

En vy över en bytearray är också skrivbar – att förändra vyn förändrar den underliggande bufferten:

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

Ta till memoryview när det skulle vara slöseri att kopiera en slice – vanligtvis när samma stora buffert skickas runt eller behandlas i delar. För vardaglig sträng-liknande hantering av små bytes räcker vanlig slicing gott.