Una guía para principiantes sobre Unicode en Python

Una vez pasé un par de días frustrantes en el trabajo aprendiendo cómo manejar adecuadamente las cadenas Unicode en Python. Durante esos dos días, comí muchos bocadillos, aproximadamente una bolsa de peces de colores por cada uno de estos errores encontrados, que deberían ser demasiado familiares para aquellos que programan con Python:

UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xf0 in position 0: ordinal not in range(128)

Mientras resolvía mi problema, busqué mucho en Google, lo que me indicó algunos artículos indispensables. Pero a pesar de lo grandiosos que son, todos fueron escritos sin la ayuda de un aspecto crucial de la comunicación en la época actual.

Es decir: todos fueron escritos sin la ayuda de emoji.

Entonces, para aprovechar esta situación, decidí escribir mi propia guía para comprender Unicode, con muchas caras e íconos renderizados a lo largo del camino.

Antes de sumergirnos en los detalles técnicos, comencemos con una pregunta divertida. ¿Cuál es tu emoji favorito?

El mío es el "rostro con la boca abierta", ¿que se ve así? - con una salvedad importante. ¡Lo que ves depende de la plataforma que estés usando para leer esta publicación!

Visto en mi Mac, el emoji parece una bola de boliche amarilla. En mi tablet Samsung, los ojos son negros y circulares, acentuados por un punto blanco que delata una mayor profundidad de emoción.

Copia y pega el emoji (?) En Twitter y verás algo completamente diferente. Sin embargo, cópielo y péguelo en messenger.com y verá por qué es mi favorito.

???? ¿Por qué son todos diferentes?

Nota: A partir del 9 de julio de 2018: Messenger parece haber actualizado sus íconos emoji, por lo que el ícono en la parte superior derecha ya no se aplica. ?

Este pequeño misterio divertido es nuestro paso al mundo de Unicode, ya que los emojis han sido parte del estándar Unicode desde 2010. Además de darnos emoji, Unicode es importante porque es la opción preferida de Internet para la codificación, representación y manejo de texto ”.

Unicode y codificación: una breve introducción

Al igual que con muchos temas, la mejor manera de entender Unicode es conocer el contexto que rodea su creación, y para eso, es necesario leer el artículo de Joel Spolsky.

Puntos de código

Dado que ahora hemos entrado en el mundo de Unicode, primero debemos disociar los emojis de los iconos maravillosamente expresivos que son y asociarlos con algo mucho menos emocionante. Entonces, en lugar de pensar en los emojis en términos de las cosas o las emociones que representan, pensaremos en cada emoji como un número simple. Este número se conoce como punto de código .

Los puntos de código son el concepto clave de Unicode, que fue "diseñado para soportar el intercambio, procesamiento y visualización mundial de los textos escritos de los diversos idiomas ... del mundo moderno". Lo hace asociando prácticamente todos los caracteres imprimibles con un punto de código único. Juntos, estos caracteres componen el juego de caracteres Unicode .

Los puntos de código generalmente se escriben en hexadecimal y tienen el prefijo U+para indicar la conexión a Unicode, que representa caracteres de:

  • lenguas exóticas como el telugu [ఋ | punto de código: U + 0C0B]
  • símbolos de ajedrez [♖ | punto de código: U + 2656]
  • y, por supuesto, emojis [? | punto de código: U + 1F64C]

Los glifos son lo que ves

La representación real en pantalla de los puntos de código se llama glifos ,mapeo completode puntos de código a glifos se conoce como fuente ) .

Como ejemplo , tome esta letra A, que es un punto de código U+0041en Unicode. La "A" que ves con tus ojos es un glifo; se ve así porque está renderizada con la fuente de Medium. Si cambiara la fuente a Times New Roman, por ejemplo, solo cambiaría el glifo de "A", pero no el punto de código subyacente.

Los glifos son la respuesta a nuestro pequeño misterio de renderizado. Bajo el capó, todas las variaciones de la cara con emoji de boca abierta apuntan al mismo punto de código U+1F62E, pero el glifo que lo representa varía según la plataforma.

Los puntos de código son abstracciones

Debido a que no dicen nada sobre cómo se representan visualmente (que requieren una fuente y un glifo para "darles vida"), se dice que los puntos de código son una abstracción.

Pero así como los puntos de código son una abstracción para los usuarios finales, también son abstracciones para las computadoras. Esto se debe a que los puntos de código requieren una codificación de caracteres para convertirlos en lo único que las computadoras pueden interpretar: bytes. Una vez convertidos a bytes, los puntos de código se pueden guardar en archivos o enviar a través de la red a otra computadora? ➡️ ?.

UTF-8 es actualmente la codificación de caracteres más popular del mundo. UTF-8 usa un conjunto de reglas para convertir un punto de código en una secuencia única de (1 a 4) bytes, y viceversa. Se dice que los puntos de código se codifican en una secuencia de bytes y las secuencias de bytes se decodifican en puntos de código. Esta publicación de Stack Overflow explica cómo funciona el algoritmo de codificación UTF-8.

Sin embargo, aunque UTF-8 es la codificación de caracteres predominante en el mundo, está lejos de ser la única. Por ejemplo, UTF-16 es una codificación de caracteres alternativa del juego de caracteres Unicode. La siguiente imagen compara las codificaciones UTF-8 y UTF-16 de nuestro emoji ?.

Los problemas surgen cuando una computadora codifica puntos de código en bytes con una codificación, y otra computadora (u otro proceso en la misma computadora) decodifica esos bytes con otra.

Afortunadamente, UTF-8 es lo suficientemente ubicuo como para que, en su mayor parte, no tengamos que preocuparnos por codificaciones de caracteres no coincidentes. Pero cuando ocurren, se requiere familiaridad con los conceptos mencionados anteriormente para salir del lío.

Breve resumen

  • Unicode es una colección de puntos de código , que son números simples generalmente escritos en hexadecimal y con el prefijo U+. Estos puntos de código se asignan a prácticamente todos los caracteres imprimibles de los lenguajes escritos de todo el mundo.
  • Los glifos son la manifestación física de un personaje. Este chico ? es un glifo. A f ONT es una asignación de puntos de código para glifos.
  • Para enviarlos a través de la red o guardarlos en un archivo, los caracteres y sus puntos de código subyacentes deben codificarse en bytes. Una codificación de caracteres contiene los detalles de cómo se incrusta un punto de código en una secuencia de bytes.
  • UTF-8 es actualmente la codificación de caracteres más popular del mundo. Dado un punto de código, UTF-8 lo codifica en una secuencia de bytes. Dada una secuencia de bytes, UTF-8 la decodifica en un punto de código.

Un ejemplo practico

La representación correcta de caracteres Unicode implica atravesar una cadena, desde bytes hasta puntos de código y glifos.

Usemos ahora un editor de texto para ver un ejemplo práctico de esta cadena, así como los tipos de problemas que pueden surgir cuando las cosas salen mal. Los editores de texto son perfectos, porque involucran las tres partes de la cadena de renderizado que se muestra arriba.

Nota: El siguiente ejemplo se realizó en mi MacOS usando Sublime Text 3. Y para dar crédito a quien se debe el crédito: el comienzo de este ejemplo está fuertemente inspirado en esta publicación de Philip Guo, que me presentó el hexdumpcomando (y mucho más).

Comenzaremos con un archivo de texto que contiene un solo carácter: mi emoji favorito de "cara con la boca abierta". Para aquellos que quieran seguir adelante, he alojado este archivo en una esencia de Github, que se obtiene localmente curl.

curl //gist.githubusercontent.com/jzhang621/d7d9eb167f25084420049cb47510c971/raw/e35f9669785d83db864f9d6b21faf03d9e51608d/emoji.txt > emoji.txt

Como aprendimos, para que se guarde en un archivo, el emoji se codificó en bytes usando una codificación de caracteres. Este archivo en particular fue codificado usando UTF-8, y podemos usar el hexdumpcomando para examinar el contenido real de bytes del archivo.

j|encoding: hexdump emoji.txt0000000 f0 9f 98 ae 0000004

The output of hexdump tells us the file contains 4 bytes total, each of which is written in hexadecimal. The actual byte sequence f0 9f 98 ae matches the expected UTF-8 encoded byte sequence, as shown below.

Now, let’s open our file in Sublime Text, where we should see our single ? character. Since we see the expected glyph, we can assume Sublime Text used the correct character encoding to decode those bytes into code points. Let’s confirm by opening up the console View -> Show Console, and inspecting the view object that Sublime Text exposes as part of its Python API.

>>> view
# returns the encoding currently associated with the file>>> view.encoding()'UTF-8'

With a bit of Python knowledge, we can also find the Unicode code point associated with our emoji:

# Returns the character at the given position>>> view.substr(0)'?' 
# ord returns an integer representing the Unicode code point of the character (docs)>>> ord(view.substr(0))128558
# convert code point to hexadecimal, and format with U+>>> print('U+%x' % ord(view.substr(0)))U+1f62e

Again, just as we expected. This illustrates a full traversal of the Unicode rendering chain, which involved:

  • reading the file as a sequence of UTF-8 encoded bytes.
  • decoding the bytes into a Unicode code point.
  • rendering the glyph associated with the code point.

So far, so good ?.

Different Bytes, Same Emoji

Aside from being my favorite text editor, I chose Sublime Text for this example because it allows for easy experimentation with character encodings.

We can now save the file using a different character encoding. To do so, click File -> Save with Encoding -> UTF-16 BE. (Very briefly, UTF-16 is an alternative character encoding of the Unicode character set. Instead of encoding the most common characters using one byte, like UTF-8, UTF-16 encodes every point from 1–65536 using two bytes. Code points greater than 65536, like our emoji, are encoded using surrogate pairs. The BE stands for Big Endian).

When we use hexdump to inspect the file again, we see that byte contents have changed.

# (before: UTF-8)j|encoding: hexdump emoji.txt0000000 f0 9f 98 ae 0000004
# (after: UTF-16 BE)j|encoding: hexdump emoji.txt0000000 d8 3d de 2e0000004

Back in Sublime Text, we still see the same ? character staring at us. Saving the file with a different character encoding might have changed the actual contents of the file, but it also updated Sublime Text’s internal representation of how to interpret those bytes. We can confirm by firing up the console again.

>>> view.encoding()'UTF-16 BE'

From here on up, everything else is the same.

>>> view.substr(0)'?' 
>>> ord(view.substr(0))128558
>>> print('U+%x' % ord(view.substr(0)))U+1f62e

The bytes may have changed, but the code point did not — and the emoji remains the same.

Same Bytes, But What The đŸ˜®

Time for some encoding “fun”. First, let’s re-encode our file using UTF-8, because it makes for a better example.

Let’s now go ahead use Sublime Text to re-open an existing file using a different character encoding. Under File -> Reopen with Encoding, click Vietnamese (Windows 1258), which turns our emoji character into the following four nonsensical characters: đŸ˜®.

When we click “Reopen with Encoding”, we aren’t changing the actual byte contents of the file, but rather, the way Sublime Text interprets those bytes. Hexdump confirms the bytes are the same:

j|encoding: hexdump emoji.txt0000000 f0 9f 98 ae0000004

To understand why we see these nonsensical characters, we need to consult the Windows-1258 code page, which is a mapping of bytes to a Vietnamese language character set. (Think of a code page as the table produced by a character encoding). As this code page contains a character set with less than 255 characters, each character’s code points can be expressed as a decimal number between 0 and 255, which in turn can all be encoded using 1 byte.

Because our single ? emoji requires 4 bytes to encode using UTF-8, we now see 4 characters when we interpret the file with the Windows-1258 encoding.

A wrong choice of character encoding has a direct impact on what we can see and comprehend by garbling characters into an incomprehensible mess.

Now, onto the “fun” part, which I include to add some color to Unicode and why it exists. Before Unicode, there were many different code pages such as Windows-1258 in existence, each with a different way of mapping 1 byte’s worth of data into 255 characters. Unicode was created in order to incorporate all the different characters of the all the different code pages into one system. In other words, Unicode is a superset of Windows-1258, and each character in the Windows-1258 code page has a Unicode counterpart.

In fact, these Unicode counterparts are what allows Sublime Text to convert between different character encodings with a click of a button. Internally, Sublime Text still represents each of our “Windows-1258 decoded” characters as a Unicode code point, as we see below when we fire up the console:

>>> view.encoding()'Vietnamese (Windows 1258)'
# Python 3 strings are "immutable sequences of Unicode code points">>> type(view.substr(0))
>>> view.substr(0)'đ'>>> view.substr(1)'Ÿ'>>> view.substr(2)'˜'>>> view.substr(3)'®'
>>> ['U+%04x' % ord(view.substr(x)) for x in range(0, 4)]['U+0111', 'U+0178', 'U+02dc', 'U+00ae']

This means that we can re-save our 4 nonsensical characters using UTF-8. I’ll leave this one up to you — if you do so, and can correctly predict the resulting hexdump of the file, then you’ve successfully understood the key concepts behind Unicode, code points, and character encodings. (Use this UTF-8 code page. Answer can be found at the very end of this article. ).

Wrapping up

Working effectively with Unicode involves always knowing what level of the rendering chain you are operating on. It means always asking yourself: what do I have? Under the hood, glyphs are nothing but code points. If you are working with code points, know that those code points must be encoded into bytes with a character encoding. If you have a sequence of bytes representing text, know that those bytes are meaningless without knowing the character encoding that was used create those bytes.

As with any computer science topic, the best way to learn about Unicode is to experiment. Enter characters, play with character encodings, and make predictions that you verify using hexdump. While I hope this article explains everything you need to know about Unicode, I will be more than happy if it merely sets you up to run your own experiments.

Thanks for reading! ?

Answer:

j|encoding: $ hexdump emoji.txt0000000 c4 91 c5 b8 cb 9c c2 ae0000008