13 de mayo de 2016

25. Juegos de caracteres

 Juego de caracteres


Nota: para probar los apuntes de este apartado quizás sea necesario abandonar el IDE ninja-ide y usar en su lugar Geany o IDLE (Python 2.7).
Si abrimos un terminal y escribo locale, obtendré información en Ubuntu sobre el idioma configurado en mi ordenador por defecto. Si todo está en castellano, obtendremos una salida parecida a ésta:

Observamos que las distintas informaciones del idioma se obtienen del juego de caracteres UTF-8. Por defecto, en nuestros programas, por tanto, se utilizan los caracteres de este juego.
Si escribo locale -a obtendremos una lista de los idiomas instalados en nuestro ordenador y sus variantes.
La información que usa el ordenador para cargar un lenguaje determinado se encuentra en el fichero /etc/default/locale. Conviene no modificarlo.
Pero no hace mucho tiempo, este juego de caracteres UTF-8 no existía. En su lugar teníamos uno muy sencillo, llamado ASCII, formado por 128 (ASCII extendido 255) símbolos entre los cuales no estaban incluidos algunos tan fundamentales como las letras acentuadas o la "ñ".
Abre un terminal y escribe lo siguiente:
  • ascii S ; saldrá información sobre la letra "S" en el juego de caracteres ASCII (número de orden, sobre todo). Quizás necesites descargar e instalar el programa con apt-get install ascii
  • ascii ; saldrá en pantalla el juego de caracteres ASCII completo.
  • ascii ñ ; no producirá ninguna salida, ya que la "ñ" no pertenece al juego de caracteres ASCII
  • unicode ñ ; la orden unicode dará la información correcta de la ñ, ya que UTF-8 es un subconjunto unicode (compatible con ASCII). Quizás necesites descargar e instalar el programa con apt-get install unicode.
  • unicode U+4531
  • unicode 4000..4500
= = =
En general, se utiliza una ampliación del código ASCII llamada Unicode. Unicode se representa por números de 16 (32) bits. Cada carácter, por tanto se asocia a un código. Por ejemplo, el carácter "Σ" tiene el código U+03A3. 
Sin embargo, en el caso del castellano, y para textos normales, no usamos frecuentemente todos los caracteres UNICODE. Usamos el subconjunto UTF-8. ¿Por qué? hay muchos motivos, pero entre otros se ahorra en memoria, y se evita representaciones con cadenas de bytes ceros (Por ejemplo, la letra P, es la U+0050 en una representación de 16 bits o 2 bytes, de la cual 1 byte es sólo ceros. Así con muchas letras comunes). Una cadena como "python" tendría muchos bytes a cero.
El conjunto UTF-8, sería una codificación o un encoding de Unicode; cumple lo siguiente:
  • Si el código es menor de 128 (27), se representa por su correspondiente byte (igual que el ascii).
  • Si el código está entre 128 y 0x7FF (2047), se convierte a valores de 2 bytes entre 128 y 255. (por ejemplo la "ñ" es 0xc3 0xb1 - 195 177 )
  • Los mayores de 0x7FF se convierten en secuencias de 3 ó 4 bytes, en el que los números de cada byte están entre 128 y 255.
Y UTF-8 tiene varias ventajas:
  • Puede tratar cualquier carácter UNICODE.
  • No contiene cadenas de bytes vacías. Las cadenas de caracteres pueden ser procesadas por lenguajes como C.
  • Las cadenas de texto ASCII son 100% compatibles con UTF-8
  • UTF-8 es bastante compacto. Muchos caracteres ocupan sólo un Byte, y bastantes más sólo 2 Bytes.
  • Es posible reconstruir hasta cierto punto los bits perdidos.
Subrayar además que existen más encodings, como Latin-1, ISO-8859-15, etc.
= = =
El constructor Unicode.
Usamos la construcción unicode para convertir cadenas de 8-bits a una codificación determinada. Tiene la forma unicode(string[, encoding, errors]); si se omite encoding (y errors) intentará codificar la cadena al conjunto ASCII. Si no puede dará un error.
Por tanto, al imprimir la primera cadena del siguiente ejemplo retornará la misma. Pero no la segunda. El error será del tipo "UnicodeDecodeError: 'ascii' codec can't decode byte..."
#!/usr/bin/python
# -*- coding: utf-8 -*-

s = unicode("abcde")
print s

s = unicode("abcdeñ")
print s
= = =
Hay tres formas de manejar los errores: "strict" simplemente lanza el error de forma estricta, "replace" reemplaza los errores con el carácter u+fffd (carácter de reemplazo) e "ignore" simplemente ignora el error.
#!/usr/bin/python
# -*- coding: utf-8 -*-

s = unicode("abcde")
print type(s)
print s

s = unicode("abcdeñ", errors="ignore")
print s
s = unicode("abcdeñ", errors="replace")
print s
s = unicode("abcdeñ", errors="strict")
print s
Intentadlo también desde la línea de comandos
= = =
Y también podemos usar las órdenes siguientes: unichr y ord.
#!/usr/bin/python
# -*- coding: utf-8 -*-

s = unichr(49000)
print s

print ord(s)
Verlo también desde la consola
>>> unichr(49000)
u'\ubf68'
>>> ord (u'\ubf68')
49000 
= = =
Las instancias UNICODE tienen los mismos métodos a las cadenas de texto de 8-bits.
#!/usr/bin/python
# -*- coding: utf-8 -*-

s = u'La provincia donde se encuentra Jerez es Cádiz'
print s

print ("posición de la primera 'e': %d") % (s.find('e'))
print ("posición de 'Jerez': %d") % (s.find('Jerez'))

print s.replace('Jerez','El Puerto')
print s.upper()
= = =
Y tampoco puede usarse otro carácter que no sea ascii. Esta instrucción print s.find("\x9faaa") dará error

Juego de caracteres (y 2)

Método ENCODE (codificar)
De una expresión UNICODE se obtiene una cadena en formato 8-bit en la codificación requerida. Posee los mismos tipos de errores (strict, ignore y replace) más "xmlcharrefreplace", que obtiene la cadena convirtiendo los caracteres que no pueda reemplazar con caracteres en formato XML.
#!/usr/bin/python
# -*- coding: utf-8 -*-

u = unichr(35000) + "ABCDE" + unichr(35000)

print u.encode("utf-8")
print u.encode("ascii", errors="replace")
print u.encode("ascii", errors="ignore")
print u.encode("ascii", errors="xmlcharrefreplace")

print u.encode("ascii", errors="strict")
= = =
Método DECODE (decodificar)
Lo contrario también lo tenemos. De una cadena de 8-bit codificada, obtenemos la cadena UNICODE
#!/usr/bin/python
# -*- coding: utf-8 -*-

# 50 caracteres chinos
u = unichr(0)
for i in range(35000,35050):
    u += unichr(i)

# 1º) Se codifica en utf-8
varutf8 = u.encode("utf-8")
print varutf8
# 2º) Se decodifica otra vez a UNICODE
varutf8_DEC = varutf8.decode ("utf-8")
# ¿Son iguales u y varutf8_DEC?
print u == varutf8_DEC

# ¿Y sería igual si la decodifico en otro encoding?
varutf8_DEC_latin1 = varutf8.decode ("latin1")
print u == varutf8_DEC_latin1
print varutf8_DEC_latin1
= = =
Literales UNICODE
Puedo escribir literales o caracteres UNICODE de estas formas:
  • Si se pueden escribir los caracteres se escribe u ó U precediendo a la cadena:  u'abcdeñ'
  • Si no puedo escribirlos, uso la secuencia de escape '\u' seguido de cuatro dígitos hexadecimales
  • O bien, la secuencia de escape '\U' con 8 dígitos hexadecimales,
  • o bien '\x' con 2 caracteres hexadecimales, pero no puedo expresar cualquier código.
#!/usr/bin/python
# -*- coding: utf-8 -*-

# declaro un expresión UNICODE con caracteres de escape
cad = u'\x70\u78f1\u78fa\U00008001'
print cad
= = =
Codificación por defecto en tu programa Python
PYTHON sabe escribir caracteres UNICODE en cualquier codificación. Otra cosa es que tu editor esté configurado para hacerlo. Por ejemplo, en GEANY, o en el editor de textos GEDIT puede escribirse muy bien con UTF-8, pero puede ser distinto en otro idioma o con otra codificación. PYTHON aceptará la cadena u'ñN' si puede escribirse, y no tendrás que escribir u'\uc3b1\uc391'. Ahora bien, tendrás que explicitar en el programa qué juego de caracteres estás usando. Por eso empezamos los programas por
# -*- coding: utf-8 -*-
= = =
Propiedades del tipo UNICODE
Aunque aún no sabemos lo que es un módulo, hablaremos de uno: unicodedata. Este permite obtener información de caracteres UNICODE. Es más bien una base de datos con información sobre los mismos.
import unicodedata

u = unichr(233) + unichr(0x0bf2) + unichr(3972) + unichr(6000) + unichr(13231)
print u + "\n"

for i, c in enumerate(u):
    print i, '%04x' % ord(c), unicodedata.category(c),
    print unicodedata.name(c)

# Get numeric value of second character
print unicodedata.numeric(u[1])
En el siguiente programa, se muestra información sobre cada caracter UNICODE: el número hexadecimal, la categoría, la descripción... La última línea expresa el código numérico (en base 10) del carácter u[1]
= = =
Hablaremos más adelante de la escritura y lectura de ficheros de caracteres UNICODE, cuando veamos los módulos.


No hay comentarios:

Publicar un comentario