dimanche 5 août 2012

Chute ASCII

Il y a deux sources d'ennuis majeures lorsqu'on est développeur informatique Français : le clavier AZERTY et les accents. J'exagère pour le premier car ce n'est pas ) proprement parler un problème : tous les bons développeurs utilisent la disposition QWERTY...

Le second est plus épineux.

Je vais vous narrer un cas de figure assez inattendu. En cherchant à répondre à une question posée sur le liste de diffusion technique de ma boite, j'ai testé la redirection de la sortie standard Windows dans le cadre d'un script Python.

Contexte : je lance les commandes dans cmd sous Windows XP SP3 (!). Mon encodage de fichier est tout naturellement utf-8. J'utilise Python 2.7.

Le code de test est le suivant :

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

print("Chaine ascii")
print("Chaîne unicodée")

Testons la sortie dans cmd

Chaine ascii
Cha├«ne unicod├®e

Pas terrible (mais bon, c'est cmd)... Jouons un peu avec l'encodage des caractères :

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

print("Chaine ascii")
print(u"Chaîne unicodée")

On obtient :

Chaine ascii
Chaîne unicodée

Redirigeons la sortie standard. Comme pour tout bon Unix, on utilise l'opérateur >>.

Traceback (most recent call last):
  File "test.py", line 5, in <module>
      print(u"Cha├«ne unicod├®e")
      UnicodeEncodeError: 'ascii' codec can't encode character u'\xee' in position 3:
      ordinal not in range(128)

Ouch...

Corrigeons. Python utilise une représentation interne appelée unicode pour les chaînes non purement ASCII. Le type str dispose de deux méthodes liées à cette représentation :

  • str.decode(encoding) : décode une chaîne encodée en utilisant encoding vers le format unicode.
  • str.encode(encoding) : réalise l'opération inverse, c'est à dire encode une chaîne en représentation unicode vers encoding.

C'est cette dernière qu'il faut utiliser pour corriger notre problème.

# -*- coding: utf-8 -*-

print("Chaine ascii")
print(u"Chaîne unicodée".encode('utf-8'))

Là ça marche, la redirection écrit dans le fichier en utf-8. Par contre, la sortie console est moche.

Cette petite expérience soulève 2 points :

  • L'année 2012 est bien entamée, l'informatique moderne à plus de 42 ans (!). Pourtant, les questions d'encodage et de gestion des coractères non ASCII ne sont jamais triviales.

  • Je ne sais pas qui blâmer mais un code qui plante lamentablement si on a l'audace de rediriger sa sortie est quelque-chose qui me chagrine. Peut-être y a-t-il des bonnes pratiques en Python sur les sorties (ne pas utiliser print ? Ça semble bizarre !). J'ai essayé sous Linux, cela fonctionne si on ne préfixe pas la chaîne unicode par u (on évite alors la conversion vers unicode).

Je trouve les problèmes d'encodage passonnants ! C'est souvent oublié par les développeurs. Par exemple, j'ai rarement vu un contrat d'interface pour un échange de fichiers plats spécifier l'encodage des fichiers. Dans les bases de données, cela donne lieux à des problématiques délicieuses (transcoder une base de données Oracle est une expérience que je recommande à tous !)

Habituellement, Python gère cela d'une façon plutôt élégante grâce aux méthodes str.encode et str.decode mais assi au module codecs qui offre une belle boite à outils pour gérer les encodages.