Table of Contents

Unicode con Python

Vedere l'articolo Python Unicode Tutorial.

Oppure queste slide: Unicode In Python, Completely Demystified.

Input: lettura da database MySQL

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
    ...

Input/Output: da database PostgreSQL

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()

Input/Output: lettura/scrittura da pipe

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).

Output: codifica implicita della print

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')

Nomi di file e directory

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'))

Lettura file di testo

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()