Skip to content

Latest commit

 

History

History
228 lines (169 loc) · 8.5 KB

File metadata and controls

228 lines (169 loc) · 8.5 KB

Unicode, cadenas de texto y bytes

Esta es una adaptación del contenido de la presentación One Encoding to Rule Them All.

Codificación y decodificación de texto

Con ficheros de "texto plano" nos referimos a aquellos ficheros compuestos únicamente por caracteres. Sin embargo no hay que olvidar que los caracteres son una abstracción y que los ficheros están compuestos realmente de bytes, es decir, números en el rango 0-255.

Hablar de ficheros de texto sin hablar de codificación es inútil ya que para convertir los bytes en caracteres, es decir, para decodificar un fichero, debemos saber la codificación que se usó para escribirlo.

En este contexto, una codificación es un conjunto de reglas que convierten un caracter en una secuencia de bytes. La codificación es el proceso por el cual convertimos un texto en una secuencia de bytes, y la decodificación es el procedimiento, inverso, por el cual recuperamos un texto de una secuencia de bytes.

Codificaciones de texto

La primera codificación de texto fue ASCII (American Standard Code for Information Interchange). ASCII era una tabla que asignaba 128 posiciones a distintos caracteres del alfabeto inglés.

Envíar la letra ñ o un carácter acentuado era imposible, puesto que los símbolos no estaban presentes en la tabla.

Un intento por extender el conjunto de caracteres representables fue Extended ASCII que extendía la tabla de 128 a 256 entradas para incluir muchos de los caracteres usados en la Europa Occidental. Aun así, insuficientes para constituir un sistema de símbolos universal.

En sus sistemas, IBM implementó las llamadas code pages que eran un conjunto de extensiones a ASCII con las que se pretendían cubrir los distintos idiomas del mundo. La Organización Internacional de Estandarización (ISO, por sus siglas en inglés) llegaría a estandarizar las code pages de IBM (con ciertas variaciones) bajo el estándar 8859.

En 1990, ISO lanza Universal Coded Character Set (UCS), como una extensión del estándar 8859-1 (también llamado "Latin 1") pero se impone Unicode, una iniciativa puesta en marcha por ingenieros de Xerox, Apple y Microsoft, con especial énfasis en la interoperabilidad entre distintas lenguas.

Unicode

Unicode es algo más que una tabla gigantesca de símbolos. Unicode incluye reglas para la normalización, decomposición, cotejamiento, interpretación y orden de representación bidireccional. Siendo estos añadidos la principal diferencia con UCS.

En términos de compatibilidad, Unicode se solapa completamente con UCS, Latin 1 y ASCII.

En cuanto a la tabla de símbolos, Unicode posee espacio para 1.114.112 entradas organizadas en 17 planos de 65.535 entradas cada uno. Cada entrada en la tabla se denomina code point y se designa por U+ seguido de su representación hexadecimal.

El plano 0 se conoce como plano multilenguaje básico (BMP) y contiene la mayor parte de los lenguajes modernos del globo.

Al plano 1 se lo llama plano multilenguaje suplementario (SMP) y contiene lenguas históricas, símbolos matemáticos y Emojis, entre otros.

El plano 2, denominado, plano ideográfico suplementario (SIP) contiene ideogramas CJK unificados que no se habían estandarizado anteriormente.

Los planos 3 a 13 están vacíos por el momento aunque se espera que el plano 3 contenga lenguages ideográficos históricos.

Los planos 14 a 16 contienen elementos semánticos y espacio para que aplicaciones de terceros incluyan sus propios símbolos.

UTF-8

Imagina que quieres transmitir la palabra "España", formada por 6 símbolos con code points:

U+0045 U+0073 U+0070 U+0061 U+00F1 U+0061

Una opción sería enviar estos code points tal cual, en una cadena de 16x6=96 bits:

0x004500730070006100F10061

El problema radica en que estamos usando 12 bytes para enviar una palabra de 6 caracteres.

UTF-8 es una codificación más eficiente que permite enviar cualquier carácter ASCII con 1 byte, la mayoría de caracteres de la europa occidental con 2 bytes, los ideogramas asiáticos con 3 y el resto de la tabla Unicode con 4, haciendo que el cociente bytes envíados / caracteres envíados sea menor.

La palabra España se codifica con 7 bytes:

E  s  p  a  ñ     a
45 73 70 61 C3 B1 61

Observa algunos ejemplos de codificación en la Wikipedia, donde los tramos pertenecientes al code point han sido coloreados.

Cadenas de caracteres y Python

Históricamente, las cadenas de caracteres se referían a cadenas de caracteres ASCII. Este era el caso de Python 2 también. Si queríamos utilizar "cadenas de caracteres unicode" o "cadenas de texto", teníamos que usar el tipo especial unicode que podía escribirse usando literales de cadena precedidos de una u:

u'¡Esto sí que es una cadena de texto Unicode en Python 2! 

Sin embargo, en Python 3, las cadenas de caracteres pasan a ser cadenas de caracteres Unicode y el tipo str se convierte en lo que fuera unicode. El viejo tipo str de Python 2 queda reemplazado por el tipo bytes en Python 3 y debe indicarse explícitamente añadiendo una b antes de un literal de cadena. Sólo los caracteres ASCII son permitidos dentro de una cadena de bytes:

b'A pure-ASCII string. This is just a convenient way of dealing with numbers.'
  1. Ejecuta el siguiente fragmento:

    text = 'España'
    type(text)
    len(text)

    ¿Son los valores que esparabas?

  2. Encuentra en codepoints.net los códigos (en decimal) para cada una de las letras de "España" y compáralos con los elementos de la siguiente lista:

    codepoints = [ord(char) for char in text]

    Trata de convertir la lista en una lista de cadenas con la representación hexadecimal.

    ¿Cuál es la longitud de esa lista? ¿Es la que esperabas?

  3. Codifica ese texto utilizando latin1, utf8 y ascii como codificación:

    in_latin1 = text.encode('latin1')
    in_utf8 = text.encode('utf8')
    in_ascii = text.encode('ascii')

    ¿Por qué el error?

    ¿Cuál es el tipo (type) de in_latin1 y de in_utf8? ¿Y la longitud (len)?

  4. Ejecuta el siguiente código para extraer los bytes (en decimal) de cada cadena:

    bytes_latin1 = list(in_latin1)
    bytes_utf8 = list(in_utf8)

    ¿Compara las listas? ¿En qué se parecen y en qué se diferencian?

  5. Partiendo de ellas, ¿puedes reconstruir el texto "España"?

  6. ¿Qué ocurre si intercambias las codificaciones (es decir, si tratas de interpretar la secuencia de bytes en UTF-8 como Latin 1 y viceversa)?


Recuerda, codificar es pasar de texto a bytes y decodificar es pasar de bytes a texto. En ambos se necesita una codificación. Puedes crear tu propia codificación o usar una de las codificaciones que vienen con Python.

Comparativa str y bytes

Característica bytes str
En Python 2 se llamaba... str unicode
Colección de... bytes caracteres Unicode
len() número de bytes número de caracteres unicode
Convertir a bytes - .encode(encoding)
Covertir a str .decode(encoding) -
Obtener codepoint - ord(text[index])

Las APIs de los tipos str y bytes son muy parecidas. Compáralas:

  1. Obten todos los métodos de los tipos str y bytes con dir:

    str_methods = dir(str)
    bytes_methods = dir(bytes)
  2. Filtra estas listas para ignorar los métodos mágicos que comienzan y terminan con doble guión-bajo __.

  3. Convierte las colecciones resultantes en conjuntos y usa las operaciones entre conjuntos para obtener los métodos comunes, los exclusivos del tipo str y los exclusivos del tipo bytes.