cours1 min de lecture

Textes et encodages — ASCII, ISO-8859-1, Unicode

Comment la machine représente des caractères, et pourquoi un mauvais encodage transforme « café » en « café ».
programme

Introduction

Vous savez désormais comment la machine code des nombres. Mais comment représente-t-elle un mot, une phrase, un emoji ? Aucun « A » ne vit dans la mémoire d'un ordinateur : seuls vivent des bits. Il faut donc une convention qui associe à chaque caractère un numéro, lui-même représentable en binaire. Cette convention s'appelle un encodage de caractères.

Plusieurs encodages ont coexisté dans l'histoire — chacun avec ses avantages et ses limites. Comprendre lequel est utilisé est essentiel pour ouvrir correctement un fichier texte, transmettre des données, ou simplement éviter qu'un accent ne se transforme en hiéroglyphe.

ASCII — l'ancêtre

L'ASCII (American Standard Code for Information Interchange) date de 1963. Il code chaque caractère sur 7 bits, soit 27=1282^7 = 128 valeurs possibles, de 0 à 127.

PlageContenu
0–31Caractères de contrôle (retour chariot, tabulation…)
32–47Espace et ponctuation (!, ", #, etc.)
48–57Chiffres 09
65–90Lettres majuscules AZ
97–122Lettres minuscules az

Quelques codes à connaître :

CaractèreCode décimalCode binaire
A651000001
a971100001
0480110000
(espace)320100000
Remarquez qu'il y a exactement 32 d'écart entre une majuscule et la minuscule correspondante : A vaut 65 et a vaut 97. C'est pour cela que basculer un caractère en minuscule revient, en interne, à mettre un bit à 1.

En Python, les fonctions ord() et chr() permettent de naviguer entre un caractère et son code :

print(ord('A'))   # 65
print(chr(97))    # 'a'
print(ord('0'))   # 48

Limites évidentes de l'ASCII : pas d'accent, pas de é, à, ç. Le c français cédille n'existe pas. L'ASCII est resté valable pour l'anglais, mais intenable pour le reste du monde.

Quel est le code ASCII de la lettre Z ?

ISO-8859-1 — l'extension européenne

L'ISO-8859-1 (parfois appelée Latin-1) étend l'ASCII à 8 bits et définit les 128 caractères supplémentaires (codes 128 à 255). On y trouve les caractères accentués des langues d'Europe occidentale : é, à, ç, ñ, ö

CaractèreCode décimalCode hexa
é2330xE9
à2240xE0
ç2310xE7

ISO-8859-1 reste rétrocompatible avec l'ASCII : les 128 premiers codes sont identiques. Un fichier ASCII pur reste valide en ISO-8859-1.

Mais ISO-8859-1 ne couvre que l'Europe de l'Ouest. Pour le grec, le russe, l'arabe, le chinois… il a fallu d'autres variantes (ISO-8859-2, ISO-8859-5, etc.). Cette multiplication des encodages a vite tourné au cauchemar : un même octet ne signifiait plus la même chose selon le contexte.

Unicode — un caractère, un numéro universel

Unicode est né dans les années 1990 d'une ambition simple : attribuer un numéro unique à tous les caractères de toutes les langues du monde, plus les symboles, les emojis, les caractères mathématiques, etc.

Chaque caractère reçoit un code point (point de code), noté typiquement U+XXXXXXXX est sa valeur en hexadécimal :

CaractèreCode pointDécimal
AU+004165
éU+00E9233
λ (lambda grec)U+03BB955
(idéogramme japonais « jour »)U+65E526 085
😀U+1F600128 512

Unicode définit aujourd'hui plus de 150 000 caractères. Mais Unicode n'est qu'un catalogue : il dit quel numéro a chaque caractère, pas comment écrire ce numéro en mémoire. Pour cela, il faut un encodage, parmi plusieurs disponibles.

UTF-8 — l'encodage qui a tout emporté

UTF-8 est de loin l'encodage Unicode le plus utilisé aujourd'hui — sur le Web, dans les fichiers texte, dans la quasi-totalité des langages modernes.

Ses propriétés :

  • Il code chaque caractère sur 1 à 4 octets selon sa valeur Unicode.
  • Il est rétrocompatible avec l'ASCII : un fichier ASCII pur est aussi un fichier UTF-8 valide.
  • Les caractères courants en anglais tiennent sur 1 octet, les caractères accentués courants sur 2 octets, les idéogrammes sur 3, certains emojis sur 4.
CaractèreCode pointOctets en UTF-8
AU+004141 (1 octet)
éU+00E9C3 A9 (2 octets)
U+65E5E6 97 A5 (3 octets)
😀U+1F600F0 9F 98 80 (4 octets)
⏵ Ctrl+↵ pour exécuter
Aucune exécution pour l'instant.

Sortie attendue :

Chaîne   : café
Octets   : b'caf\xc3\xa9'
En hexa  : 63 61 66 c3 a9
Longueur : 5 octets pour 4 caractères
Le préfixe b'...' signale à Python que l'objet est une suite d'octets (bytes) — pas une chaîne de caractères. Les octets non imprimables comme 0xC3 sont affichés sous la forme \xc3 ; les octets correspondant à des lettres ASCII sont affichés tels quels (c, a, f).

Le é occupe 2 octets en UTF-8 alors qu'il n'occupe qu'un seul caractère logique dans la chaîne Python.

Combien d'octets occupe la chaîne "é" en UTF-8 ?

Le bug classique des encodages mélangés

Imaginez un fichier notes.txt enregistré en UTF-8 par votre éditeur de texte. Vous l'ouvrez ensuite avec un logiciel qui croit lire de l'ISO-8859-1. Que se passe-t-il pour le mot café ?

  • En UTF-8, é est stocké comme les octets 0xC3 0xA9.
  • Lus en ISO-8859-1, ces deux octets sont interprétés comme deux caractères séparés : Ã (code 195) et © (code 169).

Résultat à l'écran : café devient café. C'est le signe quasi universel d'un fichier UTF-8 mal interprété.

Quand vous voyez des séquences comme é, à ou ç à la place d'accents, c'est presque toujours un fichier UTF-8 lu comme ISO-8859-1. Le contenu n'est pas corrompu — il est juste mal interprété.

En Python — préciser l'encodage à l'ouverture

Python permet de préciser l'encodage à l'ouverture d'un fichier :

# Lecture explicite en UTF-8
with open("notes.txt", encoding="utf-8") as f:
    contenu = f.read()

# Conversion en bytes puis dans un autre encodage
texte = "café"
octets_utf8 = texte.encode("utf-8")     # b'caf\xc3\xa9'
octets_latin = texte.encode("latin-1")  # b'caf\xe9'

Inversement, decode() reconstruit la chaîne à partir des octets, en précisant l'encodage utilisé.

octets = b'caf\xc3\xa9'
print(octets.decode("utf-8"))    # 'café' — correct
print(octets.decode("latin-1"))  # 'café' — buggué

Pièges courants

  • Toujours préciser l'encodage quand vous ouvrez un fichier texte (open("fichier.txt", encoding="utf-8")). Les valeurs par défaut varient d'un OS à l'autre.
  • Le nombre d'octets d'une chaîne (len(s.encode())) n'est pas forcément le nombre de caractères (len(s)) — vrai uniquement en ASCII.
  • Un caractère emoji peut occuper jusqu'à 4 octets en UTF-8, et certains combinent plusieurs code points (famille, drapeaux…).

Pour aller plus loin

UTF-8 n'est pas le seul encodage Unicode. UTF-16 est utilisé en interne par Java, JavaScript et Windows ; UTF-32 (4 octets par caractère, fixe) est rare mais simple. Le Web s'est concentré sur UTF-8 pour sa compacité et sa rétrocompatibilité ASCII — c'est aujourd'hui le standard de facto.