ISO-8859-1 et UTF-8⚓︎
Au delà de l'ASCII
Python n'est pas limité aux caractères ASCII.
>>> encodage("éèàêï")
[233, 232, 224, 234, 239]
>>> decodage([192, 198, 181, 182, 215])
'ÀÆμ¶×'
Les codes obtenus sont ceux de la table ISO-8859-1, aussi appelée latin-1. Pour des raisons de place en mémoire, Python va prendre la représentation la plus petite pour chaque caractère : ASCII ou latin-1 pour les premiers caractères, puis UTF-8 pour les autres.
Les caractères peuvent également être donnés directement à partir de leur code unicode :
>>> "\u0043\u0069\u0065\u006C\u0020\u00c9\u0074\u006F\u0069\u006C\u00E9"
'Ciel Étoilé'
Lorsque le code unicode n'utilise que 4 chiffres en hexadécimal, il peut s'écrire "\uHHHH"
, où les 4 H
sont les chiffres en hexadécimal. Il faut toujours mettre 4 chiffres, même si les premiers sont 0.
Par contre s'il y a plus de chiffres, il faut utiliser "\UhhhhHHHH"
en mettant 4 chiffres en hexadécimal. Néanmoins, ces symboles ne sont pas forcément pris en compte par la police de caractères utilisée par Python.
Il est aussi possible d'utiliser la description du caractère pour l'obtenir :
>>> print("\N{Latin Small Letter E with Acute}")
é
Nous verrons plus tard comment trouver ces descriptions.
Exercice 3
Découvrez le message codé en unicode ci-dessous :
Vous pouvez chercher d'autres symboles du même genre sur des sites présentant les tables unicodes, comme ce site. On y trouve notamment la table complète de tous les symboles unicode.
Les autres encodages
Python peut convertir n'importe quel texte dans n'importe quel encodage, à condition que les caractères utilisés existent dans cet encodage.
>>> "é".encode("utf8")
b'\xc3\xa9'
>>> "é".encode("latin1")
b'\xe9'
>>> "é".encode()
b'\xc3\xa9'
Les objets que l'on obtient ne sont pas des textes, mais des suites d'octets, identifiées par le b
avant les apostrophes. On remarque que l'encodage de base proposé par Python est l'UTF-8, qui garantit que tout peut être encodé. On remarque également qu'en latin-1, le caractère n'est codé que sur un octet, contre 2 pour l'UTF-8. C'est pourquoi en interne, Python utilise le latin-1 lorsque c'est possible, pour économiser de l'espace mémoire. Voici les versions en binaire de ces octets.
>>> ' '.join([f"{c:08b}" for c in 'é'.encode('utf8')])
'11000011 10101001'
>>> ' '.join([f"{c:08b}" for c in 'é'.encode('latin1')])
'11101001'
Le format 08b
pour les f-strings indique qu'il faut afficher le résultat en binaire, sur 8 bits.
En UTF-8, le caractère é
correspondant à U+00E9
en unicode. Son écriture binaire est bien de la forme 110xxxxx 10xxxxxx
.
Décoder une suite d'octets
À l'inverse, il est possible de décoder une suite d'octets, et c'est là que les choses peuvent se compliquer.
>>> b'\xc3\xa9'.decode("utf8")
'é'
>>> b'\xc3\xa9'.decode("latin1")
'é'
>>> b'\xe9'.decode("latin1")
'é'
>>> b'\xe9'.decode("utf8")
Traceback (most recent call last):
File "<pyshell>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 0: unexpected end of data
Lorsqu'on decode une suite d'octets avec le bon encodage, on retrouve le texte voulu. Par contre, si on utilise un autre encodage, on obtient soit des caractères qui ne sont pas les bons, soit une erreur parce qu'une suite d'octets n'existe pas dans l'encodage choisi. Ainsi, les octets C3
et A9
correspondent à deux symboles différents en latin-1. De même, l'octet E9
, correspond au début d'une suite de 3 octets de la forme 1110xxxx 10xxxxxx 10xxxxxx
.
Exercice 4
En sachant que \(10000000_2=128_{10}\) et \(10111111_2=191_{10}\), trouver deux nombres n1
et n2
entre 128 et 191 tels que "é"+chr(n1)+chr(n2)
donne une chaîne de 3 caractères existants (pas de \xHH
) et que ("é"+chr(n1)+chr(n2)).encode("latin1").decode("utf8")
donne également un symbole valide.
>>> n1 = 130
>>> n2 = 140
>>> "é" + chr(n1) + chr(n2)
'é\x82\x8c'
>>> ("é"+chr(n1)+chr(n2)).encode("latin1").decode("utf8")
'邌'
# Tests
(insensible à la casse)(Ctrl+I)
Solution
On peut prendre n1
et n2
entre 161 et 191, à l'exception de 173.
>>> n1 = 162
>>> n2 = 165
>>> "é" + chr(n1) + chr(n2)
'颥'
>>> ("é"+chr(n1)+chr(n2)).encode("latin1").decode("utf8")
'颥'
>>> n1 = 171
>>> n2 = 187
>>> "é" + chr(n1) + chr(n2)
'é«»'
>>> ("é"+chr(n1)+chr(n2)).encode("latin1").decode("utf8")
'髻'
>>> n1 = 189
>>> n2 = 188
>>> "é" + chr(n1) + chr(n2)
'é½¼'
>>> ("é"+chr(n1)+chr(n2)).encode("latin1").decode("utf8")
'齼'
# Tests
(insensible à la casse)(Ctrl+I)