Novedades en Django 1.5

Hace aproximadamente un año desde la última versión de Django, y desde entonces muchas cosas han cambiado. La versión 1.5 fue lanzada hace apenas un par de días y viene cargada de novedades (también podéis ver los artículos sobre las versiones versión 1.3 y versión 1.4).

Soporte para Python 3

Por fin Django es compatible con Python 3... aunque de forma no estable aún. Tras un esfuerzo muy grande por parte de todos los desarrolladores se ha conseguido portar todo el código de Django de tal manera que el mismo código base sea compatible al mismo tiempo con Python 2 y Python 3.

Para ello han hecho uso de la librería de python six, la cual se encarga de proporcionar una API que salva las diferencias que pueda haber entre ambas versiones.

Django ha creado una guía muy útil en su wiki sobre como portar a Python 3 aplicaciones para Django. A quien haga webs para cliente final usualmente no le va a interesar (una vez empiezas una web decides para que versión de Python hacerla; normalmente no interesa hacerla compatible para las dos), pero en el caso de desarrolladores de módulos de Django aconsejo la lectura del enlace anterior, ya que pasará mucho tiempo hasta que Django deje de soportar Python 2, y hasta que llegue ese momento, las aplicaciones deberán ser compatibles con ambas versiones.

La situación actual queda entonces de la siguiente manera:

  • En la rama de Python 2 se soporta como mínimo la versión 2.6.5, aunque se aconseja fervientemente usar Python 2.7.3 o superiores. La compatibilidad con estas versiones quedará ya así posiblemente hasta que Django decida dejar de soportar Python 2 oficialmente (para lo que queda aún mucho tiempo).
  • Sobre Python 3 se soporta como mínimo la versión 3.2.0, aunque es de esperar que esta versión mínima vaya aumentando, como lo ha hecho en los últimos años con la de Python 2.

Por último, para los que no sean valientes, Django ofrecerá una versión estable y plenamente compatible con Python 3 en su próxima versión: Django 1.6

Modelos de usuario configurables

Este es el gran cambio de Django 1.5, uno de esos que se llevaban esperando años. Tras mucha discusión en la lista de desarrollo, para esta versión han llegado a un acuerdo sobre como ofrecer modelos de usuario configurables.

Hasta ahora, en django.contrib.auth.models teníamos el modelo User, que es el que se usaba para la gestión de usuarios. El problema de este modelo es que es muy poco configurable. Ofrece campos que no son siempre útiles (hay muchas webs que no necesitan el campo username, usando el email para identificar a la persona) y es muy difícil de extender, ya que aunque pudieses heredar de ese modelo, era una tarea complicada el hacer que el resto del portal, con el admin de Django incluído, se integrase correctamente con ese nuevo modelo.

En Django 1.5 se añade una nueva directiva de configuración llamada AUTH_USER_MODEL que especifica que modelo de usuario se quiere usar.

Para que las aplicaciones sean compatibles con ese posible cambio, a partir de este momento, en todos los modelos donde se quiera crear una ForeignKey al usuario, no se deberá usar el User de django normal, si no que se tendrá que crear la relación sobre la directiva antes mencionada.

# settings.py
AUTH_USER_MODEL = 'usuarios.Usuario'

# blog/models.py
from django.conf import settings

class Articulo(models.Model):
    autor = models.ForeignKey(settings.AUTH_USER_MODEL)

Como veis en el settings.py definimos cual será nuestro modelo de usuario (el formato es app.modelo), y a partir de ahí, en las relaciones que tengan nuestros modelos con los usuarios, usamos ese nuevo setting.

Por último quedaría la parte más importante: definir el nuevo modelo de usuario. Para ello Django nos facilita la tarea ofreciéndonos varios modelos base de los que poder hederar (entre los que tenemos AbstractBaseUser). La base obligatoria que Django necesita para que el nuevo modelo de usuario sea válido son:

  • Tiene que tener una clave primaria
  • También debe tener un campo que sea único con el que poder identificar al usuario (un nombre de usuario, email, ...)
  • Por último debes poder referirte al usuario de una manera corta y de otra larga (nombre y apellidos, y nombre de usuario por ejemplo).
from django.contrib.auth.models import AbstractBaseUser

# usuarios/models.py
class Usuario(AbstractBaseUser):
    email = models.EmailField(max_length=254, unique=True)
    cif = models.CharField(max_length=20)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['cif']

    def get_full_name(self):
        return self.email

    def get_short_name(self):
        return self.email

Como podemos ver en el ejemplo anterior, creamos un nuevo modelo de usuario heredando desde la clase AbstractBaseUser. En este modelo definimos un campo de email y un CIF. A continuación usamos la variable USERNAME_FIELD para indicarle a Django cual es el campo que será usado como campo único (para cumplir el segundo requisito) y cuales son los campos que son obligatorios de introducir para crear un usuario (el campo definido en USERNAME_FIELD pasa a ser requerido automáticamente).

Por último, definimos los métodos get_full_name y get_short_name para cumplir el tercer requisito, indicándole como obtener el nombre corto y largo del usuario (que en nuestro caso no es muy imaginativo: devolvemos siempre el email).

Y con esto quedarían satisfechas las tres condiciones, ya que el campo que define una clave primaria (primer requisito) se crea automáticamente. Ya solo nos quedaría crearle un Manager al modelo, para que Django sepa como crear usuarios y super-usuarios.

Recomiendo la lectura de la wiki de Django sobre como crear modelos de usuario ya que explican perfectamente como crear modelos y managers que sean válidos. Espero escribir un artículo más detallado sobre el tema en algún momento, ya que da para un artículo entero :)

Guardando subsets de un modelo

Hasta ahora si hacíamos una operación save sobre una instancia de modelo, la sentencia SQL que se generaba contra la base de datos incluía todos los campos del propio modelo, y no solo los que hubiesen cambiado.

Esto, que puede parecer una tontería, es un problema en caso de que el modelo tenga muchos campos, o que algunos de ellos tengan mucho contenido. Aparte, podía provocar problemas de concurrencia, en los que varios usuarios se pisan sus cambios sobre el mismo objeto aunque ni siquiera hubiesen cambiado los mismos campos.

Para resolver esta circunstancia Django ha añadido un nuevo parámetro a la llamada save en el que se puede especificar que campos en concreto se quieren guardar, tal que la consulta SQL de UPDATE incluirá únicamente esos.

def mi_vista(request):
    user = request.user
    user.username = 'Paco'
    user.save(update_fields=['username'])

Como podéis ver en el ejemplo anterior, tras cambiarle el nombre al usuario que ejecuta la vista, se realiza un save en el que se especifica mediante el parámetro update_fields que solo queremos guardar ese campo.

Como nota adicional, los métodos defered y only (los cuales fueron explicados en las novedades de Django 1.4), pasan a usar este nuevo sistema, por lo que solo guardarán los campos que realmente se hayan bajado de la BD.

Mejora en el cacheo de modelos

El ORM de Django pasa a ser un poco más listo en esta nueva versión.

Hasta ahora, a la hora de atravesar relaciones (ForeignKey o ManyToMany), Django no era capaz de detectar cuando un modelo había sido ya cogido de la base de datos, por lo que podía llegar a hacer más de una consulta para traerse exactamente el mismo objeto.

def mi_vista(request, pk):
    noticia = Noticia.objects.get(pk=pk)
    comentario = noticia.comentario_set.all()[0]

    assert comentario.noticia == noticia

Hasta ahora el código anterior generaba una consulta en la linea número 3 para coger el comentario, y otra en la línea número 5 para volver a obtener la noticia, a pesar de que la noticia ya la tenemos ya que estamos obteniendo el comentario a través de ella. Pues Django 1.5 corrige este comportamiento, generando una única consulta entre la línea en la que se coge el comentario, y la línea del assert).

Se añade un nuevo response para streaming

En una página web normalmente las respuestas que da el servidor son de contenido finito y tamaño conocido, tal que por ejemplo, en el caso más básico, se renderiza un template con unos datos y se manda al navegador web.

Este caso no tiene por qué ser siempre así, siendo posible también querer devolver una respuesta de tipo streaming, en la que el contenido de la respuesta no tiene un tamaño delimitado, si no que se va enviando poco a poco (por ejemplo, para hacer streaming de audio o vídeo).

Hasta Django 1.5, la única forma de devolver este tipo de response era devolviendo un iterador a través de un HttpResponse normal y corriente (el cual iría consumiendo el iterador hasta que se quedase sin contenido). Este sistema tenía muchos inconvenientes, como que por ejemplo un Middleware podía consumir el iterador antes de que este llegase a salir hacia el cliente.

Para resolverlo, a partir de ahora se añade una nueva clase llamada StreamingHttpResponse, especializada en este tipo de responses

Otros cambios menores

Algunos cambios menores que se han añadido que merecen la pena señalar son:

  • Se ha añadido un nuevo templatetag, llamado verbatim, que tiene como objetivo el desactivar el parser de Django en todo el código que haya en su interior. Esto es útil para escribir templates con ciertas librerías de JS que tenían como tag el mismo que Django (las dos llaves {{}}) y que por lo tanto provocaban conflictos con Django.
  • Se ha mejorado la documentación de Django, estando ahora mucho mejor organizada. Aparte, se ha actualizado el tutorial de introducción a Django, siendo ahora más completo.
  • Todos los mensajes de logging se imprimirán por la consola cuando la variable DEBUG esté habilitada.
  • Django pasa a proveer templates 404.html y 500.html por lo que deja de ser obligatorio proporcionarlos (evitando generar errores 500 en producción por no existir dichos templates por ejemplo).

Y estos son algunos de los cambios que traen esta nueva versión. La lista completa en inglés la podéis consultar en el siguiente enlace, en la web oficial de Django.

Novedades en Django 1.4

Y el desarrollo de Django sigue a buen ritmo. La versión 1.4 de este framework fue lanzado hace un par de semanas, y al igual que hice con la versión 1.3, vamos a repasar las novedades que presenta esta nueva versión.

Python 2 vs Python 3

Django tiene una política de soporte de las versiones de Python muy estricto. Hasta ahora tenían soporte de toda la rama v2 de Python desde la versión 2.4 Ahora, acaban de anunciar que dejan de soportar la versión 2.4 (siendo oficialmente soportadas sólo la 2.5, 2.6 y 2.7).

La idea es poder soportar en un futuro Python 3 sin dejar de soportar Python 2, y para ello, se simplifican mucho las cosas si de Python 2 solo hay que soportar desde la v2.5 para arriba (debido a que hay muchos backports de características de Python 3 a partir de esa versión).

En cuanto a la versión que soporte Python 3, aún no es oficial, pero ya hay una rama con el desarrollo bastante avanzado. Se plantea que para lo que sería Django 1.5 o 1.6 se podrá incluir el port.

Estrenando nuevo layout

En un movimiento bastante sorprendente el layout de los proyectos de Django ha cambiado totalmente. Hasta ahora el layout era el siguiente:

mi_proyecto/
    __init__.py
    manage.py
    settings.py
    urls.py
    mi_app/
        __init__.py
        models.py
        ...

A partir de ahora, el layout de los proyectos será el siguiente:

manage.py
mi_proyecto/
    __init__.py
    settings.py
    urls.py
    mi_app/
        __init__.py
        models.py
        ...

Como podéis ver el manage.py pasa a estar en el mismo nivel que la carpeta de nuestro aplicación. Esto arregla una serie de problemas que ocasionaban que se importase más de una vez el mismo módulo, así como unifica la manera de importar módulos en Django.

A partir de ahora, en los ficheros generales del proyecto (en el settings.py por ejemplo) se deberá anteponer el nombre del proyecto al módulo en cuestión (mi_proyecto.urls para importar el urls.py). Los módulos internos a las aplicaciones se seguirán importando como hasta ahora, incluyendo el nombre de la aplicación (mi_app.models para importar el models.py de mi_app).

Este nuevo layout pasará a ser el oficial (y único válido) a partir de la versión 1.6 de Django. En la 1.5 el layout antiguo simplemente lanzará un DeprecationWarning.

Soporte para zonas temporales

Hasta ahora Django usaba valores temporales absolutos en todo el framework. Esto ocasionaba problemas, o directamente carecía de la potencia necesaria para gestionar el hecho de que los usuarios de una web vienen de distinto países, con distintas zonas temporales.

Para solucionar esto, Django usa desde ahora valores temporales relativos, en los que acompaña a cada valor con el huso horario en el que ha sido generado (valores aware). Toda la información guardada en la base de datos, por un campo DateTimeField por ejemplo, se pone en formato UTC, de esta manera pueden transformarse a la zona horaria que esté usando el usuario que visita la página web.

Los cambios que hay que hacer para usar esta nueva API son pocos, ya que Django realiza todos los cambios de manera interna... pero alguno hay, así que veámoslos.

Para empezar para que Django sepa extraer la hora UTC de los valores aportados por los usuarios tendrá que saber en que zona horaria está el susodicho. Para ello intentará primero averiguar si hay alguna zona activada, o en caso contrario, usará la configurada en el settings.py.

Para activar alguna bastará con usar un shorcut proporcionado por Django (dentro de django.utils). Y para saber que zona activar... pues no hay más opción que preguntarle al usuario (típico formulario de en que zona horaria está situado). Una vez obtenida esa información, bastará con activar la zona mientras el usuario esté navegando por el portal (usando un middleware por ejemplo, y guardando el valor de la zona horario en la sesión del usuario).

from django.utils import timezone

class TimezoneMiddleware(object):
    def process_request(self, request):
        tz = request.session.get('django_timezone')
        if tz:
            timezone.activate(tz)

Una vez hecho esto todas las fechas que sean aware aparecerán adaptados al huso horario que tenga activado ese usuario.

Este es el único cambio realmente necesario. Aparte, y solo en los casos en los que usemos nosotros valores absolutos (por ejemplo en el default de un campo de un modelo, si usamos datetime.now) tendremos que cambiarlos para usar valores relativos.

Para esto Django nos proporciona un nuevo shorcut

from django.utils.timezone import now

class Post(models.Model):
    titulo = models.CharField(max_length=50)
    fecha = models.DateTimeField(default=now())

Y así de simple es. Django mostrará por la salida estándar Warnings en caso de que vea que se usa algún valor absoluto donde no se debe.

Nuevo sistema criptográfico

Django ha añadido en esta nueva versión un sistema criptográfico que permite la firma criptográfica de textos (realmente no solo textos, también se pueden firmar estructuras complejas como diccionarios, tuplas...).

Al firmar criptográficamente cierto texto conseguimos lo siguiente:

  • Asegurar que nosotros hemos generado ese texto.
  • Comprobar que no ha sido alterado de ninguna manera.

Esto puede ser muy útil como dicen en la página oficial de Django por ejemplo para generar tokens (como los usados al activar cuentas de usuarios, recuperar contraseñas, ...).

El uso de esta API es muy sencilla.

>>> from django.core.signing import Signer
>>> signer = Signer()
>>> signer.sign('Este texto esta firmado')
'Este texto esta firmado:QXw4TMUiuoRyqKxxLuUqQUGYyDk'
>>> signer.unsign('Este texto esta firmado:QXw4TMUiuoRyqKxxLuUqQUGYyDk')
u'Este texto esta firmado'

Como podemos ver, concatena el texto inicial a la firma. Para realizar la operación usa el valor SECRET_KEY del settings.py por lo que tened mucho cuidado en que no caiga en malas manos ya que en ese caso perdería toda su utilidad.

Como detalles adicionales se puede realizar la firma mediante el uso de un valor salt adicional, para que el mismo texto no genere siempre la misma firma. Y por último, hay una clase adicional llamada TimestampSigner que añade información temporal a la firma.

Mejoras en seguridad

Se ha mejorad la seguridad de Django en varios aspectos.

Para empezar, se ha dejado de usar SHA1 en las contraseñas de los usuarios debido al problema de seguridad que fue encontrado en él hace unos meses. A partir de ahora se usará un algoritmo llamado PBKDF2.

Este cambio es totalmente compatible hacia atrás. Los viejos usuarios seguirán usando el mismo esquema que antes, los que se registren a partir de ahora en vuestras webs, en cambio, usarán el nuevo.

Por otra parte, se ha añadido un nuevo middleware en Django encargado de proteger contra un tipo de ataque llamado ClickJacking. Este ataque se basa en situar un iframe invisible delante de la web para conseguir que el usuario haga click en un enlace sin que sea realmente consciente de ello.

Para solucionarlo, se usa la cabecera X-Frame-Options, que puede evitar que la web sea cargada en un iframe.

Esta nueva proteccción no viene activada predeterminadamente, por lo que para usarla hay que configurarla manualmente en el settings.py.

Otros cambios menores

Algunos cambios menores que se han añadido que merecen la pena señalar son:

  • Se añade la capacidad de crear tests específicos para baterías que usan navegadores web en vivo (como Selenium). Para ello se puede usar la nueva clase LiveServerTestCase.
  • Se añade un fichero al crear un nuevo proyeto, llamado uwsgi.py que ayuda a realizar el deploy de la aplicación mediante WSGI.
  • Se añade un nuevo método para crear muchísimos objetos de un módelo de forma rápida: bulk_create.
  • Se añade un método llamado reverse_lazy que sirve para obtener la URL de una página incluso cuando aún no ha sido cargado el urls.py. De esta manera podremos evitar repetir URLs en ficheros como el settings.py.
  • Se permite internacionalizar URLs.
  • Nuevos templatetags: static y truncatechars.
  • Y mucho, mucho más... ;)

Los que quieran leerse la lista entera (es bastante más amplia que lo expuesto aquí), puede consultar el siguiente enlace, en la web oficial de Django.

Juegos de Caracteres II: Unicode y UTF

Continuamos con nuestro viaje por el maravilloso mundo de las codificaciones de caracteres. Si has llegado aquí de primeras te aconsejo que antes leas la primera parte de este artículo.

Lo habíamos dejamos en un momento bastante caótico de la historia. Como dijimos, tenemos una infinidad de juegos de caracteres incompatibles entre sí, llamados ASCII Extendido, que usan el octavo bit de ASCII para añadir 128 caracteres más.

Así pues era necesaria una forma global de representar todos los posibles caracteres existentes. Y Unicode fue el encargado de acometer dicha tarea.

Unicode

Este estándar fue desarrollado por la UTC (no, no tiene nada que ver con los husos horarios) y tiene como particularidad que no es un juego de caracteres. Como vimos en el primer artículo, la primera tarea para representar los distintos caracteres es asignarles un código numérico. Pues esto es exactamente lo que hace Unicode, y nada más. Básicamente es el estándar encargado de asignarle un código numérico a cada uno de los distintos caracteres representables sobre la faz de la tierra (y a muchas chorradas más).

A la hora de referirnos a un carácter unicode vamos a usar el siguiente formato: U+XXXX, siendo las X el código numérico del respectivo carácter (en base hexadecimal). Por poner un ejemplo, el carácter e se representa mediante la secuencia U+0065. Por poner otro ejemplo, el carácter montón de mierda tiene el código unicode U+1F4A9 (sí, en efecto, hay un carácter que representa un montón de mierda, podéis comprobarlo en el siguiente enlace).

El lector avispado se habrá dado cuenta de que el carácter e usa el mismo código numérico en ASCII y en Unicode. Y es que esta es una de las grandes cualidades de Unicode, los primeros caracteres son los ASCII, por lo que igual que en el caso anterior, tenemos que este set, en sus primeros valores al menos, es compatible con el estándar de facto anterior.

Pero nos faltan muchos actores aún en la película. Como hemos dicho, Unicode no es un juego de caracteres, por lo que en el estándar no hay ninguna referencia a como se han de representar los caracteres de manera binaria (dentro de un ordenador). Y esta tarea no es tan sencilla como hasta ahora, debido a que Unicode contiene muchísimos más caracteres de los que caben en un byte, por lo que nuestra forma de codificarlos tendrá que tener en cuenta también la ordenación de bytes de la arquitectura (little-endian o big-endian). Y es aquí donde aparecen las tres codificaciones más usadas en la actualidad: UTF-8, UTF-16 y UTF-32. Vamos a ver como funcionan en orden inverso, ya que es la forma más simple de ver sus ventajas y desventajas. Y si me lo permitís, vamos a ignorar el tema de la ordenación de bytes hasta el final del artículo.

UTF-32

Esta es la variante más sencilla que hay. Los caracteres se guardan en 32bits (4Bytes), por lo que la correspondencia entre el código numérico Unicode y la representación binaria en esta codificación es directa. Es decir, el carácter Unicode e, que como vimos antes tiene el código U+00000065 (puedo añadir todos los ceros que quiera delante) se representará de la siguiente manera:

e
00000000 00000000 00000000 01100101

Sí, un sólo carácter ocupa toda esa cantidad de espacio. Si os acordáis del primer artículo, de hecho, podréis ver como cada carácter ocupará 4 veces más (1B contra 4B). Al hecho de que es un derroche de espacio sangrante hay que sumarle que en un gran porcentaje de los casos la información que circula es de los primeros 127 caracteres, es decir, de la tabla ASCII inicial, por lo que estaremos mandando bytes de ceros a mansalva, ya que los caracteres ASCII se codificarán siempre con los tres primeros bytes a cero, y el último con el código del carácter en cuestión.

Las mentes pensantes vieron que esto no podía ser (decidle a Google que tiene que gastar 4 veces más ancho de banda en servir sus páginas webs por ejemplo), por lo que idearon otras formas más óptimas de codificar Unicode. Decir que, por esta razón, muy pocas aplicaciones/sistemas usan esta codificación para representar Unicode.

UTF-16

Las cabezas pensantes de las que hablábamos antes se dieron cuenta de que la inmensa mayoría de la humanidad usa un subconjunto de lo que ellos codificaron en Unicode, y que este subconjunto estaba situado en la primera mitad de la tabla. A este subconjunto lo llamaron plano básico multilingüe (o BMP de sus siglas en inglés). El BMP puede representarse mediante 2Bytes, por lo que esta codificación usa de forma fija 2Bytes para poder representar todos los caracteres de este subconjunto (2Bytes = 16bits, ¿empezáis a sospechar que significa el número que acompaña a UTF? ;) ).

Es decir, si cogéis un carácter que esté desde el U+0000 al U+FFFF su representación binaria será exacta (tal como lo era en UTF-32 pero con 2Bytes de ceros menos, ya que ahora los caracteres ocupan la mitad). Por poner el mismo ejemplo de antes, ahora el carácter e se representaría con la siguiente secuencia:

e
00000000 01100101

Como podemos ver, es una mejora enorme con respecto a UTF-32. Pero, ¿y qué pasa con los caracteres más allá de U+FFFF? Pues que también se pueden codificar, pero no de forma directa. En este caso UTF-16 empleará 2 pares de 2Bytes (es decir, que ocupará exactamente lo mismo que en UTF-32). Mediante estos dos pares y una serie de transformaciones se podrán codificar estos bytes. La explicación de como lo hace queda fuera de este artículo, ya que es algo intrincada. Esta codificación si que es más usada en la actualidad. Por poner un ejemplo, Qt, el framework de programación en C++ usa UTF-16 en su clase QString.

Pero aun así este sistema sigue teniendo varios problemas. El primero, que seguimos mandando muchos ceros debido a que toda la tabla ASCII tiene el primer byte a cero siempre. El segundo, que el sistema para codificar bytes más allá del U+FFFF es bastante complicado. Tercero, que todos los documentos escritos hasta la fecha se dejarían de ver bien!! UTF-16 (y 32) no son compatibles con ASCII debido a que los caracteres ocupan un número distintos de bytes, a pesar de que el código numérico sea equivalente. Para solucionar estos problemas, tenemos nuestra última codificación.

UTF-8

Este sistema de codificación está realmente bien pensado. Es un sistema dinámico, en cuanto a que a diferencia de los dos anteriores, no ocupa una cantidad de bytes fijo. En su lugar, usa un formato predefinido a la hora de codificar los caracteres, y en base a este formato ese carácter ocupará más o menos, siendo el rango entre 1Byte y 6Bytes (¡sí! puede llegar a ocupar más que UTF-32 para los caracteres más lejanos de la tabla Unicode.)

|filename|images/0011-juegos-caracteres-ii/utf8.png

Este sistema funciona de manera muy simple. En base al código numérico unicode, el carácter ocupará uno o más bytes. Para indicar esto, el formato de los bytes será representativo de cuantos bytes ocupa. Esto que parece muy complicado es muy sencillo de ver con la tabla anterior . La primera columna indica el último código numérico unicode que entra en ese grupo, y las siguientes columnas indican como se codifica cada uno de los bytes UTF-8. Los valores numéricos (en verde) son fijos, y gracias a que lo son, dependiendo de como empiece el primer byte se puede saber cuantos bytes ocupa el carácter. Por otra parte, los valores que tienen una X (en rojo) se rellenan con el código numérico Unicode del carácter que queramos codificar.

Por si aún no ha quedado claro, veamos un ejemplo completo. Intentemos codificar el carácter € (euro) en UTF-8:

|filename|images/0011-juegos-caracteres-ii/euro.png

Para codificar el carácter, primero vemos el código numérico unicode. El carácter euro tiene el código unicode U+20AC, que está en el tercer grupo, por lo que ocupará un total de 3Bytes. Así que una vez colocado el formato del tercer grupo (3 octetos, el primero empieza por 1110, y los otros dos empiezan por 10) solo se deberá sustituir las X por el valor binario del código 20AC.

Este ejemplo era del tercer grupo, que ocupan 3Bytes, pero como podéis ver en la primera tabla, los caracteres que están entre U+0000 y U+007F se codifican con un solo byte, en el que el primer bit está puesto a 0. Pero si os fijáis bien, ¿qué caracteres son esos? ¡¡Efectivamente!! Son los de la tabla ASCII. Es decir, hemos conseguido que los caracteres de la tabla ASCII y los codificados en UTF-8 se vean exactamente igual (sus códigos numéricos son los mismos, y su representación binaria también). O hablando en plata: Un documento que solo contenga caracteres de la tabla ASCII es indistinguible si está codificado en ASCII o si lo está en UTF-8: son exactamente iguales a nivel binario.

A partir de aquí la historia no es tan bonita. Por ejemplo, nuestra querida ñ tiene el código numérico U+00F1. Como veis, este código está en el segundo conjunto de los mostrados anteriormente, por lo que ocupará 2B. Y ya deja de ser compatible con ISO-8859-1 (extensión de ASCII para Europa occidental, recordad), por lo que si abrimos con UTF-8 un documento en Español que fue codificado con ISO-8859-1, se nos mostrarán los tan temidos caracteres extraños.

Y ya si nos vamos a caracteres más altos en la tabla Unicode podemos ver como empiezan a ocupar bastante más, hasta poder llegar a la friolera de 6Bytes. Pero aun así, esta codificación ha demostrado ser la más óptima a la hora de ahorrar espacio. La mayor parte de la información que se codifica actualmente está contenida en la tabla ASCII o en las siguientes posiciones, por lo que en la mayor parte de los casos esta codificación siempre ocupará lo mismo, o menos que sus hermanas mayores.

Y sí, por las razones vistas anteriormente, esta es la codificación Unicode más usada en la actualidad.

¿Y la ordenación de bytes?

En toda esta explicación hemos ignorado totalmente la ordenación de bytes de la arquitectura del computador que codifica el texto. Y eso es porque la solución a este problema se resuelve de manera similar en todos los casos.

Es un sistema muy de andar por casa, al que han llamado BOM (nosotros y nuestras dichosas siglas), o dicho de otra manera: Byte Order Mark. Este consiste, simplemente, en añadir una firma al principio del documento que le indica al lector (sea el Word, vim o nuestro navegador web preferido) que está leyendo un texto codificado en UTF, y con que ordenamiento de bytes se lo va a encontrar.

Tan simple como eso. En el caso de UTF-16 y UTF-32 el BOM está compuesto por los bytes U+FEFF, por lo que si el lector se encuentra esos bytes al principio del documento, sabrá que se encuentra ante un texto escrito en esa codificación. Que esté en big-endian o little-endian lo podrá saber mirando el orden en el que está escrito el BOM ya que si está en big-endian leerá U+FEFF; mientras que si está en little-endian leerá U+FFFE.

En el caso de UTF-8 se desaconseja totalmente el uso de BOM por varias razones. Para empezar, porque al ser una codificación de formato fijo (aunque tenga ancho variable) no es necesario. Por último, porque como vimos anteriormente, el hecho de que un documento en ASCII y uno en UTF-8 puedan ser totalmente indistingibles (siempre que use solo los primeros 127 caracteres de la tabla ASCII) es una ventaja enorme en cuanto a retrocompatibilidad, ya que todos esos documentos se podrán seguir leyendo sin ningún tipo de cambio en el software. Si metiéramos un BOM al principio del documento, dejaría de ser binariamente igual a su vertiente en ASCII.

Conclusión

Y hasta aquí llega esta serie de artículos. Espero que os haya quedado bastante más claro las distintas formas de codificar documentos que existen, y los puntos fuertes y débiles de cada sistema.

El futuro sin lugar a dudas es Unicode, y UTF-8 se está llevando por méritos propios la copa a la codificación más usada. Es cierto que por ejemplo en nuestro idioma los textos ocupan más (a la hora de usar acentos o la ñ por ejemplo), pero el precio a pagar es muy pequeño (sobretodo comparado con UTF-16 y UTF-32) a cambio de las ventajas de poder usar en todo el mundo el mismo sistema de codificación de caracteres.

Juegos de Caracteres I: ASCII e ISO-8859

A poco que hayamos usado un ordenador para poco más que jugar al buscaminas nos habremos encontrado con algún fichero que al abrirlo tiene caracteres extraños que hacen del documento algo totalmente ininteligible.

Esto se lo podemos agradecer a los juegos de caracteres y a la historia que han tenido durante las últimas décadas. Vamos a hacer un pequeño repaso (dividido en dos artículos, podéis consultar el segundo aqui y a intentar explicar como funciona el maravilloso mundo de los juegos de caracteres.

¿Juegos de caracteres?

Toda la información que vemos en nuestro ordenador está guardada de forma binaria. Ristras de 0's y 1's que representan nuestra información. Las cadenas de caracteres no son una excepción. Hay muchas maneras de representar caracteres, siendo la más sencilla el identificar cada letra mediante un código numérico. Y vamos a considerar como carácter cada uno de los símbolos que existen en el alfabeto, incluidas mayúsculas y minúsculas. Es decir, el carácter a no es el mismo que el carácter A.

Y es el sistema operativo, mediante este código, el que sabe como dibujar el respectivo carácter… ¿Pero quien decide que código corresponde a cada uno? Y es con esta pregunta con la que empieza nuestro caos.

La informática tuvo sus orígenes en el mundo anglosajón, y por consiguiente los caracteres ingleses fueron los prioritarios a la hora de asignar estos valores numéricos a los distintos caracteres. Y así nació ASCII :)

ASCII

La mayor parte de los computadores direccionan a nivel de byte, y esto de la informática nació en una época en la que el almacenamiento era algo costoso y escaso por lo que la idea de que cada carácter ocupase sólo un byte era algo bastante razonable.

De esos 8 bits (8 bits == 1 Byte) decidieron usar 7 para representar todos los caracteres que ellos creían eran necesarios para cualquier persona. En este grupo nos encontramos aparte del alfabeto inglés, distintos símbolos de puntuación (interrogación, coma, exclamación…) y por último, caracteres de control no imprimibles como el salto de línea, el tabulador, o el valor NULL, el cual como muchos sabrán, tiene el valor 0x00 (los siete bits a cero). Podéis ver que valor tiene exactamente cada carácter en la Wikipedia.

Por ejemplo, y según lo que acabamos de ver, si escribimos en un fichero la cadena Aiba! (guardándolo en ASCII), y abrimos ese fichero con algún lector binario, veremos que el contenido es el siguiente:

01000001 01101001 01100010 01100001 00100001
A        i        b        a        !

Pero hemos dicho que se direcciona a nivel de byte así que… ¿qué pasa con el bit número 8? Pues que sobra, no les hizo falta, así que decidieron usarlo como bit de paridad para comprobar que no había corrupción en los datos (a la hora de mandar el carácter por la red por ejemplo).

Y todo fue muy bonito… hasta que al resto de la humanidad (de habla no inglesa) nos dio por probar aquello de los ordenadores, y vimos que, por ejemplo, no podíamos escribir nuestra preciosa ñ. El estándar ASCII no nos servía.

Pero allá donde hay un estándar… hay otros diecisiete :)

ISO-8859-1

Cuando se planteó representar caracteres de otros alfabetos se vio claramente que ese octavo bit de ASCII que no se usaba era una oportunidad fantástica para ampliar con 128 nuevos códigos el set ya existente.

Por poner un ejemplo que nos pilla cerca, uno de estos juegos que se crearon es el llamado ISO-8859-1, que es un set creado con el propósito de representar los carácteres de la mayor parte de los alfabetos derivados del latín que hay. De esta manera, en esos 128 carácteres extras tenemos letras de nuestro idioma, francés, griego… Es este set el que más se usa actualmente en este lado del charco. Por poner un ejemplo, la palabra Joroña se representa de la siguiente manera:

01001010 01101111 01110010 01101111 11110001 01100001
J        o        r        o        ñ        a

El problema de todo esto es que lo de usar el octavo bit de ASCII (fijaos en que la letra ñ es la única que tiene un 1 en el octavo bit en el ejemplo anterior) es algo que se le ocurrió a mucha gente al mismo tiempo, creándose una infinidad de juegos de caracteres que usaban los siete primeros bits de la misma forma que ASCII (por lo que eran compatible con este) y usaban el octavo para representar los caracteres que les interesaba. Todos estos sets se dicen que son ASCII extendido.

Y con esto de tener una infinidad de juegos distintos surge un enorme problema, y es que un texto escrito en un juego es totalmente incompatible y de hecho se puede ver de forma totalmente ininteligible en un ordenador que tuviese otro distinto. Por ejemplo, la misma sucesión de bits que en ISO-8859-1 nos da la palabra Joroña, en Windows-1253 (una codificación muy usada en Grecia) nos da la palabra Joroρa (atención, no es un carácter p normal)

01001010 01101111 01110010 01101111 11110001 01100001
J        o        r        o        ρ        a

Aquí tenemos el primer origen de los caracteres extraños en nuestros ficheros. Y por esta razón solo los vemos en los caracteres acentuados. Los normales, como son compartidos por ASCII normal, son representados de igual manera en todas las codificaciones existentes. Son los caracteres de ASCII extendido los que, si los vemos desde un set distinto al que fue creado, provocan que el texto se vea totalmente distinto.

[Como curiosidad, si veis por ahí ISO-8859-15, decir que es el mismo que el ISO-8859-1 con algunos cambios menores (como la inclusión del símbolo del euro).]

Así pues, estaba claro que esta situación había que arreglarla. Y un comité se puso manos a la obra, creando lo que ahora conocemos como Unicode, el Valhala de las codificaciones. La que las uniría a todas. Un set que promete incluir todos los posibles carácteres que existen en nuestro planeta, incluídos el Klingon o el Élfico (no, no es coña).

Pero esto, amigos, queda para la segunda parte de este artículo ;)

El nuevo estándar de C, C11

Este año que termina nos deja con la actualización de dos grandes lenguajes de programación: C++ (vimos sus cambios en un artículo de dos partes y C, que lanzó C11 hace apenas dos semanas, con no demasiadas novedades que tratan de estandarizar características que muchos compiladores incluyen ya desde hace tiempo, y añadir una serie de mejoras al manejo de threads o de operaciones atómicas, así como añadir más compatibilidad con C++ (y en específico con su nueva versión, C++11).

Veamos algunos de los cambios con más detenimiento...

Funciones sin retorno (_Noreturn)

Se añade un modificador nuevo que permite al compilador optimizar el código de una función en caso de que nosotros especifiquemos que nunca va a retornar al llamante.

#include <nostdreturn.h>

void noreturn crash() {
    printf("Uops! :D\n");
    exit(-1);
}

Podemos usarlo en funciones que típicamente tratan casos de error que finalizan el programa por ejemplo.

Aparte, se añade un nuevo fichero de cabeceras, nostdreturn.h, donde se define la palabra clave noreturn como alias de _Noreturn.

Eliminada la función gets

Se elimina finalmente la función gets de la librería de C.

Esta función lee desde la entrada estándar un número indeterminado de carácteres (hasta un fin de línea). El hecho de que no se pueda especificar el máximo provoca que sea muy sencillo provocar un desbordamiento del buffer donde se están guardando esos carácteres.

#include <stdio.h>
char *gets_s(char *s, rsize_t n);

Para evitar los enormes problemas de seguridad que esto conlleva, en este nuevo estándar se elimina la función (ya en C99 estaba des-recomendada), y se aconseja el uso de la nueva función gets_s, que posee la misma cabecera, añadiendo un parámetro en el que se especifica el número máximo de carácteres a leer (menos 1, para el \0).

Mayor soporte de cadenas unicode

Al igual que en C++, y de una forma compatible con él, se añaden nuevos tipos de datos y aliases para manejar cadenas unicode. Se da soporte a tres representaciones distintas de unicode: UTF-8, UTF-16, UTF-32.

Hasta ahora estos formatos se representaban usualmente mediante los tipos char, unsigned short y unsigned long. El problema es que el tamaño de esos tipos de datos es dependiente de la plataforma y el compilador, por lo que no se podía hacer código portable.

Este nuevo estándar (al igual que el de C++), crea dos nuevos tipos de datos, llamados char16_t y char32_t, creados con el objetivo de representar carácteres en UTF-16 y UTF-32 respectivamente. UTF-8 seguirá usando un char normal. Además, se permite crear literales en las tres representaciones mencionadas marcando la cadena de carácteres con uno de los siguientes tres prefijos: (u8, u, U).

Por último, se añaden nuevas funciones en la librería estándar (almacenadas en uchar.h) para realizar conversiones entre los distintos formatos.

Si quieres más información sobre esto puedes consultar la primera parte del artículo sobre C++11.

Aserciones estáticas (static_assert)

Al igual que C++11, esta nueva versión de C añade una nueva palabra clave, static_assert, que permite añadir aserciones en el código que se ejecutan a nivel de compilador.

Las aserciones se encargan de comprobar condiciones que tienen que cumplirse a la hora de ejecutar una sección de código. Lo que hace especiales a estas aserciones es que no se ejecutan en tiempo de ejecución (cuando el programa es ejecutado por el usuario), si no que son comprobaciones realizadas en la fase de compilación (en una fase tardía donde los tipos son conocidos).

static_assert(sizeof(void*) >= 8, "No eres de 64-bit :P");

Este tipo de aserciones completa a las dos existentes anteriormente. La que se ejecuta con el preprocesador, #error, y la que se ejecuta en tiempo de ejecución, assert().

Funciones con comprobación de fronteras

El título de esta sección queda un poco críptico (no sabía muy bien como traducirlo), pero no es nada nuevo. El nuevo estándar de C añade una serie de funciones a la parte de su librería estándar encargada de manejar cadenas de carácteres. Estas nuevas funciones, cuyos nombres terminan mediante el sufijo _s, se diferencian de sus hermanas por comprobar los límites de los arrays usados como buffers antes de realizar las operaciones pertinentes.

char* strcat(char *s1, const char *s2);
errno_t strcat_s(char *s1, size_t n, const char *s2);

Como puede verse, la diferencia entre strcat y strcat_s es el segundo parámetro, que indica el número máximo de elementos que puede albergar el buffer de destino. Gracias a ese parámetro la función puede hacer más comprobaciones en tiempo de ejecución, comprobando que no se sobrepase el límite de memoria que tiene asignado el buffer de destino, por ejemplo.

Al igual que para strcat, se han añadido versiones con comprobación de fronteras para otras tantas funciones de la librería estándar.

Estructuras anónimas

Tras ser durante mucho tiempo soportadas en C++ se añade a este nuevo estándar el soporte de estructuras y uniones anónimas.

Estas estructuras son idénticas a las normales, salvando que no tienen un nombre mediante las que ser identificadas. Son muy útiles, por ejemplo, para crear código menos verboso a la hora de incorporar estructuras anidadas.

struct Persona {
    char *nombre;
    union {
        long dni;
        char *pasaporte;
    }
}

struct Persona p1;
p1.dni = 7987271;

Como puede verse en el fragmento de código anterior, al ser el unión anónimo, para acceder a sus campos no es preciso anteponer ningún nombre, dejando un código más claro y legible.

Manejo de threads

Este es posiblemente el cambio más grande que nos da este nuevo estándar. Se ha añadido el soporte a threads en la librería estándar, sin necesidad de usar librerías externas como hasta ahora con, por ejemplo, los threads POSIX (pthread.h).

Se ha añadido un nuevo fichero llamado threads.h en el que han incluído nuevos tipos de datos y funciones para ayudar a programar de forma concurrente. Se incluye el manejo de threads, así como de mutex y variables condicionales (para sincronización).

Dejamos el uso de esta librería para otro artículo separado, que hay mucho de lo que hablar... :)

Macros genéricas

Se añade algo con el mismo olor (salvando las distancias) a los templates de C++. Se pueden crear macros genéricas, que permitan seguir uno u otro camino con respecto al tipo de los parámetros recibidos.

#define cbrt(X) _Generic((X), long double: cbrtl, \
                            default: cbrt, \
                            float: cbrtf)(X)

Este es el típico ejemplo para demostrar el funcionamiento de estas macros. La familia de funciones cbrt se encarga de hallar la raíz cúbica de un número, existiendo tres distintas funciones dependiendo de si el parámetro pasado es un long, un int o un float. Gracias a las nuevas macros genéricas podemos crear una macro que traduzca la llamada a la función correcta dependiendo del tipo del parámetro pasado.

Y hasta aquí...

Y poco más que contaros en este artículo. No son todas las novedades que da este nuevo estándar, pero si que son las más importantes a mi modo de ver. Si investigais un poco más podréis ver como se añaden igualmente ciertas funciones para facilitar el alineamiento de datos, así como para finalizar de forma segura un programa, mejor manejo de números complejos, ...

¡Feliz año nuevo para todos! :D