Il linguaggio Python e le Utf8 bytestring

In origine memorizzare un testo su un supporto informatico era semplice. Ad ogni lettera dell’alfabeto corrispondeva un codice, ossia un numero tra 0 e 255. Questo numero poteva essere salvato usando un solo byte di memoria. Così una parola di quattro lettere occupava quattro byte.

In un computer costruito in America erano memorizzate di default tutte le lettere dell’alfabeto latino. Tuttavia lo stesso computer poteva essere usato anche per scrivere in greco: bastava usare un software che sostituiva le lettere in memoria con quelle greche.

Però vennero fuori due problemi. Il primo riguardava il fatto che per aprire un file di testo composto da qualcun altro bisognava prima sapere quale alfabeto utilizzare. Se il testo cominciava con un byte di valore 5, bisognava visualizzare la quinta lettera dell’alfabeto latino? Di quello greco? Di quello tailandese?

Il secondo problema riguardava la possibilità di inserire due o più alfabeti diversi nello stesso testo. Come si poteva fare per scrivere una frase in greco con vicino la traduzione in italiano, se ogni codice doveva riferirsi certe volte alle lettere dell’alfabeto greco e certe volte a quelle dell’alfabeto latino?

Gli scienziati informatici ci hanno lavorato sopra per anni, hanno standardizzato diverse codifiche, fino ad arrivare all’Unicode, in base al quale ogni lettera di qualunque alfabeto o sillabario esistente al mondo (o esistito in passato, inclusi i geroglifici) ha un suo codice univoco.

Questo significa che ci sono lettere dell’alfabeto o segni tipografici (oppure emoticon) che occupano 2, 3, o 4 byte di memoria. Nonostante questo sono stati trovati sistemi ingegnosi per cui le lettere del nostro alfabeto continuano ad occupare un solo byte di memoria: il software è in grado di distinguere le varie combinazioni.

Il problema è che ci stanno contesti diversi che si sono sviluppati indipendentemente, per cui ad ogni carattere possono corrispondere parecchi codici diversi a seconda del contesto.

Prendiamo ad esempio la lettera a dell’alfabeto tibetano.

Il valore unicode è u+0f68.

In una pagina html diventa &#xf68 in esadecimale, oppure &#3944 in numeri decimali.

Dovendolo inserire in un indirizzo web il codice diventa %e0%bd%a8.

In linguaggio Python questa lettera viene memorizzata come “\u0f68” oppure come b'\xe0\xbd\xa8' in forma di utf8 bytestring.

Insomma, può occupare due byte oppure tre.

Ok, ma quand’è che vengono fuori dei malintesi?

Facciamo un esempio: copio la lettera tibetana da una pagina web, la incollo in Blocco Note e salvo il documento come file di testo. La finestra di dialogo chiede di specificare la codifica (quando si dà il nome al file c’è un menù vicino al pulsante Salva) e l’opzione di default è utf-8.

Ok. Se riapro il file con Blocco Note lo visualizzo tranquillamente. Ma se lo apro con Python:

f=open("documento.txt", "r")

testo=f.read()

f.close()

viene fuori una stringa contenente una à accentata, un simbolo di frazione, e una dieresi. Invece di comparire la a dell’alfabeto tibetano, che occupa in memoria tre byte, compare tre simboli da un byte ciascuno.

Che si fa in questi casi?

Diciamo che abbiamo sbagliato la prima istruzione: anziché aprire il file con l’opzione “r” (read, lettura), bisogna aprirlo con opzione “rb” (read binary, leggi come bytes).

A questo punto la variabile testo contiene i seguenti dati

b'\xe0\xbd\xa8'

ossia i 3 byte separati.

Molto carino, visto che corrisponde con quello che è previsto nella tabella. Ma come si fa a convertire tutto questo nella a dell’alfabeto tibetano?

Semplicissimo, basta decodificare i byte scrivendo:

testo.decode()

Stampando dopo avere eseguito questa istruzione, viene fuori la lettera tibetana corretta.

Un altro metodo poteva essere quello di aprire il file in questo modo:

f=open("documento.txt", encoding="utf8")

E se invece abbiamo già aperto il file con modalità “r” e ci troviamo con una stringa di tre lettere, che si può fare?

La prima idea che mi era venuta in mente era convertirle in byte in questo modo:

bytes(testo, “utf-8”)

Il problema è che così facendo otteniamo due bytes per ognuna delle tre lettere, e il risultato non ha nessun senso.

La codifica Ascii non può essere usata, perché qui ci sono valori maggiori di 128.

Così ho provato Latin-1. A caso. E ha funzionato.

Ricapitolando, se abbiamo aperto il file di testo in modalità “r”, prima bisogna convertire la stringa in byte con codifica Latin-1; poi si può decodificarla. Così facendo, si ottiene la lettera tibetana unica da tre byte, invece di tre lettere diverse da un byte ciascuna. L’istruzione è questa:

bytes(testo, "latin_1").decode()

(dove la parola testo è il nome della variabile contenente la stringa, scelta arbitrariamente, mentre tutte le altre fanno parte del linguaggio di programmazione).

Commenti

Post più popolari