6.4. Unicode

Unicode è un sistema per rappresentare i caratteri di tutti i differenti linguaggi del mondo. Quando Python analizza un documento XML, tutti i dati sono immagazzinati in memoria sottoforma di unicode.

Sarà spiegato tutto fra un minuto, ma prima, una piccola introduzione.

Nota storica.  Prima dell'unicode, esistevano sistemi di codifica dei caratteri separati per ogni linguaggio, ognuno usava gli stessi numeri (0-255) per rappresentare i caratteri di quel linguaggio. Alcuni (come il russo) avevano multipli standard, in conflitto sul modo di rappresentare lo stesso carattere; altri linguaggi (come il giapponese) avevano così tanti caratteri che richiedevano numerosi set di caratteri. Lo scambio di documenti fra i sistemi era difficile perché non c'era modo per un computer di dire quale schema di codifica dei caratteri avesse usato l'autore del documento; il computer vedeva solo numeri ed i numeri potevano significare cose differenti. Perciò pensate di porre questi documenti nel medesimo luogo (ad esempio la stessa tabella di un database); avreste bisogno di registrare la codica dei caratteri per ogni parte del testo ed essere sicuri di fornirla insieme allo stesso. Quindi pensate a documenti multilingue, con caratteri provenienti da vari linguaggi nello stesso documento. Solitamente questi utilizzano caratteri di escape per cambiare il modo di codifica; usiamo il modo russo koi8-r, perciò il carattere 241 indica questo; adesso usiamo il Greco del Mac, perciò il carattere 241 indica qualcos'altro. E così via. Questi sono i problemi per i quali l'unicode è stato ideato.

Per risolvere questi problemi, l'unicode rappresenta ogni carattere come un numero di 2 byte, da 0 a 65535. [11] Ogni numero di 2 byte rappresenta un unico carattere usato in almeno uno dei linguaggi del mondo. Caratteri che sono usati in molteplici linguaggi hanno lo stesso codice numerico. C'è esattamente 1 numero per carattere ed esattamente un carattere per numero. I dati unicode non sono mai ambigui.

Ovviamente c'è ancora il problema di tutte queste eredità dei sistemi di codifica. L'ASCII a 7 bit, per esempio, registra i caratteri inglesi come numeri da 0 a 127. (65 è la “A” maiuscola, 97 è la “a minuscola” e così via.) L'inglese ha un alfabeto molto semplice, perciò può essere espresso completamente tramite l'ASCII a 7 bit. I linguaggi dell'ovest europeo come il francese, lo spagnolo e il tedesco (e l'italiano N.d.T.) usano tutti una codifica dei caratteri chiamata ISO-8859-1 (“latin-1”), per i numeri da 0 al 127 ma vengono estesi nel gruppo di caratteri dal 128 al 255 come la “n con la tilde sopra”(241), o la “u con due punti sopra”(252). L'unicode utilizza gli stessi caratteri dell'ASCII a 7 bit per i numeri dallo 0 al 127 e gli stessi caratteri dell'ISO-8859-1 per i numeri da 128 a 255, estende da quel punto in poi, tutti i caratteri per gli altri linguaggi con i numeri rimanenti, da 256 a 65535.

Quando vi occupate dei dati unicode, potreste aver bisogno, in alcuni punti, di convertire i dati in una delle altre codifiche dei caratteri descritte. Per esempio, per integrarli con qualche altro sistema che si aspetta dei dati in uno schema di codifica specifico ad 1 byte o stamparlo da un terminale o da una stampante non unicode. Oppure immagazzinarli in un documento XML che esplicita specificatamente lo schema di codifica.

E dopo questa nota, torniamo al Python.

Python supporta l'unicode nativamente sin dalla versione 2.0. [12] Il pacchetto XML usa l'unicode per registrare tutti i dati XML analizzati, ma potete usare l'unicode ovunque.

Esempio 6.13. Introduzione sull'unicode

>>> s = u'Dive in'            1
>>> s
u'Dive in'
>>> print s                   2
Dive in
1 Per creare una stringa unicode, invece di una regolare stringa ASCII, aggiungete la lettera “u” prima della stringa. Notate che questa particolare stringa non contiene nessun carattere non-ASCII. Questo è bene; unicode è un superset dell'ASCII (un superset molto grande in realtà), così che ogni stringa regolare ASCII possa essere registrata come unicode.
2 Quando stampiamo una stringa, Python si preoccupa di convertirla nel vostra codifica di default, che solitamente è l'ASCII (vi dirò di più fra un minuto). Dato che questa stringa unicode è formata da caratteri che sono anche caratteri ASCII, stamparla ha lo stesso risultato di stampare una normale stringa ASCII; la conversione è senza cuciture e se non sapeste che s era una stringa unicode, non notereste mai la differenza.

Esempio 6.14. immagazzinare caratteri non-ASCII

>>> s = u'La Pe\xf1a'         1
>>> print s                   2
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
UnicodeError: ASCII encoding error: ordinal not in range(128)
>>> print s.encode('latin-1') 3
La Peña
1 Il vero vantaggio dell'unicode è, ovviamente, la sua abilità di immagazzinare caratteri non-ASCII, come la spagnola “ñ” (n con una tilde sopra). Il carattere unicode per la tilde-n è 0xf1 in esadecimale (241 in decimale), perciò potete digitarla in questo modo: \xf1.
2 Ricordate che dissi che la funzione print tenta di convertire una stringa unicode in ASCII in modo da poterla stampare? Bene, non funzionerà qui, visto che la nostra stringa unicode contiene caratteri non-ASCII, perciò Python solleva un errore UnicodeError.
3 Ecco dove avviene la conversione dall'unicode ad altri schemi di codifica. s è una stringa unicode ma print può stampare solo stringhe regolari. Per risolvere questo problema, chiamiamo il metodo encode, disponibile in ogni stringa unicode, per convertire una stringa unicode in una stringa regolare nello schema di codifica dato, che passiamo come parametro. In questo caso, stiamo usando latin-1 (anche conosciuto come iso-8859-1), che include la tilde-n (che invece l'ASCII non include, dato che include unicamente i caratteri numerati da 0 a 127).

Ricordate quando dissi che Python solitamente convertiva unicode in ASCII, ovunque occorresse creare una stringa regolare da una unicode? Bene, questo schema di codifica predefinito è un'opzione che potete personalizzare.

Esempio 6.15. sitecustomize.py

# sitecustomize.py                   1
# this file can be anywhere in your Python path,
# but it usually goes in ${pythondir}/lib/site-packages/

import sys

sys.setdefaultencoding('iso-8859-1') 2
1 sitecustomize.py è uno script speciale; Python tenterà di importarlo all'avvio, così ogni codice al suo interno partirà automaticamente. Come menzionato dal commento, può essere posto ovunque (fin dove import lo possa trovare), ma solitamente viene messo nella directory lib/site-packages della vostra distribuzione Python.
2 La funzione setdefaultencoding imposta, quindi, la codifica predefinita. Questo è lo schema di codifica che Python proverà ad usare, ogniqualvolta occorresse automatizzare la conversione da una stringa unicode ad una stringa regolare.

Esempio 6.16. Effetti dell'impostazione di una codifica predefinita

>>> import sys
>>> sys.getdefaultencoding() 1
'iso-8859-1'
>>> s = u'La Pe\xf1a'
>>> print s                  2
La Peña
1 Questo esempio richiede che voi abbiate fatto le modifiche elencate nell'esempio precedente al vostro file sitecustomize.py e che abbiate riavviato Python. Se la vostra codifica predefinita è ancora 'ascii', vuol dire che non avete impostato correttamente il vostro sitecustomize.py o che non avete riavviato Python. La codifica predefinita può essere modificata unicamente durante l'avvio di Python; non potete farlo dopo. Grazie ad alcuni trucchi di programmazione che non voglio mostrare adesso, potete chiamare anche sys.setdefaultencoding dopo che Python è stato avviato. Scavate in site.py e cercate “setdefaultencoding” per maggiori approfondimenti.
2 Adesso che lo schema di codifica predefinito include tutti i caratteri usati nella nostra stringa, Python non ha problemi ad auto-convertire la stringa ed a stamparla.

E riguardo l'XML? Bene, ogni documento XML contiene una codifica specifica. ISO-8859-1 è una popolare codifica per dati nei linguaggi dell'ovest europeo. KOI8-R è comune nei testi russi. La codifica, se specificata, si trova nell'header del documento XML.

Esempio 6.17. russiansample.xml


<?xml version="1.0" encoding="koi8-r"?>       1
<preface>
<title>Предисловие</title>                    2
</preface>
1 Questo esempio è estratto da un reale documento XML in russo; È parte della traduzione di questo libro, notate che la codifica, koi8-r, è specificata nell'header.
2 Questi sono i caratteri cirillici che, per quanto ne so, esprimono in russo la parola “Prefazione”. Se aprite questo file con un normale editor di testi, i caratteri sembreranno incomprensibili, visto che sono codificati usando lo schema di codifica koi8-r, ma vengono mostrati in iso-8859-1.

Esempio 6.18. Analizzare russiansample.xml

>>> from xml.dom import minidom
>>> xmldoc = minidom.parse('russiansample.xml') 1
>>> title = xmldoc.getElementsByTagName('title')[0].firstChild.data
>>> title                                       2
u'\u041f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435'
>>> print title                                 3
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
UnicodeError: ASCII encoding error: ordinal not in range(128)
>>> convertedtitle = title.encode('koi8-r')     4
>>> convertedtitle
'\xf0\xd2\xc5\xc4\xc9\xd3\xcc\xcf\xd7\xc9\xc5'
>>> print convertedtitle                        5
Предисловие
1 Assunto che abbiate salvato l'esempio precedente come russiansample.xml nella directory corrente ed inoltre, per completezza, che abbiate riportato la vostra codifica predefinita di nuovo in 'ascii', rimuovendo il file sitecustomize.py o almeno commentando la linea setdefaultencoding.
2 Notate i dati testo della etichetta del titolo ... ora nella variabile title, grazie a quella lunga concatenazione di funzioni Python che ho precipitosamente saltato e, fastidiosamente, non spiegherò fino alla prossima sezione ... i dati di testo nel titolo del documento XML sono registrati come unicode.
3 Stampare il titolo non è possible, perché questa stringa unicode contiene caratteri non-ASCII, e Python non può convertirli in ASCII perché non hanno significato.
4 Possiamo, comunque, convertirlo esplicitamente in koi8-r ed otterremmo una (regolare, non unicode) stringa di caratteri ad un byte (f0, d2, c5 e così via) che sono la vesione koi8-r-encoded dei caratteri nella stringa unicode originale.
5 Stampare la stringa codificata in koi8-r probabilmente mostrerà insensatezze sul vostro schermo, dato che il vostro IDE Python sta interpretando questi caratteri come iso-8859-1, non koi8-r. Ma almeno le stampa. Se osservate attentamente, è la stessa roba che avete visto quando avete aperto il documento originale XML in un editor non unicode. Python lo convertì da koi8-r in unicode quando analizzò il documento XML ed ora lo abbiamo appena riconvertito.

Per riassumere, unicode stesso è un pò intimidente se non lo avete mai visto prima, ma i dati unicode sono veramente facili da gestire in Python. Se i vostri documenti XML sono tutti in ASCII a 7 bit (come gli esempi in questo capitolo) non avrete mai bisogno di pensare all'unicode. Python convertirà i dati ASCII nel documento XML in unicode, durante il parsing, ed auto-convertirà in ASCII ogniqualvolta sarà necessario, non ve ne accorgerete neanche. Ma se avete bisogno di occuparvi di dati in altre lingue, Python è pronto.

Ulteriori letture

Footnotes

[11] Questo, tristemente, è ancora una semplificazione. Unicode ora è stato esteso per gestire testi in Cinese antico, in Coreano ed in Giapponese, che hanno talmente tanti caratteri diversi che il sistema unicode a 2 byte non li potrebbe rappresentare tutti. Ma Python attualmente non supporta tutto questo, e non so se ci sia qualche progetto che permetta di estenderlo in tale senso. Spiacente, avete raggiunto i limiti della mia conoscenza.

[12] Python ha il supporto per unicode dalla versione 1.6, ma la versione 1.6 era un obbligo contrattuale di cui nessuno ama parlare, un figliastro da dimenticare. Anche la documentazione ufficiale di Python dichiara che unicode è “nuovo nella versione 2.0”. È una bugia ma, come le bugie dei presidenti che dicono di aver fumato ma che non gli è piaciuto, scegliamo di crederci perché ricordiamo la nostra giovinezza mal spesa in maniera fin troppo vivida.