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. .0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .A .B .C .D .E .F NUL DLE SOH DC1 STX DC2 ETX DC3 EOT DC4 ENQ NAK ACK SYN BEL ETB BS CAN HT EM LF SUB VT ESC FF FS CR GS SO RS SI US SP ! 1 A Q a q ” 2 B R b r # 3 C S c s $ 4 D T d t % 5 E U e u & 6 F V f v ’ 7 G W g w ( 8 H X h x ) 9 I Y i y * : J Z j z + ; K [ k { , < L \ l — = M ] m } . > N ˆ n ˜ / ? O 0 @ P ` p o 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 unicode str Python 3 σ ο φ str 03C3 03BF 03C6 bytes CF83 CEBF CF86 Un exemple en UTF-8 ί 03AF CEAF α 03B1 CEB1 . 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 s S B = = = = ”\ xc3 \ xa9 ” # un ” é ” en UTF−8 b . decode ( ” u t f −8” ) # un ” é ” r e p r é s e n t é en U n i c o d e (U+00E9 ) s . upper ( ) # un ” É” r e p r é s e n t é en U n i c o d e (U+00C9 ) S . encode ( ” u t f −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). Le sandwich unicode 2.1 Mauvaises pratiques 1. Une fonction universelle de conversion (Python 2.x, a son équivalent en Python 3.x) def c o n v e r t e v e r y t h i n g t o u n i c o d e ( x ) : i f i s i n s t a n c e ( x , unicode ) : return x else : r e t u r n s t r ( x ) . decode ( ’ u t f −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 ) : if isinstance (x , str ) : 4 return x else : r e t u r n unicode ( x ) . encode ( ’ u t f −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 i n p o s i t i o n 0 : o r d i n a l not i n 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 ’ i n p o s i t i o n 0 : o r d i n a l not i n 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=” u t f −8” ) as f : f . w r i t e ( 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=” u t f −8” ) as f : f . w r i t e ( ” 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 v e r s i o n =” 1 . 0 ” encoding=” i s o −8859−1” ?> <meta http−equiv=” Content−Type” c o n t e n t =” t e x t /html ; c h a r s e t = u t f −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 v e r s i o n = ” 1 . 0 ” encoding =” u t f −8”?> <junk >\ x f f \ x00 \ x11 \ x22</junk> ””” data = data raw . decode ( ” u t f −8” ) UnicodeDecodeError : ’ u t f 8 ’ codec can ’ t decode byte 0 x f f i n 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