9.4. Le standard Unicode

Le standard Unicode est un mécanisme pour représenter les caractères de tous les différents langages à travers le monde. Quand Python analyse un document XML, toutes les données sont stockées en mémoire au format Unicode.

Vous y reviendrez dans une minute après un bref rappel historique.

Remarque historique. Avant l'Unicode, il y avait des systèmes d'encodage de caractères propres à chaque langage, chacun utilisant les mêmes positions (0-255) pour représenter son jeu de caractères. Certains langages (comme le Russe) disposaient de multiples standards divergents sur le manière de représenter les mêmes caractères; d'autres langages (comme le japonais) avaient tant de caractères qu'ils nécessitaient de recourir à de multiples jeu de caractères. L'échange de documents entre systèmes était difficile parce qu'il n'y avait aucun moyen pour une machine de dire avec certitude quel schéma d'encodage avait utilisé l'auteur d'un document; la machine ne voyait que des nombres et ces nombres pouvaient avoir plusieurs significations. Imaginez alors d'enregistrer ces documents au même endroit (comme dans la même table d'une base de données); vous auriez besoin d'enregistrer le jeu d'encodage avec chaque partie du texte et de vous assurer de le transmettre en même temps que le texte. Imaginez alors à quoi ressembleraient des documents multilingues rassemblant les caractères issus de différents langages. (Typiquement, ils utiliseraient des codes d'échappement pour passer d'un mode à l'autre; et hop, vous utilisez le mode russe koi8-r, et le caractère 241 signifie ceci; et hop, vous êtes maintenant dans un mode grec, et le caractère 241 signifie cela. Et ainsi de suite.) C'est pour résoudre ce problème qu'Unicode a été conçu.

Pour résoudre ces problèmes, Unicode représente chaque caractère comme un nombre codé sur 2 octets, de 0 à 65535.[6] Chaque nombre représente un unique caractère utilisé au moins dans l'un des langages existant. (Les caractères présents dans de multiples langages ont le même code numérique.) Il y a exactement un nombre par caractère et un caractère par nombre. Un caractère Unicode n'est jamais ambigu.

Bien sûr, il n'en demeure pas moins le problème de tous ces systèmes d'encodage antiques. L'ASCII 7-bit, par exemple, qui enregistre les caractères anglais dans une fourchette de 0 à 127. (65 représente le «A» majuscule, 97 le «a» minuscule et ainsi de suite.) L'anglais dispose d'un alphabet très simple et il peut être complètement exprimé en ASCII 7 bits. Les langages d'Europe de l'Ouest comme le français, l'espagnol, et l'allemand utilisent tous un système d'encodage appelé ISO-8859-1 (appelé encore «latin-1»), qui reprend les caractères de l'ASCII 7 bits pour les positions de 0 à 127, puis étend son espace de positions de 128 et 255 pour les caractères comme le 'n coiffé d'un tilde' (241), et le 'u surmonté de deux points' (252). Unicode utilise les mêmes caractères que l'ASCII 7 bits de 0 à 127, les mêmes caractères que l'ISO-8859-1 de 128 à 255, puis étend son espace de positions de 256 à 65535 pour accueillir les caractères des autres langues.

Quand vous vous occupez de données Unicode, vous pouvez avoir ponctuellement besoin de rétroconvertir ces données dans un système d'encodage archaïque. Par exemple, pour les intégrer dans un autre système informatique qui s'attend à recevoir des données dans un modèle d'encodage spécifique sur un octet, ou pour les afficher sur une console ou une imprimante qui ne gère pas l'Unicode. Ou encore, pour les stocker dans un document XML qui précise explicitement un modèle d'encodage différent.

Et sur cette remarque, retournons à Python.

Le langage Python supporte Unicode depuis la version 2.0. Le paquetage XML utilise Unicode pour mémoriser toutes les données XML analysées, mais vous pouvez utiliser Unicode partout.

Exemple 9.13. Introduction à Unicode

>>> s = u'Dive in'            1
>>> s
u'Dive in'
>>> print s                   2
Dive in
1 Pour créer une chaîne de caractères Unicode plutôt qu'une chaîne ASCII, ajoutez la lettre «u» devant la chaîne. Notez que cette chaîne particulière ne possède aucun caractère non-ASCII. Parfait; Unicode est un sur-ensemble d'ASCII (un immense sur-ensemble en vérité), ainsi toute chaîne de caractères ASCII peut être aussi stockée en Unicode.
2 Lorsque l'on affiche une chaîne de caractères, Python essaie de la convertir dans l'encodage par défaut, généralement l'ASCII. (Nous y venons dans une minute.) Puisque cette chaîne Unicode est constituée de caractères qui sont également des caractères ASCII, leur affichage produit dans les deux cas le même résultat; la conversion est uniforme et si vous ne saviez pas que s était une chaîne de caractères Unicode, vous ne remarquez pas la différence.

Exemple 9.14. Mémoriser des caractères non-ASCII

>>> s = u'La Pe\xf1a'         1
>>> print s                   2
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
UnicodeError: ASCII encoding error: ordinal not in range(128)
>>> print s.encode('latin-1') 3
La Peña
1 Le véritable avantage d'Unicode est, bien entendu, sa capacité à mémoriser des caractères non-ASCII, comme «ñ» espagnol (n surmonté d'un tilde). Le caractère Unicode pour n tilde est U+0xf1 en hexadécimal (241 en décimal), que vous pouvez écrire de cette façon : \xf1.
2 Vous rappelez-vous que je disais que la fonction print essaie de convertir une chaîne de caractères Unicode en ASCII afin de pouvoir l'afficher ? Et bien, cela ne marche pas ici parce que notre chaîne Unicode contient des caractères non-ASCII et Python déclenche une erreur UnicodeError.
3 Voici comment s'effectue la conversion d'Unicode vers d'autres modèles d'encodage. s est une chaîne de caractères Unicode, mais print peut seulement afficher une chaîne de caractères normale. Pour résoudre ce problème, vous appelez la méthode encode, disponible pour toute chaîne Unicode, afin d'effectuer la conversion de l'Unicode vers le modèle d'encodage passé en paramètre. Dans ce cas, vous utilisez latin-1 (aussi connu sous le nom de iso-8859-1), qui inclut le n tilde (tandis que l'encodage ASCII par défaut ne le prend pas en charge, étant donné qu'il inclut seulement les caractères positionnés de 0 à 127).

Vous rappelez-vous que je disais que Python convertit d'habitude l'Unicode en ASCII toutes les fois qu'une chaîne Unicode a besoin d'être transformée en une chaîne normale ? Et bien, ce modèle d'encodage par défaut est une option qui peut être personnalisée.

Exemple 9.15. sitecustomize.py

# sitecustomize.py                   1
# this file can be anywhere in your Python path,
# but it usually goes in ${pythondir}/lib/site-packages/
import sys
sys.setdefaultencoding('iso-8859-1') 2
1 sitecustomize.py est un script particulier; Python essaie de le charger au démarrage, si bien qu'il est lancé automatiquement. Comme il est indiqué dans le commentaire, il peut se trouver n'importe où (à condition que import puisse le retrouver), mais il est placé généralement dans le répertoire site-packages au sein du répertoire Python lib.
2 La fonction setdefaultencoding déclare un encodage par défaut. Python tâchera d'utiliser ce modèle d'encodage quand il aura besoin de transformer automatiquement une chaîne Unicode en une chaîne normale.

Exemple 9.16. Les effets du paramétrage de l'encodage par défaut

>>> import sys
>>> sys.getdefaultencoding() 1
'iso-8859-1'
>>> s = u'La Pe\xf1a'
>>> print s                  2
La Peña
1 Cette exemple suppose que vous ayez effectué les changements indiqués dans l'exemple précédent concernant le fichier sitecustomize.py et que vous ayez redémarré Python. si l'encodage par défaut signale encore 'ascii', vous n'avez pas paramétré correctement sitecustomize.py, ou bien Python n'a pas été redémarré. L'encodage par défaut peut seulement être changé au démarrage de Python; vous ne pouvez pas le modifier ultérieurement. (En raison de quelques excentricités de programmation que je ne détaillerai pas maintenant, vous ne pouvez plus appeler sys.setdefaultencoding après que Python ait démarré. Pour creuser la question, voyez site.py et recherchez «setdefaultencoding».)
2 Maintenant que le modèle d'encodage par défaut inclut tous les caractères que vous utilisez dans votre chaîne, Python n'a aucun problème pour la convertir automatiquement et l'afficher.

Exemple 9.17. Spécifier l'encodage des fichiers .py

Si vous stockez des chaînes non-ASCII dans votre code Python, vous aurez besoin de spécifier l'encodage pour chaque fichier .py en déclarant l'encodage en tête de chaque fichier. Cette déclaration définit le fichier .py au format UTF-8 :

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

Qu'en est-il maintenant des documents XML ? Et bien, chaque document XML possède un encodage spécifique. De nouveau, ISO-8859-1 est un encodage courant pour traiter les données des langages de l'Europe de l'Ouest. C'est également le cas de KOI8-R pour les documents en langue russe. Lorsque l'encodage est spécifié, il apparaît dans l'en-tête du document XML.

Exemple 9.18. russiansample.xml


<?xml version="1.0" encoding="koi8-r"?>       1
<preface>
<title>Предисловие</title>                    2
</preface>
1 Cet exemple est extrait d'un véritable document XML rédigé en russe; c'est une partie de la traduction de ce livre-ci. Remarquez l'encodage, koi8-r, spécifié dans l'en-tête.
2 Ces caractères cyrilliques composent, à ma connaissance, le mot russe qui signifie «Preface». Si vous ouvrez ce fichier dans un éditeur de texte classique, vous obtiendrez vraisemblablement une chaîne de caractères incompréhensible, parce que ces caractères sont encodés en koi8-r, mais sont affichés en iso-8859-1.

Exemple 9.19. Analyser russiansample.xml

>>> from xml.dom import minidom
>>> xmldoc = minidom.parse('russiansample.xml') 1
>>> title = xmldoc.getElementsByTagName('title')[0].firstChild.data
>>> title                                       2
u'\u041f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435'
>>> print title                                 3
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
UnicodeError: ASCII encoding error: ordinal not in range(128)
>>> convertedtitle = title.encode('koi8-r')     4
>>> convertedtitle
'\xf0\xd2\xc5\xc4\xc9\xd3\xcc\xcf\xd7\xc9\xc5'
>>> print convertedtitle                        5
Предисловие
1 Je suppose que vous avez sauvegardé l'exemple précédent en tant que russiansample.xml dans le répertoire courant. Je suppose également, pour ne rien oublier, que vous êtes revenu à un encodage en 'ascii' par défaut, en retirant le fichier sitecustomize.py, ou en commentant la ligne setdefaultencoding.
2 Notez que les données texte de la balise title (maintenant dans la variable title, grâce à cette longue concaténation de fonctions Python dont je ne dirai mot jusqu'à la prochaine section, au risque de vous agacer) -- les données texte de l'élément title du document XML sont mémorisées en Unicode.
3 Il n'est pas possible d'afficher le titre parce que cette chaîne Unicode contient des caractères non-ASCII et Python ne peut le convertir en ASCII car cela n'a aucun sens.
4 Vous pouvez cependant les convertir explicitement en koi8-r et dans ce cas vous obtenez une chaîne (normale, et non Unicode) de caractères codés sur un octet (f0, d2, c5, et ainsi de suite) qui sont les versions koi8-r des caractères de la chaîne Unicode d'origine.
5 L'affichage de la chaîne encodée en koi8-r ne produira probablement qu'un charabia sur votre écran, parce que votre IDE Python interprète ces caractères en iso-8859-1, non en koi8-r. Mais ils sont au moins affichés. (Et , si vous regardez attentivement, il s'agit du même charabia que lorsque vous aviez ouvert le document XML original dans un éditeur de texte non-Unicode. Python le convertit de koi8-r en Unicode lorsqu'il analyse le document XML et vous l'avez juste rétroconvertis.)

Pour résumer, Unicode peut paraître intimidant si vous n'y avez jamais eu à faire auparavant, mais les données en Unicode sont très faciles à manipuler avec Python. Si vos documents XML sont tous codés en ASCII 7-bit (comme les exemples de ce chapitre), vous ne vous soucierez jamais d'Unicode. Python convertira les données ASCII des documents XML en Unicode au moment du traitement et les restituera automatiquement en ASCII au besoin, sans que vous ne le remarquiez jamais. Mais s'il devient nécessaire de les gérer dans d'autres langages, Python répond présent à l'appel.

Lectures complémentaires

Footnotes

[6] Il s'agit là encore d'une extrême simplification. Unicode a maintenant été étendu pour tenir compte des textes en chinois ancien, en coréen et en japonais, lesquels sont composés de tant de caractères que le système Unicode sur 2 octets n'aurait pu suffire à tous les représenter. Mais Python ne le supporte pas actuellement et je ne sais pas s'il y a un projet en cours pour l'y ajouter. Vous avez atteint les limites de mon expertise, désolé.