Table des matières
Type Byte
Dans un fichier, qu'il s'agisse d'une image, d'un programme ou d'un texte, les données brutes sont sous une forme binaire. Puis, selon le type de fichier, on pourra interpréter ces données binaires. Elles représentent des caractères ou bien des pixels…
Le type Byte correspond à une interprétation binaire. Byte est devenu un équivalent de octet.
Cas d'un texte
>>> texte = "Un éléphant"
>>> texte.encode("utf8")
b'Un \xc3\xa9l\xc3\xa9phant'
La fonction encode indique que l'on veut traduire ce texte dans sa version codée en binaire et on précise le type d'encodage désiré.
Le résultat ressemble à une chaîne de texte mais ce n'en est pas une. b'Un \xc3\xa9l\xc3\xa9phant' est juste une façon de représenter une succession d'octets.
- Le
ben tête indique qu'il s'agit d'octets. - Les octets sont représentés sous forme d'un caractère quand il s'agit de caractères ASCII. Par exemple, dans la chaîne d'origine, le
"U"a été encodé, en UTF8, par l'octet 85. Or en ASCII, 85 correspond aussi à"U"– on a la même situation pour tous les caractères de base. Au lieu d'écrire 85, Python écrit'U'. - Le
"é"de éléphant est encodé en UTF8 par les octets0xC3 0xA9, et ces deux octets ne correspondent à rien en ASCII. Python écrit donc\xc3\xa9.
On peut choisir d'afficher un octet en particulier. Le type Bytes fonctionne alors comme tous les indexables Python, avec des [] :
>>> b'Un \xc3\xa9l\xc3\xa9phant'[0] 85 >>> b'Un \xc3\xa9l\xc3\xa9phant'[3] 195
195 n'est autre que l'hexadécimal 0xc3.
>>> type(b'Un \xc3\xa9l\xc3\xa9phant') <class 'bytes'>
On peut passer du type bytes au type str en décodant :
>>> b'Un \xc3\xa9l\xc3\xa9phant'.decode('utf8')
'Un éléphant'
Cas d'un fichier texte
Prenons un fichier texte file.txt contenant le texte suivant, encodé en UTF8 :
Un éléphant traverse le zoo. Le gardien le regarde.
On pourrait ouvrir ce fichier avec Python :
>>> f = open("file.txt", "r", encoding="utf8")
>>> texte = f.read()
>>> f.close()
>>> type(text)
<class 'str'>
>>> texte
'Un éléphant traverse le zoo.\nLe gardien le regarde.\n'
\n est le caractère de retour à la ligne.
Vous remarquez que le texte a été ouvert dans sa version texte ce qui a nécessité un décodage. On aurait pu ouvrir le fichier pour extraire les données brutes, en binaire. Pour cela on utilise "rb" et on n'a plus besoin d'encodage puisqu'on ne décode plus.
>>> f = open("file.txt", "rb")
>>> octets = f.read()
>>> f.close()
>>> type(octets)
<class 'bytes'>
>>> octets
b'Un \xc3\xa9l\xc3\xa9phant traverse le zoo.\nLe gardien le regarde.\n'
octets est maintenant de type bytes. L'affichage qui ressemble à du texte nous masque un peu la réalité. On peut forcer un affichage hexadécimal – nécessite un peu de formatage – Voici les octets, sous forme hexadécimale, représentant ce texte :
>>> ["{:02x}".format(octet) for octet in octets]
['55', '6e', '20', 'c3', 'a9', '6c', 'c3', 'a9', '70', '68', '61', '6e', '74', '20', '74', '72', '61', '76', '65', '72', '73', '65', '20', '6c', '65', '20', '7a', '6f', '6f', '2e', '0a', '4c', '65', '20', '67', '61', '72', '64', '69', '65', '6e', '20', '6c', '65', '20', '72', '65', '67', '61', '72', '64', '65', '2e', '0a']
On peut même aller un peu plus loin et écrire la suite de 0 et de 1 correspondant au fichier. Ci-dessous :
"{:08b}".format(octet)produit une écriture sur 8 bits de l'entieroctetfor octet in octetspermet d'énumérer les octets"".join([...])permet de coller les octets les uns à la suite des autres
>>> "".join(["{:08b}".format(octet) for octet in octets])
'010101010110111000100000110000111010100101101100110000111010100101110000011010000110000101101110011101000010000001110100011100100110000101110110011001010111001001110011011001010010000001101100011001010010000001111010011011110110111100101110000010100100110001100101001000000110011101100001011100100110010001101001011001010110111000100000011011000110010100100000011100100110010101100111011000010111001001100100011001010010111000001010'
L'option "wb" permet d'écrire des bytes dans le fichier.
Saisie directe
On peut créer une donnée de type Bytes directement.
>>> b = bytes([29, 74]) >>> type(b) <class bytes> >>> b b'\x1dJ'
Dans la représentation, \x1d est l'hexadécimal 0x1D = 29 – ne correspond pas à un caractère ASCII affichable – et 'J' est le caractère pour le code ASCII 74.
Quelques propriétés
Comme les tuple ou str, le type bytes est immuable.
Voici quelques manipulations.
>>> b = bytes([18, 91, 252]) >>> b[0] # accède à l'élément de rang 0 18 >>> b[0] = 12 # interdit : on n'a pas le droit de modifier un élément, comme dans un tuple (immuable) TypeError: 'bytes' object does not support item assignment >>> b'\x03\x15' * 3 # comme un str, crée un bytes avec 3x le même motif b'\x03\x15\x03\x15\x03\x15' >>> b'\x03\x15' + b'\x11\x27' # concaténation b'\x03\x15\x11\x27'
Conversion float, int - bytes
On a vu qu'il était facile de passer de bytes à str et réciproquement par le biais de l'encodage.
Peut-on convertir aussi un nombre ? On pourrait très bien utiliser un nombre comme $\pi$ et en faire une clé de chiffrement !
Pour un entier positif
Il n'y a pas de solution toute faite. Il faut écrire une fonction.
def int_to_bytes(n:int)->bytes:
octets = []
while n > 0:
octets.append(n % 256)
n = n >> 8 # décalage de 8 bits
# le poids fort est à droite, il faut inverser avec octets[::-1]
return bytes(octets[::-1])
Exemple d'utilisation :
>>> int_to_bytes(47024623697421286865352) b'\t\xf55\xbf\xd9\xab\x9b\xe1y\xc8'
On peut aussi utiliser la fonction intégrée to_bytes mais il faut connaître le nombre d'octets du résultat :
>>> (47024623697421286865352).to_bytes(10, 'big') b'\t\xf55\xbf\xd9\xab\x9b\xe1y\xc8'
Dans l'autre sens on peut reconstruire à partir des octets.
C'est plus compliqué pour un entier négatif car il faut tenir compte du complément à 2.
def bytes_to_int(B:bytes) -> int:
n = 0
poids = 1
for b in B[::-1]:
n += poids*b
poids *= 256
return n
Test :
>>> bytes_to_int(b'\t\xf55\xbf\xd9\xab\x9b\xe1y\xc8') 47024623697421286865352
Là encore il existe une fonction intégrée :
>>> int.from_bytes(b'\t\xf55\xbf\xd9\xab\x9b\xe1y\xc8', byteorder = 'big') 47024623697421286865352
Pour un float
Pour cela on va utiliser le module struct qui permet d'obtenir la forme bytes de divers type de données.
>>> import struct
>>> from math import pi
>>> struct.pack(">d", pi) # d pour double, flottant sur 64 bits
b'@\t!\xfbTD-\x18'
comment comprendre ce résultat ?
b'@\t!\xfbTD-\x18' s'écrit en binaire 01000000 00001001 00100001 11111011 01010100 01000100 00101101 00011000
- Le premier bit est
0, c'est bien un nombre positif - Les 11 bits qui suivent sont
10000000000ce qui représente 1024, l'exposant est donc 1024 - 1023 = 1 - Les bits suivants
1001 00100001 11111011 01010100 01000100 00101101 00011000représentent la partie après la virgule en binaire.
Le nombre est donc $1,1001\,00100001\cdots \times 2^1 = 11,00100100001\cdots$. En convertissant en décimal on retrouve l'expression habituelle de $\pi$.
Dans l'autre sens :
>>> struct.unpack(">d", b'@\t!\xfbTD-\x18')
(3.141592653589793,)
Le résultat est un tuple même s'il ne contient qu'un élément.
