4. Caracteres no-ingleses: Un código para gobernarlos a todos

Tu programa se va ejecutando alegremente, se topa con la cadena 'piña' y se atraganta, por ejemplo, cuando trata de saber la longitud de la palabra:

1
2
3
>>> C = 'piña'
>>> len(C)
5

¿Te parece que 'piña' tiene cinco caracteres? Quizás pienses que sólo tiene cuatro?

Vas a tener que preguntarle a la consola como entiende la palabra:

1
2
>>> C
'pi\xc3\xb1a'

¿Qué demonios es 'pi\xc3\xb1a'? O mejor dicho, '\xc3\xb1', ya que el resto parece ser 'pi_a'. Pídele una aclaración a print:

1
2
>>> print '\xc3\xb1'
ñ

'\xc3\xb1' corresponde a ñ de alguna forma. Evidentemente, tu programa no estaba preparado para lidiar con un carácter que se sale del alfabeto inglés. En este capítulo, vas a conocer el trasfondo del problema y la solución principal.

4.1. Los caracteres ingleses y ASCII

Las computadoras se diseñaron originalmente para utilizar el alfabeto inglés y en concreto, con una codificación de él que se llama el American Standard Code for Information Interchange, con la abreviatura de ASCII., pronunciado como [ˈæski] o “ass-kee”, véase ASCII en Wikipedia. ASCII se vale de las 128 combinaciones de los números enteros binarios de 7 bits para definir 128 caracteres – los dígitos 0-9, las letras inglesas a-z y A-Z, los signos de puntuación ingleses más el espacio – junto con códigos de control que tienen su origen en las máquinas de teletipo, algunos de los cuales son obsoletos hoy en día. La tabla siguiente ilustra la codificación:

Los caracteres de ASCII
  0 1 2 3 4 5 6 7 8 9 A B C D E F
0
1
2   ! # $ % & ( ) * + , - . /
3 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
4 @ A B C D E F G H I J K L M N O
5 P Q R S T U V W X Y Z [ \ ] ^ _
6 ` a b c d e f g h i j k l m n o
7 p q r s t u v w x y z { | } ~

Las filas 0 y 1 tienen los códigos de control, como el salto de línea, el retorno de carro, la tecla de escape, o el tocar un timbre. La célula en columna 0 y fila 2 (02) contiene el espacio. La célula F7 contiene la tecla de borrado.

El ASCII es incapaz de representar caracteres que se escapan de esta tabla y no necesitamos ir más allá de las letras acentuadas de otros idiomas de Europa Occidental, como el ejemplo de 'piña' del español, para encontrar un ejemplo.

Se han inventado muchísimas alternativas al ASCII a lo largo de los años, para idiomas distintos y sistemas operativos distintos. Han proliferado de forma orgánica, en la medida de los recursos disponibles, creando una jungla de codificaciones mutuamente ininteligibles que impedía el flujo de información por el mundo.

4.2. Unicode y UTF-8

La única forma de imponer un orden en esta confusión era crear un estándar que abarcaba todos los caracteres de las ortografías del mundo, más espacio para crecer. Éste es el propósito de Unicode, el cual puede representar más de un millón de caracteres, aunque la versión actual sólo defines unos 110,000.

Unicode se diseñó teniendo en cuenta la exhaustividad, pero por motivos de eficacia, un versión más compacta que se llama el Universal Character Set Transformation Format—8-bit o UTF-8 se ha adoptado como el estándar de facto. Todos los documentos con los que vas a trabajar en este libro son de UTF-8.

4.3. La codificación de caracteres de Python

A pesar de la gran aceptación de UTF-8, Python no presupone que sea la única codificación que vas a manejar. Prefiere hacer el procesamiento interno de caracteres con Unicode y luego convertir la salida a otros formatos según las necesidades del usuario. Por lo tanto, Python pone en práctica el flujo de trabajo trazado en el diagrama que hay abajo, inspirado en Figure 3.3: Unicode Decoding and Encoding:

_images/4-FlujoUnicode.png

En prosa, lo que ocurre es que una cadena se descodifica o se traduce de su formato original a Unicode con decode() para que Python lo procese, y después se codifica o se traduce a un formato más compacto con encode().

4.3.1. Un ejemplo de decode() y encode()

Siguiendo con el ejemplo de 'piña', el bloque siguiente ilustra el flujo de trabajo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
>>> C = 'piña'
>>> U = C.decode('utf8')
>>> U
u'pi\xf1a'
>>> len(U)
4
>>> UM = U.upper()
>>> C2 = U.encode('utf8')
>>> print C2
piña
>>> C3 = UM.encode('utf8')
>>> print C3
PIÑA

Línea 2 efectúa la descodificación de UTF-8 a Unicode. Línea 4 es es la representación de piña en Unciode, como una cadena que empieza con u. '\xf1' es la representación de ñ en Unciode, como una secuencia de escape de carácter.

Líneas 5 y 7 hace un poco de procesamiento de la cadena de Unicode, calculando su longitud y pasándola a mayúscula.

Las últimas líneas efectúan la codificación de Unicode a UTF-8 y se valen de print para mostrar el resultado.

A propósito, el intento de procesar una cadena que no esté en Unicode fracasa:

1
2
>>> print C.upper()
PIñA

4.3.2. Como emplear una expresión regular en Unicode

Para aprender como el módulo de re de las expresiones regulares maneja los caracteres que no son de ASCII, vas a volver a la cita de Don Quijote, pero ahora con sus dos acentos:

1
2
3
4
C = ('La libertad, Sancho, es uno de los más preciosos dones '
'que a los hombres dieron los cielos; con ella no pueden igualarse '
'los tesoros que encierran la tierra y el mar: por la libertad, '
'así como por la honra, se puede y debe aventurar la vida.')

Se pasa a Unicode:

1
2
3
>>> U = C.decode('utf8')
>>> U
u'La libertad, Sancho, es uno de los m\xe1s preciosos dones que a los hombres dieron los cielos; con ella no pueden igualarse los tesoros que encierran la tierra y el mar: por la libertad, as\xed como por la honra, se puede y debe aventurar la vida.'

4.3.2.1. Como activar la coincidencia de caracteres no ASCII con UNICODE

Como antes, quieres deseñar una expresión regular que coincide con las palabras de tres letras. Mira con detenimiento las cuatro instrucciones de findall():

1
2
3
4
5
6
>>> from re import findall, UNICODE
>>> findall(r'\b[a-z]{3}\b', U)
>>> findall(r'\b[a-z]{3}\b', U, UNICODE)
>>> findall(r'\b\w{3}\b', U)
>>> findall(r'\b\w{3}\b', U, UNICODE)
[u'uno', u'los', u'm\xe1s', u'que', u'los', u'los', u'con', u'los', u'que', u'mar', u'por', u'as\xed', u'por']

Las instrucciones de 2 y 3 se valen de la gama alfabética [a-z], pero no coinciden con las palabras acentuadas. Las instrucciones de 4 y 5 se valen del metacarácter de clase \w, pero sólo la 5 que tiene la bandera de UNICODE coincide con las palabras acentuadas.

La gama alfabética [a-zA-Z] se restringe a ASCII, lo cual la hace inútil para el texto. El metacarácter alfanumérica de clase \w, en cambio, es sensible a su contexto y por lo tanto puede adaptarse a Unicode – si es que la bandera de UNICODE lo avisa.

4.3.3. Como traducir entre cadenas y números de Unicode con ord() y unichar()

A todos los caracteres de Unicode les corresponde un número, parecido a un índice. Python dispone de un par de métodos para traducir entre un carácter y su número, ejemplificado abajo con ñ:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> C = 'ñ'
>>> U = C.decode('utf8')
>>> U
u'\xf1'
>>> ord(U)
241
>>> unichr(241)
u'\xf1'
>>> print unichr(241).encode('utf8')
ñ

En 5, ord() retorna el número de ñ en Unicode. En 6, unichar() retorna la secuencia de escape de carácter del número. Línea 9 se vale de unichar() para codificar la cadena de Unicode a UTF-8.

Saber el número de un carácter te proporciona una manera útil de filtrar los caracteres no ASCII de un texto, porque todos ellos son más grandes de 128, aunque no sabes todavía la técnica computacional para hacerlo.

4.4. La codificación por defecto de Python

No sé si te has dado cuenta que la consola lidia bastante bien con la codificación de las letras que escribes en ella. Es porque Python tiene una codificación incorporada que se puede revelar así:

1
2
3
>>> import sys
>>> sys.stdout.encoding
'UTF-8'

stdout abrevia standard output, o sea, la salida estándar. La salida estándar es como Python pasa datos a la consola. Normalmente no nos importa, sobre todo, porque es bastante difícil cambiarla. En mi distribución de Anaconda, está fijada en UTF-8, como se ve de línea 3.

4.5. Como saber la codificación de un documento con chardet

Los documentos con las que vas a trabajar estarán en UTF-8, o por lo menos eso espero. Pero, ¿qué ocurre si obtienes un documento en formato desconocido? Parece razonable esperar que haya una forma sencilla de adivinar la codificación que tiene, pero resulta que no hay. Lo que hay es un módulo de Python que se llama chardet para descubrir la codificación de una cadena, pero lo hace de manera probabilística. Es decir, produce una serie de adivinanzas, cada una con un grado de confianza.

Chardet no se incluye en la distribución de Python de Anaconda, pero se puede instalar con pip, véase Chardet: The Universal Character Encoding Detector.

4.6. Resumen

Hemos empezado este capítulo hablando de qué hacer con la palabra piña. Debes entender ya que se tiene que descodificar a Unicode, supuestamente de UTF-8, procesar en Unicode, y codificar a un formato cómod para guardar o compartir. De nuevo, el formato más cómodo suele ser UTF-8. En Unicode, todos los problemas que surgen de caracteres que no son de ASCII se pueden resolver.

4.7. Práctica

Crea una cadena de todos los caracteres españoles que no son de ASCCI, separando cada uno con un espacio y muéstrala. Descodificala a Unicode y muéstrala. Codificala en latin1 y muéstrala otra vez.

Una solución está en Soluciones a “Práctica” con Unicode, para trata de hacerlo por tu cuenta antes de mirarla.

4.8. Lecturas adicionales

Python 2.7’s documentation of Unicode is at Unicode HOWTO.

Decoding and encoding are general functions in Python, handled by the codecs module, see 7.8. codecs — Codec registry and base classes. An encoding of a character set is itself called a codec. The character encodings or codecs that are built into Python are listed at 7.8.3. Standard Encodings. The codecs module can be used to open a file in a specific encoding, but we are going to use one of NLTK’s corpus readers instead.

You can search for the encoding of any character at Unicode Character Search.

4.9. Apéndice

4.9.1. los caracteres españoles que no son de ASCCI

los caracteres españoles que no son de ASCCI
char ANSI# Unicode UTF-8 Latin 1 nombre
¡ 161 u’\xa1’ \xc2\xa1 \xa1 inverted exclamation mark
¿ 191 u’\xbf’ \xc2\xbf \xbf inverted question mark
Á 193 u’\xc1’ \xc3\x81 \xc1 Latin capital a with acute
É 201 u’\xc9’ \xc3\x89 \xc9 Latin capital e with acute
Í 205 u’\xcd’ \xc3\x8d \xcd Latin capital i with acute
Ñ 209 u’\xd1’ \xc3\x91 \xd1 Latin capital n with tilde
Ó 191 u’\xbf’ \xc3\x93 \xbf Latin capital o with acute
Ú 218 u’\xda’ \xc3\x9a \xda Latin capital u with acute
Ü 220 u’\xdc’ \xc3\x9c \xdc Latin capital u with diaeresis
á 225 u’\xe1’ \xc3\xa1 \xe1 Latin small a with acute
é 233 u’\xe9’ \xc3\xa9 \xe9 Latin small e with acute
í 237 u’\xed’ \xc3\xad \xed Latin small i with acute
ñ 241 u’\xf1’ \xc3\xb1 \xf1 Latin small n with tilde
ó 243 u’\xf3’ \xc3\xb3 \xf3 Latin small o with acute
ú 250 u’\xfa’ \xc3\xba \xfa Latin small u with acute
ü 252 u’\xfc’ \xc3\xbc \xfc Latin small u with diaeresis

Footnotes

[1]ISO8859-1 is also known as Latin 1 and was a precursor to UTF-8, see ISO/IEC 8859-1.

Last edited: February 10, 2015