Python, MySQL, UTF-8 y la madre que los parió

Errar es humano, pero para liar las cosas de verdad se necesita un ordenador… que trabaje con UTF-8.

Para los que no lo conozcan, UTF-8 es una codificación multibyte para la tabla de caracteres UNICODE. Un caracter UNICODE está representado por un número entre 0 y 1,114,111, y UTF-8 es un sistema para representar dicho número mediante una secuencia de bytes. Su característica más atractiva es que los primeros 128 caracteres se corresponden con los de la tabla ASCII y, además, ocupan un solo byte, lo que significa que un texto en ASCII estándar (de 7 bits) es, a la vez, un texto UTF-8; sin embargo, tan pronto comenzamos a usar otros caracteres más raros (como nuestra querida letra Ñ, o nuestras vocales acentuadas), ocuparemos dos, tres o hasta cuatro bytes por caracter.

En efecto, hagamos una pequeña prueba: abramos nuestro interprete de Python y escribamos:

Python 2.4.4c1 (#2, Oct 11 2006, 21:51:02)
[GCC 4.1.2 (Ubuntu 4.1.1-13ubuntu5)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> cadena = u"Papá"
>>> print len(cadena)
4

Hasta aquí nada raro: hemos definido una cadena de tipo UTF-8 (la letra u antes de las comillas sirve para eso) y le hemos pedido al interprete que nos diga su longitud, que, efectivamente, es cuatro. Pero si cambiamos un poco el experimento llegan las cosas «raras»:

>>> cadena2 = "Papá"
>>> print len(cadena2)
5

Ahora la longitud es cinco, lo que no supone ningún misterio si nos damos cuenta de que la cadena la estamos definiendo como cadena normal, no cadena UTF-8, y por tanto nos devuelve el número de bytes que ocupa; y como la letra á (con tilde) necesita dos bytes (pues mi sistema utiliza UTF-8), el misterio queda resuelto.

Por desgracia las cosas se complican cuando uno tiene que usar Python para transmitir y recibir cadenas en UTF-8, byte a byte, por la red; cadenas de las que tomará trozos y compondrá con ellos peticiones para MySQL. Entonces es cuando llega la pesadilla… ¿Y por qué? Pues porque por defecto MySQL espera y entrega sus datos en Latin-1, y también los almacena en dicho formato; pero si yo meto una cadena UTF-8 sin decirle que es UTF-8, el la considera una secuencia el Latin-1, con lo que los datos que me entrega llegan bien, y los que yo le paso también… si no fuese porque el módulo de Python para trabajar con MySQL sabe que éste espera los datos en Latin-1 en intenta convertirlos, soltando a veces una excepción, o metiendo basura en la base de datos el resto.

Por todo ello debemos ser muy cuidadosos a la hora de trabajar con MySQL y Python en UTF-8, y aquí van las pistas que he ido recolectando después de varios días de sufrimientos varios:

Convertir las tablas a UTF-8 Es el primer paso: si creaste tus tablas en formato Latin-1, lo mejor es pasarlas a UTF-8 para evitarse problemas. Para ello usaremos

mysqldump --opt --password=miclave --user=miuser mibasededatos > archivo.sql

A continuación editaremos el código y cambiaremos la palabra latin1 por utf8, con lo que al recuperar los datos, las tablas se crearán en el nuevo formato. Ahora sólo queda volver a grabarlas con

mysql -u miuser -p miclave mibasededatos < archivo.sql

y listo, ya tenemos nuestras tablas en UTF-8.

Acceder a MySQL mediante UTF-8 Esta parte es sencilla también, pero la documentación es escasa y sólo la encontré después de bucear por muchas páginas de Internet y juntar resultados. Tenemos que usar la línea en python:

mibase=MySQLdb.connect(host="mihost", user="usuario", passwd="clave", db="basedatos", charset="utf8", init_command="set names utf8")

(atención a la negrita). Justo a continuación tenemos que añadir:

mibase.names="utf8"

y con ésto ya tendremos garantizado el acceso a la base de datos mediante UTF-8.

En principio con esto ya tendríamos todo listo para poder trabajar cómodamente con MySQL, Python y UTF-8, pero aún falta algo, y es que la cadena que contenga las peticiones no basta con que contenga secuencias UTF-8, sino que tiene que ser un string de tipo UTF-8. Sin embargo, puede ocurrir que tengamos las secuencias UTF-8 almacenadas en cadenas normales. En estos casos, el método encode(«utf-8») nos sacará del apuro. También nos será util si necesitamos mezclar cadenas normales con los resultados que nos devuelva, en UTF-8, la base de datos.

CC BY-SA 4.0 Python, MySQL, UTF-8 y la madre que los parió por A cuadros está licenciado bajo una Licencia Creative Commons Atribución-CompartirIgual 4.0 Internacional.

10 comentarios en “Python, MySQL, UTF-8 y la madre que los parió

  1. 1. ¿y si no tenemos los permisos para modificar las tablas?
    2. ¿por qué pasa esto entonces (tengo una cadena normal, y dices que encode es la solución’?
    >>> ‘jesús’.encode(‘utf-8’)
    Traceback (most recent call last):
    File «», line 1, in
    UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xc3 in position 3: ordinal not in range(128)
    >>> ‘jesús’.decode(‘utf-8′)
    u’jesxfas’

  2. Si no tienes permisos para modificar las tablas, me parece que no podrás hacer mucho, salvo intentar trabajar en Latin-1.

    Respecto a lo segundo, algo han debido cambiar en los métodos encode y decode. Cuando escribí la entrada (en 2006) ésto funcionaba… (suena fatal, pero es así).

  3. tengo problemas al exportar por MySql esta cadena de caracteres «A6101251236)/[L VAL» , cuando la exporto como texto me devuelve «A6101251236)/[L VAL» la barra «» se duplica , por que pasa esto.

    la exportacion la realizo desde «MySQL Query Browser»

  4. Pues por lo que veo, esa herramienta no expande los caracteres especiales. Entiendo que no sabes para qué se utiliza la barra invertida (): normalmente es para introducir una serie de caracteres no visibles, como por ejemplo un retorno de línea (n), un tabulador (t), o unas comillas («) sin cerrar una sentencia.

    Un ejemplo muy simple: si queremos meter una frase como la siguiente en una sentencia MySQL

    Buenos días:
    Hoy hablaremos sobre «El ingenioso hidalgo Don Quijote de La Mancha», escrito por Miguel de Cervantes.

    Tendríamos que meterla así:

    «Buenos días:nHoy hablaremos sobre «El ingenioso hidalgo Don Quijote de La Mancha», escrito por Miguel de Cervantes.»

    Ves que el salto de línea lo he sustituido por n, y las comillas llevan una barra invertida antes, para no confundirlas con las comillas de inicio y de final.

    Y entonces llega la respuesta a tu pregunta: si lo que quiero meter es una barra invertida ¿como lo hago? Pues con una doble barra ().

    El problema en esa aplicación es que, cuando metes una sentencia, añade correctamente el prefijo a los caracteres especiales, pero al mostrarla no los expande; por eso lo ves así.

  5. Gracias ya se para que es la barra «» pero no se como aplicarlo a mi sentencia , esta es la sentencia que estoy utilizando :

    SELECT a.id , b.id_c INTO OUTFILE «/tmp/archivo.txt»
    FIELDS TERMINATED BY ‘,’
    OPTIONALLY ENCLOSED BY ‘»‘
    LINES TERMINATED BY «n»
    FROM mitabla1 a , mitabla2 b
    WHERE a.id = b.id_c

    son muchos registros con el mismo problema :
    «A6101251236)/[L VAL” , cuando la exporto como texto me devuelve
    “A6101251236)/[L VAL” la barra “” se duplica .

    Como deberia ser la sentencia para salvar este problema.

    Gracias por la atencion.

  6. Hombre, no soy tan experto en MySQL como para saber si se puede solucionar desde el propio comando SQL. Lo que me temo es que tendrás que hacerlo «a mano»: buscar en todas las cadenas devueltas y, cada vez que encuentres una barra, quitarla y dejar el siguiente caracter tal cual.

  7. Amigo, muchas gracias por la informacion, esto funciono excelentemente. Te agradezco me ayudo mucho tu informacion. Muchas gracias por compartilo con la comunidad.

  8. Muchas muchas muchas muchas gracias!!!!

    Me estaba volviendo loco con el mysql + bottle + mako !!!

    Ahora funciona a las mil maravillas !

    🙂

Responder a Miguel Silva Cancelar la respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *