Le codage des caract`eres en Python

publicité
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
Téléchargement