Document : Calculer le poids d'un texte en Python

Calculer le poids d'un texte en Python

  • admin
  • 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 A41
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 un str en bytes
  • donnees.decode("utf-8") : transforme des bytes en str

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é à un str mesure des caractères, jamais des octets.
  • Pour obtenir le poids réel, il faut d'abord utiliser .encode() pour sérialiser la chaîne en bytes, 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.