2.6. Text vs. octeți

Python are două tipuri de secvențe pentru date brute de caractere:

  • str – o secvență de puncte de cod Unicode. Folosit pentru tot textul lizibil de către om: căi de fișiere, mesaje de jurnal, sarcini utile JSON.

  • bytes – o secvență de numere întregi în intervalul 0 – 255. Folosit pentru date binare brute: cadre UART, tampoane de imagine, pachete de rețea, valori de registre.

Acestea nu pot fi amestecate fără o conversie explicită. Trecerea unui str către o metodă hardware write ridică TypeError, iar inversul este de asemenea respins.

Un str de puncte de cod Unicode în stânga și o secvență bytes de octeți bruți în dreapta, cu săgeți encode și decode între ele.

Un str stochează caractere Unicode; un bytes stochează octeți bruți. Trecerea de la unul la altul este codificare (str → bytes) și decodificare (bytes → str).

2.6.1. literali bytes

Un literal bytes este un literal asemănător unui șir, prefixat cu b:

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

Doar caracterele ASCII sunt permise direct în interiorul unui literal bytes; valorile non-ASCII trebuie scrise ca secvențe de evadare hexazecimale \xHH.

2.6.2. Codificare și decodificare

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

UTF-8 este alegerea implicită și cea corectă pentru orice ar putea conține caractere non-ASCII. Folosiți "ascii" doar când datele sunt garantat ASCII simplu; în acest fel, un octet non-ASCII rătăcit ridică UnicodeError în loc să treacă în tăcere.

2.6.3. Indexare și feliere

O valoare bytes se comportă ca o secvență de numere întregi atunci când este indexată, nu ca o secvență de șiruri de un singur octet:

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

O greșeală frecventă este compararea data[0] == "a" și surprinderea că rezultatul este Falsedata[0] este un număr întreg, nu un șir de un singur caracter, așa că cele două valori nu se pot potrivi niciodată.

2.6.4. ord și chr – punte între caractere și numere întregi

Deoarece indexarea unui bytes returnează un număr întreg, dar restul programului probabil gândește în termeni de caractere, Python oferă două funcții încorporate pentru trecerea între ele:

  • ord() – ia un șir de un singur caracter și returnează punctul său de cod întreg.

  • chr() – inversul: dat fiind un număr întreg, returnează șirul de un singur caracter pentru acel punct de cod.

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

Pentru caracterele ASCII, punctul de cod este egal cu valoarea octetului, așa că ord("a") și b"a"[0] dau ambele 97. Asta face ca comparațiile de octeți să se citească în termeni de caracterul care vă interesează cu adevărat:

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

Iar chr() este util pentru jurnalizare sau depanare atunci când doriți să vedeți forma tipăribilă a unui octet:

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

Pentru caracterele non-ASCII, ord() returnează punctul de cod Unicode, care nu este același cu niciun octet individual din forma codificată; reprezentarea în octeți depinde de codificare.

2.6.5. bytearray pentru tampoane mutabile

bytes este imutabil – fiecare „modificare” returnează un obiect nou și lasă originalul neatins. Pentru date pe care intenționați să le modificați, la care să adăugați sau pe care să le completați bucată cu bucată, folosiți bytearray. Acesta deține același conținut ca bytes, dar acceptă modificarea pe loc:

>>> 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. Crearea unui bytearray

Constructorul bytearray acceptă mai multe intrări:

  • bytearray(8) – un tampon de 8 octeți zero.

  • bytearray(b"hello") – o copie mutabilă a unei valori bytes.

  • bytearray("hello", "utf-8") – un bytearray dintr-un șir, folosind codificarea dată.

  • bytearray([72, 73, 74]) – un bytearray dintr-o secvență de numere întregi din 0 – 255 (aici, 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. Modificarea unui bytearray

Atribuirea indexată și cea pe felii funcționează la fel ca pentru o 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')

Octeții individuali trebuie să fie numere întregi din 0 – 255; atribuirea oricărui alt tip ridică TypeError sau ValueError.

Atribuirea pe felii poate schimba lungimea tamponului. Înlocuirea unei felii cu o valoare mai lungă mărește bytearray-ul; înlocuirea cu o valoare mai scurtă îl micșorează. Înlocuirea cu b"" șterge complet felia:

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

Metodele bytearray.append() și bytearray.extend() adaugă octeți la sfârșit fără a realoca de fiecare dată întregul tampon:

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

2.6.5.3. Citirea dintr-un bytearray

Indexarea, felierea, iterarea și metodele de inspecție bytes (bytes.startswith(), bytes.find(), bytes.strip() etc.) funcționează toate la fel ca asupra unei valori bytes. Indexarea returnează un număr întreg; felierea returnează un alt bytearray:

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

2.6.5.4. Conversia între bytes și bytearray

bytes și bytearray se convertesc reciproc cu constructorii lor. Folosiți acest lucru atunci când un API necesită în mod specific o anumită formă:

>>> 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 pentru feliere fără copiere

Felierea unui bytes sau bytearray copiază în mod normal octeții într-un tampon nou. memoryview expune aceiași octeți fără a copia:

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

O vizualizare asupra unui bytearray este de asemenea inscriptibilă – modificarea vizualizării modifică tamponul subiacent:

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

Recurgeți la memoryview atunci când copierea unei felii ar fi risipitoare – de obicei când același tampon mare este transmis în jur sau procesat pe bucăți. Pentru lucrul cotidian în stil șir cu cantități mici de octeți, felierea simplă este suficientă.