Le codage des caractères en Python Olivier Iffrig 2 novembre 2014 Licence Cet article, ainsi que les images qu il contient (sauf mention contraire explicite) sont sous licence Creative Commons CC-BY-SA. Vous pouvez le copier et le modifier à votre guise, à condition de citer l auteur, de mettre en évidence vos modifications et de partager les modifications sous la même licence. Pour plus de détails : http://creativecommons. org/licenses/by-sa/4.0/ 1 Le co quoi? Nos ordinateurs ne comprennent que le binaire, c est à dire des 0 et des 1 1, souvent regroupés par 8 (les octets). Pour pouvoir représenter du texte dans ce système, il faut donc choisir une représentation pour chaque caractère. C est ce qu ont fait un certain nombre de gens, et vous vous imaginez bien qu ils ne se sont pas concertés avant, du coup, à la fin des années 50, chacun avait sa propre convention de codage des caractères. Afin d arranger un peu les choses, l ISO a décidé en 1960 de créer un comité chargé des systèmes d information, dont l un des objectifs était de coordonner les différentes conventions de codage. C est ainsi que naît l American Standard Code for Information Interchange, abrégé ASCII. 1. pouvant être représentés physiquement de diverses manières, par exemple un potentiel électrique inférieur ou supérieur à un seuil donné 1
.0.1.2.3.4.5.6.7.8.9.A.B.C.D.E.F 0. NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI 1. DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US 2. SP! # $ % & ( ) * +, -. / 3. 0 1 2 3 4 5 6 7 8 9 : ; < = >? 4. @ A B C D E F G H I J K L M N O 5. P Q R S T U V W X Y Z [ \ ] ˆ 6. ` a b c d e f g h i j k l m n o 7. p q r s t u v w x y z { } DEL La table des caractères ASCII L ASCII permet de représenter sur 7 bits (que l on préfixe en général par un 0 pour compléter l octet) les caractères usuels de la langue anglaise, c est-à-dire les lettres majuscules et minuscules, les chiffres, quelques symboles de ponctuation, ainsi que des caractères de contrôle servant à modifier le comportement des terminaux 2. UnicodeDecodeError : ascii codec can t decode byte 0xc3 in position 7 : ordinal not in range(128) Étant donné que beaucoup de langues utilisent des caractères ne figurant pas dans l ASCII, des extensions ont été proposées afin de pallier ce manque. Une fois de plus, les diverses extensions n étaient pas toujours compatibles entre elles, et nous voilà revenus à notre point de départ. c XKCD, http://xkcd.com/927 Bref, il existe beaucoup de codages possibles pour représenter du texte. Et je ne parle même pas de le manipuler. Imaginons que vous voulez réécrire un mot en lettres capitales. Tant qu il s agit de caractères ASCII, il suffit d enlever 32 aux caractères entre 97 et 2. Oui, à l époque, les ordinateurs étaient quelque peu encombrants, donc les opérateurs disposaient d un clavier et d un écran permettant de les contrôler depuis un bureau. 2
122 (les lettres minuscules). Mais comment faire si c est une autre lettre? Ça dépend du codage utilisé. Et rien ne nous garantit que la capitale correspondante peut elle aussi être représentée. Et puis, qu est-ce qui me dit que je ne suis pas en train d utiliser une lettre différente dessinée de la même façon? U+0399 GREEK CAPITAL LETTER IOTA Tous ces problèmes trouvent une solution avec le standard Unicode, qui associe d une part un nom à chaque caractère (abstrait), et d autre part un numéro, appelé point de code. Ainsi, si on connaît la correspondance entre une convention de codage et les points de code de chacun des caractères, la manipulation devient plus aisée. De manière à pouvoir représenter aisément les caractères Unicode, on peut bien sûr utiliser le codage que l on veut. Il en existe cependant trois qui ont été prévus de manière à faciliter les choses. Il s agit d UTF-8, UTF-16 et UTF-32, utilisant comme unité de base 8, 16 ou 32 bits (je dis bien unité de base, parce qu un caractère peut nécessiter plusieurs unités), et qui peuvent encoder n importe quel caractère Unicode. 2 Et Python dans tout ça? En Python 2.x, il existe deux types de chaînes de caractères : str et unicode. Les deux peuvent servir à représenter des caractères. Comme vous l aurez deviné, unicode sert à représenter des chaînes de caractères Unicode. Par opposition, comme son nom ne l indique pas, str devrait servir à représenter des suites d octets, dont certaines sont, de manière fortuite, des représentations encodées d une chaîne de caractères. En Python 3, la notation est clarifiée puisque les chaînes Unicode sont rebaptisées str, alors que les suites d octets prennent le type bytes..encode( utf 8 ) Python 2 Python 3 σ ο φ ί α unicode str 03C3 03BF 03C6 03AF 03B1 str bytes CF83 CEBF CF86 CEAF CEB1 Un exemple en UTF-8.decode( utf 8 ) En pratique, pour passer d une chaîne d octets à une chaîne Unicode, il faut utiliser la méthode decode, qui prend en argument le nom du codage utilisé (et aussi la manière 3
de traiter les erreurs, je vous laisse lire la documentation si ça vous intéresse). Le passage d une chaîne Unicode à une chaîne d octets se fait via la méthode encode qui fonctionne de la même façon. b = \ xc3 \xa9 # un é en UTF 8 s = b. decode ( utf 8 ) # un é r e p r é s e n t é en Unicode (U+00E9 ) S = s. upper ( ) # un É r e p r é s e n t é en Unicode (U+00C9 ) B = S. encode ( utf 8 ) # un É en UTF 8 ( C389 ) Lorsqu on veut représenter du texte, il est donc fortement recommandé d utiliser des chaînes Unicode le plus longtemps possible, et de ne choisir un encodage que pour importer ou exporter le texte (dans un fichier ou via un réseau, par exemple). Que ce soit pour du texte ou des suites d octets, il est primordial de bien savoir ce que l on manipule afin d éviter les confusions. Surtout en Python 2 où le passage entre str et unicode peut être implicite (et source d erreurs cryptiques et difficiles à repérer). 2.1 Mauvaises pratiques Le sandwich unicode 1. Une fonction universelle de conversion (Python 2.x, a son équivalent en Python 3.x) def convert e v erything to unicode ( x ) : i f i s i n s t a n c e ( x, unicode ) : return x e l s e : return s t r ( x ). decode ( utf 8 ) Cette fonction n est pas fondamentalement mauvaise, puisqu elle sert à convertir une chaîne d octets en chaîne Unicode. Cependant, elle n aide en rien à connaître le contenu de x, et donc devient dangereuse si utilisée n importe où. De plus, elle présuppose que la chaîne d octets encode un texte en UTF-8, ce qui n est pas nécessairement le cas (sauf convention d usage, qui doit dans ce cas être clairement explicitée). Enfin, elle opère une conversion en chaîne d octets, ce qui ouvre la porte à beaucoup de mauvaises utilisations. Même remarque pour la fonction inverse : def c o n v e r t e v e r y t h i n g t o s t r ( x ) : i f i s i n s t a n c e ( x, s t r ) : 4
return x e l s e : return unicode ( x ). encode ( utf 8 ) 2. Mélanger str et unicode (Python 2.x uniquement) def p r i n t r e s u l t ( r ) : p r i n t u Ré s u l t a t : + s t r ( r ) Cette fonction présente deux points de danger : premièrement, l opérateur + opéré entre un str et un unicode, qui entraîne une conversion implicite du str en unicode. Si par malheur, str (r) renvoie une chaîne d octets contenant des octets de valeur numérique supérieure à 127, une erreur serait lancée : UnicodeDecodeError : a s c i i codec can t decode byte 0 xc3 in p o s i t i o n 0 : ordinal not in range ( 1 2 8 ) Deuxièmement, l erreur inverse risque de se produire si l encodage de la chaîne Unicode demandé implicitement par print échoue (souvent parce que l encodage de la sortie standard n est pas connu ou est plus restrictif que nécessaire) : UnicodeEncodeError : a s c i i codec can t encode c h a r a c t e r u \ xe9 in p o s i t i o n 0 : ordinal not in range ( 1 2 8 ) Ces problèmes sont résolus en Python 3.x, où aucune conversion entre bytes et str n est faite implicitement. On a donc une erreur lorsqu on essaie par exemple de concaténer une chaîne d octets et une chaîne de caractères Unicode, quel que soit le contenu des chaînes. 2.2 Quelques conseils 1. Fichiers texte En Python 2.x, on peut manipuler des fichiers texte à l aide du module codecs qui permet de spécifier un codage lors de l ouverture du fichier : import codecs with codecs. open ( t o t o. t x t, w, encoding= utf 8 ) as f : f. write ( u Enchant\u00e9. \ n ) Attention cependant, en utilisant le module codecs, on perd la conversion automatique des fins de ligne. En Python 3.x, la fonction open dispose d un argument encoding qui permet d utiliser directement ce que permettait codecs.open en Python 2.x. On peut également noter que les retours à la ligne peuvent être convertis automatiquement, contrairement aux fichiers ouverts avec le module codecs. with open ( t o t o. t x t, w, encoding= utf 8 ) as f : f. write ( Enchant\u00e9. \ n ) 5
2. Déclarez un codage si possible Pour de nombreux dispositifs d entrée-sortie, il est possible de déclarer un encodage : # c o d i n g : cp1252 <?xml version= 1. 0 encoding= iso 8859 1?> <meta http equiv= Content Type content= t e x t /html ; c h a r s e t =utf 8?> Si vous utilisez un codage fixe, n oubliez pas de préciser la convention dans la documentation de votre code. Attention cependant, vous ne devez (et ne pouvez) pas faire confiance aux données venant de l extérieur. Il se peut très bien que le codage annoncé ne permette pas de décoder l entrée correspondante. data raw= <?xml version = 1.0 encoding= utf 8?> <junk>\ x f f \x00\x11\x22</junk> data = data raw. decode ( utf 8 ) UnicodeDecodeError : utf8 codec can t decode byte 0 x f f in p o s i t i o n 4 6 : i n v a l i d s t a r t byte N essayez pas pour autant de deviner l encodage à utiliser. Prévoyez simplement l éventualité de manière à ce que l exception levée ne perturbe pas le comportement de votre code. 3 Conclusion Maintenant que vous avez les bases, il ne vous reste qu à voler de vos propres ailes. Comme partout, c est en essayant et en faisant des erreurs qu on apprend. Et surtout, testez votre code avec des entrées volontairement pathologiques (UTF-8 non valide, codage annoncé différent du codage réel, caractères non-ascii, caractères multi-octets, etc.). Un lien utile, qui a grandement inspiré cet article : http://nedbatchelder.com/ text/unipain.html 6