[Résolu - par Python] Aidez-moi, je ne comprends rien aux encodages !
Les pages Web qui affichent des « � », les emails qui titrent « Réunion à reporter » et vos scripts Python qui geignent « SyntaxError: Non-ASCII character », c’est une histoire d’encodage. L’encodage est un sujet éminemment complexe, mais ce que vous avez besoin de savoir pour développer l’esprit tranquille est on ne peut plus simple. D’autres l’ont fait avant moi (joel, vincent, seb), mais c’est le genre de vaccin qui nécessite un rappel.
Question de représentation
Prenons un animal au hasard. L’hirondelle d’Afrique par exemple. Ou d’Europe. A vide en tout cas. Bon ça devient trop compliqué, prenons un chien.
Le mot « chien », représente l’animal quadrupède et cabochard que nous apprécions tous (ou pas). « Chien », est une représentation de l’animal, pas l’animal lui-même. Encore heureux, un chien n’est pas une série de caractères alignés, c’est un organisme complet avec poils, bave et toutes ces joyeusetés. Mais pour nous permettre de communiquer entre nous, de garder trace aussi en écrivant sur papier et informatiquement, nous utilisons le mot « chien ».
« Chien » est une représentation, et il en existe d’autres. Les anglais utilisent « dog », les espagnols utilisent « perro ». On parle du même truc qui aboie et mange trop de croquettes, mais on utilise une représentation différente, composée de symboles textuels : les mots.
Un autre exemple souvent cité, celui des chiffres. On utilise « 5 » pour représenter le concept de cinq, mais les romains utilisaient « V ». On parle de la même chose, la donnée est la valeur de cinq, mais sa représentation peut être « 5 », « V », « cinq », « five », « cinco », etc.
C’est un concept très important en informatique : la représentation est différente de la donnée. Par exemple quand vous faites :
>>> age = 5
Python ne stocke pas 5, car l’ordinateur ne connait que des zéros et des uns. Il stocke quelque chose qui ressemble à sa représentation binaire : « 101 ». On parle de la même chose, le concept du « nombre 5 », mais Python et votre esprit le représentez différemment.
Et bien pour le chaîne de caractère, c’est pareil. Si vous faites :
nom = 'e-vidence'Python ne stocke par la chaîne ‘e-vidence’ mais sa représentation. Chaque lettre est traduite en un nombre, qui lui, est gardé en mémoire.
Là où ça se gâte, c’est pour savoir : à quel nombre correspond quelle lettre ?
Et ça, ça dépend de l’encodage utilisé. L’encodage, c’est un énorme tableau qui dit à quelle lettre correspond quel nombre. Il y a beaucoup d’encodages différents. Beaucoup, beaucoup. La plupart ne permettent pas de représenter toutes les lettres, car il existent des caractères spéciaux dans toutes les langues : chinois, suédois, russe…
Votre système d’exploitation (Windows, Mac, Linux, etc) lit et écrit en utilisant un encodage. Python également. Toute donnée texte est écrite en utilisant un encodage, et on ne peut lire ce texte que si on connait son encodage d’origine.
En gros, si pendant des années vous avez manipulé des textes avec Python sans connaître leur encodage d’origine, c’est parce que vous avez eu de la chance. Si, si, c’est un coup de moule, promis.
Les encodages, du plus simple au plus complexe
Le plus simple des encodage est l’encodage ASCII. Inventé par les américains, il contient 128 caractères, dont une centaine sont visibles :
>>> import string >>> print(string.printable) '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c' >>> print(len(string.printable)) 100
Ces caractères vous sont familiers ? En Europe, on les retrouve partout, et c’est une des raisons pour laquelle on ignore souvent les problèmes d’encodage : ASCII suffit à de très nombreux usages dans nos pays. En fait, tant que vous n’utilisez pas « é » ou «ç » et autres exotismes, tout ira bien car ASCII est souvent contenu dans la plupart des autres encodages.
Seulement viendra le moment où vous devrez traiter un texte français correctement écris. Viendra le moment où vous traiterez un texte espagnol, et tous ces vilains « ~ », et, en bidouillant, vous vous en sortirez. Et un matin, vous codez un projet open source avec un coréen et vous êtes forcés de chercher à comprendre.
D’abord, il faut savoir que chaque langue ou presque à son tableau de correspondant « lettre - nombre ». Le plus utilisé en Europe est l’encodage « ISO-8859-1 », qui permet de traduire en nombre tous les caractères ASCII, mais aussi les accents, les cédilles, les tildes et tout le toutim. Windows utilise un encodage quasiment similaire appelé « CP-1252 ».
Mais que se passe-t-il si vous recevez un texte en chinois, encodé par exemple avec « GB2312 » ? Et bien vous recevez des nombres qui correspondent à des caractères chinois. Si le programme sait que le texte est chinois et qu’il a accès à un encodage chinois, il pourra traduire chaque chiffre en caractère.
Dans le cas contraire, le programme essayera de transformer les chiffres en utilisant votre encodage, souvent ISO-8859, ce qui vous donnera soit des lettres incohérentes, soit un gros plantage car il y a bien plus de caractères en chinois que de lettres en français et certains nombres n’apparaissent donc pas dans ISO-8859.
Pour pouvoir lire un texte, quel qu’il soit (page Web, email, fichier texte, XML, nom de fichier, titre de MP3, etc), il faut savoir dans quel encodage il est écrit et avoir cet encodage sous la main. Si ça marche sans cela, c’est simplement que par chance le texte est écrit dans l’encodage par défaut de votre système.
De plus, pas besoin de chercher un exemple tordu à base de correspondances asiatiques pour avoir des problèmes d’encodage. Un simple lecteur de musique peut afficher « Les chants de Noël » avec une orthographe des plus farfelus, simplement parce que la personne qui a enregistré la chanson la fait depuis un autre système.
Vu le nombre de langues et de caractères spéciaux, il a été créé un encodage qui permet de traduire tous les caractères : « unicode ». Unicode possède des sous ensembles tel que UTF-8 et UTF-16, et nous allons essentiellement utiliser UTF-8 dans cet article car c’est le plus rependu.
Unicode résout donc en théorie la plupart des problèmes, mais en pratique des milliers d’applications utilisent encore des encodages locaux. Il va falloir faire avec.
Gérer tout ce boxon avec Python
Pour Python, il nous faut nous soucier de trois couches : l’encodage du système d’exploitation, l’encodage du fichier du script Python et l’encodage de Python. Pour se simplifier les choses, mettez tout en unicode.
Écrivez votre script Python en UTF-8, et pour cela, réglez votre éditeur de texte sur UTF-8 dans ses préférences. Rajoutez ensuite la ligne suivant en première ligne de tous vos script :
# -*- coding= UTF-8 -*-Cela précise à Python que c’est écrit en UTF-8. Sans cette mention, la moindre apparition d’un « é » déclenchera l’erreur « SyntaxError: Non-ASCII character ». ASCII est en effet l’encodage par défaut de Python.
Enfin, écrivez vos chaînes en les préfixant de ‘u’ :
>>> comment = u'De façon évidente'
Ce ‘u’ force ce Python à créer une chaîne encodée en UTF-8.
Maintenant, tout ce que vous avez à l’intérieur de votre programme est en UTF-8, vous ne pouvez plus avoir de problèmes d’encodage sans entrée ou sortie de données. Mais un programme qui reste fermé sur lui-même est rarement très utile, il va donc falloir apprendre à traduire depuis et vers UTF-8.
Quand vous lisez un fichiez, vous faites ainsi :
>>> fichier = open("fichier.txt") >>> for ligne in fichier : … print(ligne) …
Lire un fichier est une entrée de données. Si le fichier est écrit dans votre encodage, vous n’aurez pas de problème. Typiquement, si vous êtes sous Windows et que vous lisez un fichier écrit par Windows, il sera en ASCII ou ISO-8859, et tout ira bien. Mais si vous recevez un fichier issu d’un Mac, il sera probablement en UTF-8, et alors votre programme affichera des caractères étranges ou plantera.
Le meilleur moyen de prévenir cela est de décoder la donnée ainsi lue. Python fournit aux chaîne les méthodes « encode » et « decode », qui convertissent les données depuis et vers UTF-8 :
>>> fichier = open("fichier.txt") >>> for ligne in fichier : … ligne_decodee = ligne.decode("UTF-8") … print(ligne_decodee)
Là vous allez me dire, « mais pourquoi on décode un fichier en UTF-8 vers de l’UTF-8 ? ». Tout simplement parce que Python ne sait pas que le fichier est en UTF-8. Il n’a aucun moyen de le savoir. Et comme son encodage par défaut n’est pas UTF-8, il va enregistrer ces chaines en mémoires dans son encodage par défaut au lieu du bon.
A l’inverse, si vous êtes sous Linux, et que vous voulez lire un fichier Windows :
>>> fichier = open("fichier.txt") >>> for ligne in fichier : … ligne_decodee = ligne.decode("CP-1252") … print(ligne_decodee)
Ligne_décodee sera toujours de l’UTF-8, dans l’exemple 1 comme dans l’exemple 2, donc on sait ce qu’on traite.
Un moyen rapide de lire un fichier encodé dans un autre encodage est d’utiliser le module codecs :
>>> import codecs >>> fichier = codecs.open("fichier.txt", "ISO-8859") # on donne l'encodage ici >>> for ligne_decodee in fichier : # les lignes sont automatiquement décodées ! ... print(ligne_decodee)
Pour certain d’entre vous
print(ligne_decodee)
affiche peut être des caractères bizarres, notamment si vous êtes sous Windows. C’est normal, votre système d’exploitation affiche dans la console avec son propre encodage. Non seulement il faut décoder les données en entrée, mais il faut aussi les réencoder à la sortie !
>>> fichier = open("fichier.txt") >>> for ligne in fichier : … ligne_decodee = ligne.decode("CP-1252") … chaine_transformee = faire_quelque_chose_avec_les_chaines(ligne_decodee) … print(chaine_transformee.encode('CP-1252'))
Ici, c’est un exemple avec un fichier, mais c’est valable pour toute donnée texte. Vous lisez un nom de fichier ? Il faut faut décoder et encoder. Vous téléchargez ou générez des pages Web ? Il faut décoder et encoder. Vous faites des requêtes à une base de données ? Il faut décoder et encoder.
En résumé: si des données rentrent qui ne sont pas de l’UTF-8, vous décodez. En interne, vous faites ce que vous voulez, tout est en UTF-8. Ensuite si des données sortent, vous encodez si le destinataire attend autre chose de l’UTF-8.
Pour vous donner une idée de l’importance du phénomène, voici le chemin d’une page Web et l’influence des encodages :
- Génération par un script Python écrit dans un encodage et stockant en mémoire les chaînes dans un encodage.
- Récupération de chaînes depuis la base de données qui stockent du texte dans un encodage qui peut être différent de l’encodage du texte lui-même.
- Création du HTML dans un encodage, et spécification d’un encodage dans la balise META.
Envoie de la page par un serveur qui spécifie l’encodage dans le HEADER. - Réception par le navigateur Web, codé dans un encodage, et réglé sur un encodage, installé sur un système qui possède son propre encodage.
Votre texte est en permanence décodé et encodé. Votre boulot est que au milieu de ce processus, votre script comprenne les données qui entrent, et les renvoient soit dans l’encodage d’origine, soit avec la mention du nouvel encodage.
Arrivé à ce stade, vous comprenez maintenant une chose : il est impossible de gérer cela correctement si on ne sait pas quel est l’encodage d’origine des données. On ne peut pas lire un livre si on ne sait pas dans quelle langue il est écrit, on ne peut pas écouter une émission de radio si on ne sait pas dans quelle langue elle est diffusée, et on ne peut pas manipuler un texte si on ne sait pas dans quel encodage il est représenté.
Comment déterminer l’encodage d’origine ?
Mauvaise nouvelle, il n’y a aucun moyen sûr de le faire, et c’est pour cela que parfois encore Firefox affiche de « � », et Outlook des « é ». Mais on peut faire « au mieux ».
Faire au mieux c’est :
- Bien connaître les encodages qu’on utilise soi-même : votre serveur, vos textes, votre base de données… Vous devez parfaitement savoir comment ils stockent le texte. Dans le meilleurs des cas en UTF-8.
- Partir du principe que si les données extérieures communiquent un encodage, c’est le bon. Par exemple les pages Web déclarent l’encodage dans une balise meta :
<meta http-equiv="content-type" content="text/html; charset=utf-8">
- Ne pas faire pour autant confiance à ces données extérieures pour autant et gérer les possibles erreurs de décodage :
>>> try : … u"é".decode("UTF-8") … except UnicodeEncodeError: … print('Ça ne peut pas marcher !') …
- Dans le doute, on peut tenter de deviner l’encodage. Ce n’est pas très fiable, mais c’est mieux que rien.
En résumé, le seul moyen fiable de déterminer l’encodage d’origine est que le système à l’origine des données donne explicitement son nom. De votre côté, pour limiter les dégât, utilisez UTF-8 partout : base de données, serveur, page Web, etc.
u’Le mot de la fin’.encode(”ISO-8869-1″)
Ne devenez pas parano avec les encodages. Si vous développez un programme, mettez tout en UTF-8 et tout ira bien. Le jour où une erreur survient, ce sera à cause d’une donnée externe. Ce jour là, et ce jour là seulement, préoccupez vous de décoder et encoder ce flux de données qui entre et qui sort.
Sachez enfin que Python 3 gère maintenant par défaut toutes les chaînes en unicode, plus besoin de préfixer de ‘u’. Bien sûr, c’est loin d’être la version la plus utilisée.
Enfin, pour ne pas vous laisser sans un petit plus, voici un snippet que vous pouvez utiliser normaliser tous les caractères spéciaux qui ont un équivalent ASCII :
>>> import unicodedata >>> s = u"éèêàùçÇ" >>> print(unicodedata.normalize('NFKD', s).encode('ascii','ignore')) eeeaucC
12/07/2010 à 19:44
Mon téléphone me demande de choisir entre :
ISO 8859-6
WINDOWS-1256
CP1097
CP1098
ISO-8859-9
Windows-1254
VOTRE (BELLE) EXPLICATION NE REPOND PAS A MON PROBLEME.
Pourriez-vous m’éclairer ?
Merci
13/07/2010 à 13:12
Bonjour,
Ceci est une explication dédiée à l’encodage appliquée à la programmation. Pour des problèmes liés à votre produit, je vous invite à contacter votre vendeur / constructeur.