Python 3 a introduit une distinction fondamentale entre la gestion du texte et des données binaires. Le texte, encodé en Unicode, est représenté par le type str, tandis que les flux binaires sont représentés par le type bytes. Python 3 ne permet aucun mélange implicite entre ces deux types : vous ne pouvez pas concaténer une chaîne de caractères avec un flux d'octets, rechercher une chaîne dans un flux binaire (ou l'inverse), ni passer une chaîne à une focntion attendant des octets (ou l'inverse).
Évolution historique du codage
Pour bien comprendre bytes et str, il est utile de rappeler l'historique des codages de caractères.
Au début de l'informatique, l'industrie était dominée par les pays anglophones. L'alphabet latin (26 lettres) suffisait pour représenter l'anglais. Ainsi, le premier standard largement adopté fut ASCII, un codage sur 8 bits (1 octet). Il permettait de représenter jusqu'à 255 caractères différents, couvrant ainsi les besoins des langues basées sur l'alphabet latin.
Un codage de caractères est un système permettant de convertir un symètre (lettre, chiffre, signe de ponctuation, idéogramme...) en une séquence binaire (des 0 et 1) que l'ordinateur peut stocker et traiter. À l'origine, les utilisateurs finaux n'avaient pas besoin de connaître ce processus, mais les développeurs, eux, doivent le maîtriser.
Par exemple, en ASCII, le caractère « A » majuscule est représenté par la séquence binaire 01000001, ou de manière équivalente par la valeur décimale 65.
Avancée technique : ASCII utilise 7 bits pour le codage de base, mais est souvent stocké sur 8 bits. Cependant, 255 combinaisons ne suffisent pas pour les langues non latines comme le chinois, le japonais ou le coréen. Le standard Unicode a alors été développé. Il définit un jeu universel de caractères, où chaque symbole (quelle que soit sa langue d'origine) peut être représenté sur au moins 2 octets, parfois plus (par exemple, 3 octets pour un idéogramme chinois courant).
Bien qu'Unicode soit exhaustif, il présente des inconvénients : il n'est pas rétrocompatible avec l'ASCII et consomme plus d'espace mémoire. En effet, pour un texte essentiellement composé de caractères latins, utiliser 2 octets par caractère là où 1 seul aurait suffi en ASCII est une perte d'efficacité.
C'est pourquoi le codage UTF-8 a gagné en popularité. C'est une forme d'encodage Unicode qui utilise un nombre variable d'octets : 1 octet pour les caractères ASCII (assurant ainsi la compatibilité), 3 octets pour la plupart des idéogrammes courants, etc. UTF-8 est aujourd'hui l'encodage le plus répandu sur le Web.
À noter que d'autres codages régionaux existent, comme GBK, GB2312 ou BIG5 pour le chinois, qui ne sont pas universels. Par exemple, en GBK, un idéogramme chinois courant occupe 2 octets.
Distinguons bytes et str
Le type bytes représente une séquence brute de valeurs octets (0-255), une suite de bits. Sa forme lisible (b'\xe4\xb8\xad\xe6\x96\x87') n'est qu'une représentation hexadécimale de commodité pour le développeur.
Le type str, lui, représente du texte (une séquence de caractères Unicode) déjà décodé, prêt à être lu et manipulé par l'humain.
Considérons cet exemple :
texte_original = "Bonjour"
print(type(texte_original)) # <class 'str'>
# Conversion explicite en bytes avec l'encodage UTF-8
donnees_binaires = texte_original.encode('utf-8')
print(donnees_binaires) # b'Bonjour'
print(type(donnees_binaires)) # <class 'bytes'>
# Longueur en octets
print(len(donnees_binaires)) # 7 octets
La méthode encode() des objets str transforme la chaîne en une séquence d'octets selon un encodage spécifié. Inversement, la méthode decode() des objets bytes reconstitue la chaîne de caractères à partir des octets, à condition de connaître l'encodage d'origine.
donnees_binaires_utf8 = 'Pythön'.encode('utf-8') # Le 'ö' prend 2 octets en UTF-8
donnees_binaires_latin1 = 'Pythön'.encode('latin-1') # Le 'ö' prend 1 octet en latin-1
print(f"UTF-8: {donnees_binaires_utf8}, longueur: {len(donnees_binaires_utf8)}")
print(f"Latin-1: {donnees_binaires_latin1}, longueur: {len(donnees_binaires_latin1)}")
# Tentative de décodage avec le mauvais encodage
try:
chaine_incorrecte = donnees_binaires_utf8.decode('latin-1')
print(chaine_incorrecte) # Affichera des caractères incohérents
except UnicodeDecodeError as e:
print(f"Erreur de décodage: {e}")
Essentiel à retenir :
- Lors de la lecture ou l'écriture d'un fichier texte, Python gère généralement l'encodage/décodage automatiquement si le mode est spécifié comme
'r'ou'w'(et souvent en utilisant le codage par défaut du système, comme UTF-8). - Si vous travaillez avec des données brutes (fichiers images, réseau, sérialisation), vous manipulez des objets
byteset êtes responsable de leur interprétation. - Python applique une séparation stricte entre les deux types. Tenter une opération mixte (concaténation, comparaison, passage en argument) lèvera une
TypeError.
Voici comment convertir entre les types en spécifiant explicitement l'encodage :
chaine_texte = "算法"
# Conversion str -> bytes (encodage)
octets_utf8 = chaine_texte.encode('utf-8')
print(octets_utf8) # b'\xe7\xae\x97\xe6\xb3\x95'
print(type(octets_utf8)) # <class 'bytes'>
# Conversion bytes -> str (décodage)
texte_recupere = octets_utf8.decode('utf-8')
print(texte_recupere) # 算法
print(type(texte_recupere)) # <class 'str'>
# Même contenu, encodage différent
octets_gbk = chaine_texte.encode('gbk')
print(octets_gbk) # b'\xcb\xe3\xb7\xa8'
print(len(octets_gbk)) # 4 octets en GBK, contre 6 en UTF-8