2.6. Testo e byte¶
Python ha due tipi di sequenza per i dati grezzi di caratteri:
str– una sequenza di codepoint Unicode. Usato per tutto il testo leggibile dall’uomo: percorsi di file, messaggi di log, payload JSON.bytes– una sequenza di interi nell’intervallo 0 – 255. Usato per i dati binari grezzi: frame UART, buffer di immagini, pacchetti di rete, valori di registro.
Non possono essere mischiati senza una conversione esplicita. Passare uno str a un metodo hardware write solleva TypeError, e anche l’inverso viene rifiutato.
Uno str memorizza caratteri Unicode; un bytes memorizza ottetti grezzi. Il passaggio tra di loro è la codifica (str → bytes) e la decodifica (bytes → str).¶
2.6.1. Letterali bytes¶
Un letterale bytes è un letterale simile a una stringa con il prefisso b:
header = b"OMV"
crlf = b"\r\n"
payload = b"\x01\x02\x03"
All’interno di un letterale bytes sono consentiti direttamente solo caratteri ASCII; i valori non ASCII devono essere scritti come escape esadecimali \xHH.
2.6.2. Codifica e decodifica¶
str.encode()converte una stringa in bytes utilizzando una codifica con nome (predefinita"utf-8").bytes.decode()esegue l’operazione inversa.
>>> "hello".encode()
b'hello'
>>> "héllo".encode()
b'h\xc3\xa9llo' # é is two bytes in UTF-8
>>> b"hello".decode()
'hello'
UTF-8 è la codifica predefinita e la scelta giusta per qualsiasi cosa possa contenere caratteri non ASCII. Usa "ascii" solo quando i dati sono garantiti come puro ASCII; in questo modo un byte non ASCII fuori posto solleva UnicodeError invece di passare silenziosamente.
2.6.3. Indicizzazione e slicing¶
Un valore bytes si comporta come una sequenza di interi quando viene indicizzato, non come una sequenza di stringhe di un byte:
>>> data = b"abc"
>>> data[0]
97 # the int 97, not 'a'
>>> data[0:1]
b'a' # slicing returns bytes
Un errore comune è confrontare data[0] == "a" e sorprendersi che sia False: data[0] è un intero, non una stringa di un carattere, quindi i due valori non possono mai coincidere.
2.6.4. ord e chr – collegare caratteri e interi¶
Poiché l’indicizzazione di un bytes restituisce un intero ma il resto del programma probabilmente ragiona in termini di caratteri, Python fornisce due funzioni built-in per passare tra i due:
ord()– prende una stringa di un carattere e restituisce il suo codepoint intero.chr()– l’inverso: dato un intero, restituisce la stringa di un carattere per quel codepoint.
>>> ord("a")
97
>>> chr(97)
'a'
>>> ord("A"), chr(0x41)
(65, 'A')
Per i caratteri ASCII il codepoint è uguale al valore del byte, quindi ord("a") e b"a"[0] danno entrambi 97. Questo fa sì che i confronti di byte si leggano in termini del carattere a cui sei effettivamente interessato:
>>> data = b"abc"
>>> data[0] == ord("a") # instead of the magic number 97
True
E chr() è comodo per il logging o il debugging quando vuoi vedere la forma stampabile di un byte:
>>> chr(data[0])
'a'
Per i caratteri non ASCII ord() restituisce il codepoint Unicode, che non è uguale a nessun singolo byte nella forma codificata; la rappresentazione in byte dipende dalla codifica.
2.6.5. bytearray per buffer modificabili¶
bytes è immutabile: ogni «modifica» restituisce un nuovo oggetto e lascia intatto l’originale. Per i dati che intendi modificare, aggiungere o riempire pezzo per pezzo, usa bytearray. Contiene lo stesso contenuto di bytes ma supporta la mutazione sul posto:
>>> 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. Creare un bytearray¶
Il costruttore bytearray accetta diversi input:
bytearray(8)– un buffer di 8 byte a zero.bytearray(b"hello")– una copia modificabile di un valore bytes.bytearray("hello", "utf-8")– un bytearray da una stringa, utilizzando la codifica indicata.bytearray([72, 73, 74])– un bytearray da una sequenza di interi in 0 – 255 (qui,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. Modificare un bytearray¶
L’assegnazione indicizzata e tramite slice funzionano esattamente come per una 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')
I singoli byte devono essere interi in 0 – 255; assegnare qualsiasi altro tipo solleva TypeError o ValueError.
L’assegnazione tramite slice può modificare la lunghezza del buffer. Sostituire una slice con un valore più lungo fa crescere il bytearray; sostituire con un valore più corto lo restringe. Sostituire con b"" elimina completamente la slice:
>>> 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')
I metodi bytearray.append() e bytearray.extend() aggiungono byte alla fine senza riallocare ogni volta l’intero buffer:
>>> buf = bytearray()
>>> buf.append(0x01)
>>> buf.extend(b"abc")
>>> buf
bytearray(b'\x01abc')
2.6.5.3. Leggere da un bytearray¶
L’indicizzazione, lo slicing, l’iterazione e i metodi di ispezione di bytes (bytes.startswith(), bytes.find(), bytes.strip(), ecc.) funzionano tutti allo stesso modo che su un valore bytes. L’indicizzazione restituisce un intero; lo slicing restituisce un altro bytearray:
>>> buf = bytearray(b"OpenMV")
>>> buf[0]
79
>>> buf[0:4]
bytearray(b'Open')
>>> buf.startswith(b"Open")
True
2.6.5.4. Conversione tra bytes e bytearray¶
bytes e bytearray si convertono l’uno nell’altro con i loro costruttori. Usa questa tecnica quando un’API richiede specificamente una delle due forme:
>>> 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 per slicing senza copia¶
Lo slicing di un bytes o di un bytearray normalmente copia i byte in un nuovo buffer. memoryview espone gli stessi byte senza copiarli:
>>> 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'
Una vista su un bytearray è anche scrivibile: mutare la vista muta il buffer sottostante:
>>> view[0] = ord("o")
>>> buf
bytearray(b'openMV Cam')
Ricorri a memoryview quando copiare una slice sarebbe uno spreco, tipicamente quando lo stesso buffer di grandi dimensioni viene passato in giro o elaborato a pezzi. Per il lavoro quotidiano in stile stringa su piccole quantità di byte, lo slicing semplice va benissimo.