Calculer le poids d'un texte en Python
En Python, la longueur d’une chaîne et son poids en octets sont deux notions différentes. Cet article vous aide à comprendre ASCII, Unicode, UTF-8 et la bonne méthode pour mesurer un texte.
L'illusion de la longueur en Python
Mesurer la longueur d'un texte ne donne pas la taille qu'il occupera physiquement. Confondre ces deux notions est une source classique d'erreurs, particulièrement lorsqu'il s'agit de respecter les limites d'un champ en base de données ou la taille maximale d'une requête réseau.
Si vous demandez à Python la longueur du mot "café", il vous répondra logiquement 4. Pourtant, une fois encodé avec la méthode .encode(), il pèse 5 octets.
>>> len("café")
4
>>> len("café".encode("utf-8"))
5
Pourquoi cette différence ? Le caractère accentué é nécessite un octet supplémentaire. La fonction len() compte les caractères visibles (4), tandis que .encode() calcule les octets nécessaires (5).
| Caractère | Poids en UTF-8 | len() Python |
|---|---|---|
A |
1 octet | 1 |
é |
2 octets | 1 |
字 |
3 octets | 1 |
🙂 |
4 octets | 1 |
Piège courant
La fonction len() mesure la longueur en caractères, jamais le poids en octets. La longueur n'est pas le poids.
ASCII : quand 1 caractère = 1 octet
L'idée qu'un caractère = un octet est intuitive et vient d'un standard historique : ASCII (American Standard Code for Information Interchange). Créé dans les années 1960, il a ancré cette règle dans l'informatique naissante.
ASCII utilise seulement 7 bits pour encoder l'information. Il définit 128 symboles possibles : lettres anglaises, chiffres, ponctuation et caractères de contrôle. Puisque l'unité de base de la mémoire est l'octet (8 bits), chaque caractère ASCII tient naturellement dans un seul octet.
| Caractère | Code ASCII (décimal) | Octets |
|---|---|---|
A |
65 | 1 |
a |
97 | 1 |
0 |
48 | 1 |
>>> len("Hello".encode("ascii"))
5
Pendant longtemps, pour l'anglais, la règle 1 caractère = 1 octet était donc vraie. La difficulté est apparue lorsque l'informatique a dû représenter des accents, des alphabets non latins et, plus tard, des emojis.
Unicode : le dictionnaire universel
Pour sortir du chaos des encodages incompatibles, un standard global a été conçu : Unicode. Il faut insister sur un point essentiel : Unicode n'est pas un encodage.
C'est un immense répertoire de caractères. Son rôle exclusif consiste à recenser les symboles du monde entier (lettres latines, idéogrammes, emojis) et à leur attribuer un identifiant unique appelé code point.
Voici un extrait de quelques blocs représentatifs pour illustrer comment Unicode organise ce gigantesque répertoire :
| Bloc | Plage de code points | Exemple |
|---|---|---|
| Latin de base | U+0000 à U+007F |
A (U+0041) |
| Latin étendu | U+0080 à U+017F |
é (U+00E9) |
| CJK unifié | U+4E00 à U+9FFF |
字 (U+5B57) |
| Émoticônes | U+1F600 à U+1F64F |
🙂 (U+1F642) |
En Python, vous pouvez explorer ces identifiants avec ord().
>>> ord("é"), hex(ord("é"))
(233, '0xe9')
Unicode définit donc quoi représenter. Il ne précise pas comment ce caractère sera stocké en mémoire, écrit dans un fichier ou transmis sur le réseau. C'est le rôle des encodages.
UTF-8 : l'encodage à longueur variable
Pour transformer les code points Unicode en octets réels, il faut utiliser un encodage. Le plus répandu aujourd'hui est UTF-8.
UTF-8 utilise entre 1 et 4 octets par caractère. Les caractères ASCII conservent leur représentation sur un seul octet, ce qui garantit une rétrocompatibilité parfaite avec l'ASCII. En revanche, les caractères accentués, les idéogrammes et les emojis demandent davantage d'espace.
Le tableau ci-dessous représente les octets en notation hexadécimale (base 16), la convention habituelle pour exprimer des valeurs binaires de façon compacte.
| Plage de code points | Nombre d'octets | Préfixe binaire | Exemple (hexadécimal) |
|---|---|---|---|
U+0000-U+007F |
1 | 0xxxxxxx |
A → 41 |
U+0080-U+07FF |
2 | 110xxxxx 10xxxxxx |
é → C3 A9 |
U+0800-U+FFFF |
3 | 1110xxxx ... |
字 → E5 AD A6 |
U+10000-U+10FFFF |
4 | 11110xxx ... |
🙂 → F0 9F 99 82 |
>>> "café".encode("utf-8")
b'caf\xc3\xa9'
>>> len("🙂".encode("utf-8"))
4
Dans cet exemple, le caractère é devient deux octets : \xc3 et \xa9. UTF-8 traduit les caractères Unicode en octets selon leur plage de valeurs.
str et bytes : la philosophie de Python 3
Python 3 a dressé un mur étanche entre ce qui relève de l'humain (le texte) et ce qui relève de la machine (les octets bruts).
Le type str représente du texte Unicode, c'est-à-dire une suite de caractères. Le type bytes représente une suite d'octets bruts, prête à être écrite dans un fichier ou envoyée sur le réseau.
>>> type("café")
<class 'str'>
>>> type("café".encode("utf-8"))
<class 'bytes'>
| Type | Contenu | Ce que mesure len() |
Usage typique |
|---|---|---|---|
str |
Caractères Unicode | Nombre de caractères | Affichage, traitement |
bytes |
Octets bruts | Nombre d'octets | Fichiers, réseau |
Pour passer de l'un à l'autre, Python exige une conversion explicite :
texte.encode("utf-8"): transforme unstrenbytesdonnees.decode("utf-8"): transforme desbytesenstr
Cette séparation évite de nombreux bugs silencieux et oblige à préciser le moment où le texte devient une donnée binaire.
Calculer le poids d'un texte
Puisque la fonction len() ne compte que les caractères, la méthode standard pour calculer le poids réel d'un texte consiste à l'encoder d'abord, puis à mesurer la longueur du résultat binaire.
>>> texte = "café🙂"
>>> len(texte.encode("utf-8"))
9
La formule générale est donc la suivante :
len(texte.encode("utf-8"))
Le résultat final dépend toujours de l'encodage choisi. Un même texte n'a donc pas un poids absolu : il a un poids dans un encodage donné.
À retenir
Si vous voulez connaître la taille réelle d'un texte pour l'écrire dans un fichier, l'envoyer à une API ou le transmettre sur le réseau, vous devez toujours préciser l'encodage cible.
Le piège de sys.getsizeof()
Lorsqu'on découvre le module sys, on peut être tenté d'utiliser sys.getsizeof() pour mesurer la taille d'une chaîne. Ce n'est pas la bonne approche pour notre besoin.
sys.getsizeof() renvoie la taille mémoire de l'objet Python lui-même. Cela inclut non seulement les données utiles, mais aussi la structure interne et les métadonnées utilisées par l'interpréteur CPython.
>>> import sys
>>> sys.getsizeof("café")
61
>>> len("café".encode("utf-8"))
5
Dans cet exemple, 61 correspond à l'occupation mémoire de l'objet en RAM, tandis que 5 correspond bien au poids réel du texte encodé en UTF-8.
Erreur fréquente
sys.getsizeof() ne doit jamais être utilisé pour estimer la taille d'un texte destiné à être écrit sur un disque, envoyé sur un réseau ou validé par une API.
Ce qu'il faut retenir
À ce stade, quelques idées fondamentales doivent être bien installées :
- ASCII repose sur un monde historique limité où 1 caractère = 1 octet.
- Unicode n'est pas un encodage, mais un dictionnaire universel fournissant un identifiant unique pour chaque caractère.
- UTF-8 est l'encodage qui convertit ces identifiants en une suite d'octets de longueur variable (de 1 à 4).
- En Python 3,
len()appliqué à unstrmesure des caractères, jamais des octets. - Pour obtenir le poids réel, il faut d'abord utiliser
.encode()pour sérialiser la chaîne enbytes, puis mesurer sa longueur.
Autrement dit, un texte n'a pas de poids en soi. Il acquiert un poids physique exact au moment où vous choisissez comment l'encoder pour la machine.