Vedere l'articolo Python Unicode Tutorial.
Oppure queste slide: Unicode In Python, Completely Demystified.
Si assume che nel database i campi testo siano codificati UTF-8. Sarebbe opportuno che il charset del database sia dichiarato UTF-8 in fase di creazione dello stesso; con PostgreSQL si tratta dell'impostazione predefinita, con MySQL invece no. Con MySQL è comunque possibile memorizzare testo UTF-8 anche se il charset dichiarato è quello predefinito latin1
, per fortuna perché MySQL ha ancora molti problemi nella gestione di UTF-8 (spazio occupato dalle stringhe, limiti nella dimensione degli indici, ecc.).
Per assicurarsi che Python decodifichi correttamente le stringhe lette da un database:
import MySQLdb conn = MySQLdb.connect(host='localhost', user='dbuser', passwd='dbpass', db='dbname', charset='utf8') curs = conn.cursor() # Not strictly needed: #curs.execute("SET CHARACTER SET utf8") curs.execute("SELECT * FROM table") rows = curs.fetchall() for row in rows: field0 = row[0] print type(field0), field0 ...
La funzione type()
restituisce unicode
, questo vuol dire che Python ha saputo decodificare correttamente l'input (grazie alla dichiarazione del parametro charset
nella connect()
) e memorizza internamente la stringa nel formato ottimale unicode. Questo consente a Python di operare correttamente sulle stringhe, ad esempio quando si applica la funzione len()
i caratteri multibyte vengono valutati correttamente di lunghezza pari a uno.
Se si omette la dichiarazione del charset
nella connect()
, la stringa letta dal database avrebbe un generico type() = str
. Per decodificare correttamente il contenuto bisognerebbe modificare il programma come segue:
curs.execute("SET CHARACTER SET utf8") curs.execute("SELECT * FROM table") rows = curs.fetchall() for row in rows: field0 = row[0].decode('utf-8') print type(field0), field0 ...
Con queste istruzioni si apre una connessione al database e ci si assicura che tutte le stringhe che vengono lette siano di <type 'unicode'>
:
import psycopg2 import psycopg2.extensions psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY) conn = psycopg2.connect(host = "127.0.0.1", database = "dbname", user="dbuser", password="dbpass") conn.set_client_encoding("UTF8") curs = conn.cursor()
Se si deve comunicare con un programma esterno utilizzando UTF-8 conviene come al solito memorizzare le stringhe in unicode e quindi esplicitare l'encoding sia per l'input che per l'output:
text = u"ditemi <b>perché</b> se la mucca fa mu..." subproc = subprocess.Popen(["pandoc", "-f", "html", "-t", "LaTeX"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) output, stderr = subproc.communicate(input=text.encode('utf-8')) output = output.decode('utf-8')
La stringa output
sarà di tipo unicode
, al momento di stamparla si dovrà eventualmente decidere (forzare) l'encoding opportuno per evitare conseguenze impreviste (vedi avanti).
Attenzione ad alcuni comportamenti impliciti del Python, ad esempio una semplice
print string
codifica il contenuto di string
in base all'output: se si tratta di stdout
viene usata la codifica utf-8
(per via della variabile d'ambiente LANG=en_US.UTF-8
), se invece si ridirige l'output su file (oppure la variabile LANG
non è impostata correttamente) viene usata la codifica ascii
ed eventualmente scatta l'errore:
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe0' in position 11: ordinal not in range(128)
Per avere un comportamento univoco conviene codificare esplicitamente l'output:
print string.encode('utf-8')
Alcune funzioni relative al filesystem potrebbero causare problemi, ad esempio:
os.path.isfile(filename) os.stat(filename)
potrebbe fallire con:
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 79: ordinal not in range(128)
La soluzione è codificare esplicitamente la stringa prima di passarla alla funzione:
os.path.isfile(filename.encode('utf-8')) os.stat(filename.encode('utf-8'))
Invece della semplice open()
conviene l'omologa codecs.open()
:
import codecs for line in codecs.open("filename.txt", "r", "iso-8859-15", "replace"): print line.strip()