2.6. Texte et octets¶
Python possède deux types de séquence pour les données de caractères brutes :
str– une séquence de points de code Unicode. Utilisé pour tout texte lisible par un humain : chemins de fichiers, messages de journal, charges utiles JSON.bytes– une séquence d’entiers compris entre 0 et 255. Utilisé pour les données binaires brutes : trames UART, tampons d’image, paquets réseau, valeurs de registre.
Ils ne peuvent pas être mélangés sans une conversion explicite. Passer une str à une méthode matérielle write lève TypeError, et l’inverse est également rejeté.
Une str stocke des caractères Unicode ; une bytes stocke des octets bruts. Passer de l’un à l’autre, c’est encoder (str → bytes) et décoder (bytes → str).¶
2.6.1. Littéraux bytes¶
Un littéral bytes est un littéral de type chaîne préfixé par b :
header = b"OMV"
crlf = b"\r\n"
payload = b"\x01\x02\x03"
Seuls les caractères ASCII sont autorisés directement à l’intérieur d’un littéral bytes ; les valeurs non ASCII doivent être écrites sous forme d’échappements hexadécimaux \xHH.
2.6.2. Encodage et décodage¶
str.encode()convertit une chaîne en octets selon un encodage nommé (par défaut"utf-8").bytes.decode()effectue l’opération inverse.
>>> "hello".encode()
b'hello'
>>> "héllo".encode()
b'h\xc3\xa9llo' # é is two bytes in UTF-8
>>> b"hello".decode()
'hello'
UTF-8 est l’encodage par défaut et le bon choix pour tout ce qui pourrait contenir des caractères non ASCII. N’utilisez "ascii" que lorsque les données sont garanties être de l’ASCII pur ; ainsi, un octet non ASCII errant lève UnicodeError au lieu de passer inaperçu.
2.6.3. Indexation et découpage¶
Une valeur bytes se comporte comme une séquence d’entiers lorsqu’on l’indexe, et non comme une séquence de chaînes d’un octet :
>>> data = b"abc"
>>> data[0]
97 # the int 97, not 'a'
>>> data[0:1]
b'a' # slicing returns bytes
Une erreur courante consiste à comparer data[0] == "a" et à s’étonner que ce soit False – data[0] est un entier, et non une chaîne d’un caractère, si bien que les deux valeurs ne peuvent jamais correspondre.
2.6.4. ord et chr – faire le pont entre caractères et entiers¶
Comme l’indexation d’une bytes renvoie un entier alors que le reste du programme raisonne probablement en termes de caractères, Python fournit deux fonctions intégrées pour passer de l’un à l’autre :
ord()– prend une chaîne d’un caractère et renvoie son point de code entier.chr()– l’inverse : à partir d’un entier, renvoie la chaîne d’un caractère correspondant à ce point de code.
>>> ord("a")
97
>>> chr(97)
'a'
>>> ord("A"), chr(0x41)
(65, 'A')
Pour les caractères ASCII, le point de code est égal à la valeur de l’octet, de sorte que ord("a") et b"a"[0] donnent tous deux 97. Cela permet de lire les comparaisons d’octets en termes du caractère qui vous intéresse réellement :
>>> data = b"abc"
>>> data[0] == ord("a") # instead of the magic number 97
True
Et chr() est pratique pour la journalisation ou le débogage lorsque vous voulez voir la forme imprimable d’un octet :
>>> chr(data[0])
'a'
Pour les caractères non ASCII, ord() renvoie le point de code Unicode, qui n’est pas le même qu’un quelconque octet unique de la forme encodée ; la représentation en octets dépend de l’encodage.
2.6.5. bytearray pour les tampons mutables¶
bytes est immuable – chaque « modification » renvoie un nouvel objet et laisse l’original inchangé. Pour les données que vous comptez modifier, compléter ou remplir morceau par morceau, utilisez bytearray. Il contient le même contenu qu’une bytes mais prend en charge la mutation sur place :
>>> s = b"hello"
>>> s[0] = ord("H")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
>>> s = bytearray(b"hello")
>>> s[0] = ord("H")
>>> s
bytearray(b'Hello')
2.6.5.1. Créer un bytearray¶
Le constructeur bytearray accepte plusieurs entrées :
bytearray(8)– un tampon de 8 octets nuls.bytearray(b"hello")– une copie mutable d’une valeur bytes.bytearray("hello", "utf-8")– un bytearray à partir d’une chaîne, en utilisant l’encodage donné.bytearray([72, 73, 74])– un bytearray à partir d’une séquence d’entiers compris entre 0 et 255 (ici,b"HIJ").
>>> bytearray(4)
bytearray(b'\x00\x00\x00\x00')
>>> bytearray(b"abc")
bytearray(b'abc')
>>> bytearray("café", "utf-8")
bytearray(b'caf\xc3\xa9')
2.6.5.2. Modifier un bytearray¶
L’affectation indexée et par tranche fonctionne exactement comme pour une list :
>>> buf = bytearray(8) # 8 zero bytes
>>> buf[0] = 0xFF # one byte at a time
>>> buf[1:4] = b"ABC" # replace a slice
>>> buf
bytearray(b'\xffABC\x00\x00\x00\x00')
Les octets individuels doivent être des entiers compris entre 0 et 255 ; affecter tout autre type lève TypeError ou ValueError.
L’affectation par tranche peut modifier la longueur du tampon. Remplacer une tranche par une valeur plus longue agrandit le bytearray ; le remplacer par une valeur plus courte le rétrécit. Le remplacer par b"" supprime entièrement la tranche :
>>> buf = bytearray(b"abcdef")
>>> buf[1:3] = b"XYZ" # 2 bytes replaced with 3
>>> buf
bytearray(b'aXYZdef')
>>> buf[1:4] = b"" # delete the inserted run
>>> buf
bytearray(b'adef')
Les méthodes bytearray.append() et bytearray.extend() ajoutent des octets à la fin sans réallouer tout le tampon à chaque fois :
>>> buf = bytearray()
>>> buf.append(0x01)
>>> buf.extend(b"abc")
>>> buf
bytearray(b'\x01abc')
2.6.5.3. Lire depuis un bytearray¶
L’indexation, le découpage, l’itération et les méthodes d’inspection de bytes (bytes.startswith(), bytes.find(), bytes.strip(), etc.) fonctionnent toutes de la même manière que sur une valeur bytes. L’indexation renvoie un entier ; le découpage renvoie un autre bytearray :
>>> buf = bytearray(b"OpenMV")
>>> buf[0]
79
>>> buf[0:4]
bytearray(b'Open')
>>> buf.startswith(b"Open")
True
2.6.5.4. Convertir entre bytes et bytearray¶
bytes et bytearray se convertissent l’un en l’autre à l’aide de leurs constructeurs. Utilisez cela lorsqu’une API exige spécifiquement l’une des deux formes :
>>> ba = bytearray(b"hello")
>>> snapshot = bytes(ba) # immutable copy
>>> ba[0] = ord("H")
>>> ba, snapshot
(bytearray(b'Hello'), b'hello')
2.6.5.5. memoryview pour le découpage sans copie¶
Le découpage d’une bytes ou d’une bytearray copie normalement les octets dans un nouveau tampon. memoryview expose les mêmes octets sans les copier :
>>> buf = bytearray(b"OpenMV Cam")
>>> view = memoryview(buf)
>>> view[0:6] # shares storage with buf
<memoryview ...>
>>> bytes(view[0:6]) # materialise as bytes when needed
b'OpenMV'
Une vue sur un bytearray est également accessible en écriture – muter la vue mute le tampon sous-jacent :
>>> view[0] = ord("o")
>>> buf
bytearray(b'openMV Cam')
Recourez à memoryview lorsque copier une tranche serait du gaspillage – typiquement lorsque le même grand tampon est passé de fonction en fonction ou traité par morceaux. Pour le travail quotidien de type chaîne sur de petites séquences d’octets, le découpage ordinaire convient parfaitement.