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 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¶
str.encode()convertește un șir în bytes folosind o codificare denumită (implicit"utf-8").bytes.decode()face inversul.
>>> "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 False – data[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ă.