viernes, 5 de febrero de 2010

Python - Unicode

Unicode es un sistema para representar caracteres de todos los diferentes idiomas del mundo. Cuando Python analiza un documento XML, todos los datos se almacenan en memoria como Unicode.

Antes de Unicode había diferentes sistemas de codificación de caracteres para cada idioma, cada uno usando los mismos números (0-255) para representar los caracteres de ese lenguaje. El problema radicaba cuando se quería intercambiar documentos entre sistemas heterogéneos, restultando difícil, puesto que no había manera de que un ordenador supiera con certeza qué esquema de codificación de caracteres había usado el autor del documentom y los números pueden significar muchas cosas (en japonés el carácter codificado 234 no es el mismo que el carácter codificado ruso 234, ni en español, que 234 es Û).

Para resolver este problema Unicode representa cada carácter como un número de 2 bytes (de 0 a 65535). Cada número de 2 bytes representa un único carácter utilizado en al menos un idioma del mundo (los caracteres que se usan en más de un idioma tienen el mismo código numérico). Hay exactamente un número por carácter, y exactamente un carácter por número. Los datos de Unicode nunca son ambiguos.

Evidentemente, los sistemas de codificación antiguos siguen existiendo. Los caracteres ingleses se codifican en ASCII de 7 bits (0 a 127). Idiomas europeos occidentales como el español ó el francés utilizan el ISO-8859-1 (ó Latin-1), que usa un ASCII extendido (0 a 255), para representar caracteres especiales, como la ñ, ó las tildes (á,é,í,ó,ú),por ejemplo. Unicode usa los mismos caracteres que el ASCII de 7 bits para los números de 0 a 127, y los mismos caracteres que el ISO-8859-1, del 128 a 255, y de ahí en adelante se extiende para otros lenguajes que usan el resto de los números del 256 al 65535.

La verdadera ventaja de Unicode reside en su capacidad de almacenar caracteres que no son ASCII, como la ñ española. Para crear una cadena Unicode en lugar de una ASCII normal solo hay que añadir a letra u antes de la cadena. Por ejemplo:

>>> x= u'angel'
>>> print str(x)
angel

Python normalmente convierte el Unicode a ASCII cuando necesita hacer una cadena normal partiendo de una Unicode. En este ejemplo, la función str intenta convertir una cadena Unicode en ASCII para poder imprimirla a continuación con print. ¿Y si hacemos x = u'ángel'?

>>> x = u'ángel'
>>> print str(x)
Traceback (most recent call last):
File ("stdin)", line 1, in (module)
UnicodeEncodeError: 'ascii' codec can´t encode character u'\xe1' in position 0: ordinal not in range(128)

Vemos que da error, puesto que la cadena Unicode ángel contiene carácteres (la á) que no son ASCII. Es por ello que Python se queja produciendo un error UnicodeDecodeError.

Esta conversión que la función str (lo hacen más funciones en Python, y en muchos más casos) realiza de Unicode a ASCII se puede modificar, ahorrándonos errores del tipo:

UnicodeDecodeError: 'ascii' codec can't decode byte 0x@@@ in position @@: ordinal not in range(128)

Lo que podemos hacer es decirle a Python que cuando se encuentre en el caso de que tenga que realizar una conversión, en vez de hacerlo a ASCII de 7 bits, lo haga al iso-8859-1, que es el que contempla el idioma español. ¿Cómo se hace esto?

Hay que crear un fichero, llamado sitecustomize.py. Este fichero puede estar en cualquier parte, siempre que el import pueda encontrarlo, pero normalmente se encuentra en el lib/site-packages de Python. Si por ejemplo se utiliza Python 2.5, este fichero debería de crearse en c:\Python25\Lib\site-packages.

El contenido sería el siguiente:

import sys
sys.setdefaultencoding('iso-8859-1')

Python intentará importar sitecustomize.py cada vez que arranque, de manera que ejecutará automáticamente cualquier código que incluya. La función setdefaultencoding establece la codificación por omisión. Éste es el esquema de codificación que Python intentará usar cada vez que necesite convertir automáticamente una cadena Unicode a una normal.

La codificación por omisión sólo se puede cambiar durante el inicio de Python; no se puede hacer más adelante. Es más, estando en el intérprete de Python no puede ejecutarse la sentencia sys.setdefaultencoding('iso-8859-1'), ya que nos dirá que no existe este atributo.

Ahora que el esquema de codificación por omisión incluye todos los caracteres que usa en la cadena, Python no tiene problemas en autoconvertir la cadena e imprimirla.

>>> x = u'ángel'
>>> print str(x)
ángel


10 comentarios:

  1. Genial, tío...

    Una cosa, podrías adaptar la agenda para que cuando fuera en inglés el primer día de la semana fuera domingo?

    ResponderEliminar
  2. Por supuesto, es muy sencillo. Si necesitas una, te la compilo y te la envío de inmmediato con la modificación que me propones. Un saludo!

    ResponderEliminar
  3. la verdad que tu blog me encanta esta muy bueno sigue asi

    ResponderEliminar
  4. Gracias por esta informacion.
    Estuve 3 dias metido en un problema con unicode y acentos y no tenia como solucionarlo, hasta que por suerte encontre esta pagina y como por arte de magia mis problemas se fueron.

    Saludos desde Lima / Peru

    ResponderEliminar
  5. como hacerlo en linux?

    ResponderEliminar
  6. gracias , por compartir tus conocimiento llevava dias sin resolver el tema de los caracteres especiales, aunque todabia no resolvi al 100x100 mis problema.

    ResponderEliminar
  7. y... ¿los caracteres griegos? ¿se podrá?

    ResponderEliminar
  8. he seguido al pie de la letra tus comentarios y no consigo que funciones. Mi equipo es un mac y estoy intentando hacer un parseo de twitter con el módulo de python tweetpy. El caso es que lo he probado todo y sigo sin conseguir que me saque los acentos, etc. Cualquier ayuda es bienvenida. Gracias

    ResponderEliminar