|
|
@@ -0,0 +1,314 @@ |
|
|
|
Données binaires |
|
|
|
================ |
|
|
|
|
|
|
|
Introduction : chiffres et nombres |
|
|
|
----------------------------------- |
|
|
|
|
|
|
|
Si je vous parle de ce que représente le texte: ``342`` vous pouvez le |
|
|
|
voir de deux façons: |
|
|
|
|
|
|
|
1. C'est une **suite de chiffres**: ``3``, puis ``4``, puis ``2``. |
|
|
|
1. C'est un **nombre** (quelque part entre 300 et 350) |
|
|
|
|
|
|
|
On se sert des *chiffres* de 0 à 9 pour *représenter* des *nombres* |
|
|
|
|
|
|
|
La base 10 |
|
|
|
---------- |
|
|
|
|
|
|
|
Plus exactement, pour passer de la suite de chiffres ``342`` au nombre, |
|
|
|
on part de la fin, puis on ajoute chaque chiffre, multiplié par la puissance |
|
|
|
de 10 adéquate:: |
|
|
|
|
|
|
|
2 * 1 |
|
|
|
+ 4 * 10 |
|
|
|
+ 2 * 10 * 10 |
|
|
|
|
|
|
|
soit:: |
|
|
|
|
|
|
|
2 |
|
|
|
+ 40 |
|
|
|
+ 300 |
|
|
|
|
|
|
|
ce qui fait bien 342. |
|
|
|
|
|
|
|
|
|
|
|
La base 16 |
|
|
|
---------- |
|
|
|
|
|
|
|
En informatique, on se sert souvent de la base 16. C'est le même principe: on se |
|
|
|
sert des "chiffres" de 0 à F (A vaut 10, B vaut 11, jusqu'à F qui vaut 15) |
|
|
|
|
|
|
|
Ainsi, la suite ``DA2`` peut être interprétée comme suit :: |
|
|
|
|
|
|
|
2 * 1 |
|
|
|
+ 10 * 16 |
|
|
|
+ 13 * 16 * 16 |
|
|
|
|
|
|
|
soit :: |
|
|
|
|
|
|
|
2 |
|
|
|
+ 160 |
|
|
|
+ 3328 |
|
|
|
|
|
|
|
soit 3746 |
|
|
|
|
|
|
|
On appelle aussi la base 16 la base *hexadécimale*, ou *hexa* en abrrégé. |
|
|
|
|
|
|
|
La base 2 |
|
|
|
---------- |
|
|
|
|
|
|
|
La base 2 c'est pareil, mais avec deux "chiffres" - 0 et 1. |
|
|
|
|
|
|
|
Ainsi, la suite `110` peut être interprétée comme suit :: |
|
|
|
|
|
|
|
0 * 1 |
|
|
|
+ 1 * 2 |
|
|
|
+ 1 * 2 * 2 |
|
|
|
|
|
|
|
soit :: |
|
|
|
|
|
|
|
0 |
|
|
|
+ 2 |
|
|
|
+ 4 |
|
|
|
|
|
|
|
soit 6. |
|
|
|
|
|
|
|
|
|
|
|
Bits et octets |
|
|
|
-------------- |
|
|
|
|
|
|
|
* Un bit (*bit* en anglais) c'est la valeur 1 ou 0 |
|
|
|
* Un octet (*byte* en anglais) c'est une suite de 8 bits |
|
|
|
|
|
|
|
À retenir |
|
|
|
--------- |
|
|
|
|
|
|
|
**Ces paquets de 8 ne veulent rien dire en eux-mêmes**. |
|
|
|
Ils n'ont de sens que dans le cadre d'une *convention*. |
|
|
|
|
|
|
|
Par exemple, l'octet '10100100' peut être un nombre écrit en |
|
|
|
binaire (164 en l'occurrence), mais peut avoir une toute |
|
|
|
autre signification |
|
|
|
|
|
|
|
Le nombre de valeurs possible augmente *très* rapidement avec le nombre d'octets: |
|
|
|
|
|
|
|
* 1 octet, c'est 255 valeurs possibles (``2 ** 8``) |
|
|
|
* 2 octets, c'est 65.536 valeurs possibles (``2 ** 16``) |
|
|
|
* 4 octets, c'est 4.294.967.296 valeurs possibles (``2 ** 32``) |
|
|
|
|
|
|
|
Bases en Python |
|
|
|
--------------- |
|
|
|
|
|
|
|
On se sert des préfixes `0b` et `0x` pour noter |
|
|
|
les nombres en base binaire ou hexadécimale respectivement:: |
|
|
|
|
|
|
|
print(0b1110) |
|
|
|
# affiche: 6 |
|
|
|
|
|
|
|
print(0xDA2) |
|
|
|
# affiche: 3490 |
|
|
|
|
|
|
|
Poids des bits |
|
|
|
-------------- |
|
|
|
|
|
|
|
Regardez l'example suivant:: |
|
|
|
|
|
|
|
x = 0b0010010 # 18 |
|
|
|
y = 0b0010011 # 19 |
|
|
|
z = 0b1010010 # 82 |
|
|
|
|
|
|
|
Notez que le premier bit est plus "fort" que le dernier on dit qu'on est en "little endian". |
|
|
|
|
|
|
|
|
|
|
|
Manipuler des octets en Python |
|
|
|
------------------------------ |
|
|
|
|
|
|
|
On peut construrie des listes d'octets en utilsant ``bytearray`` et |
|
|
|
une liste de nombres:: |
|
|
|
|
|
|
|
data = bytearray( |
|
|
|
[0b1100001, |
|
|
|
0b1100010, |
|
|
|
0b1100011 |
|
|
|
] |
|
|
|
) |
|
|
|
|
|
|
|
# equivalent: |
|
|
|
data = bytearray([97,98,99]) |
|
|
|
|
|
|
|
# equivalent aussi: |
|
|
|
data = bytearray([0x61, 0x62, 0x63] |
|
|
|
|
|
|
|
|
|
|
|
Texte |
|
|
|
----- |
|
|
|
|
|
|
|
On peut aussi interpréter des octets comme du texte - c'est la table ASCII |
|
|
|
|
|
|
|
.. image:: ../img/ascii-table.png |
|
|
|
|
|
|
|
|
|
|
|
ASCII - remarques |
|
|
|
----------------- |
|
|
|
|
|
|
|
* C'est *vieux* - 1960 |
|
|
|
* Le A est pour American |
|
|
|
* Ça sert à *envoyer* du texte sur des terminaux d'où les "caractères" non-imprimables dans la liste |
|
|
|
* Mais c'est une convention *très* utilisée |
|
|
|
* Techniquement, on n'a besoin que de 7 bits, mais on préfère envoyer des octets |
|
|
|
|
|
|
|
Utiliser ASCII en Python |
|
|
|
------------------------ |
|
|
|
|
|
|
|
Avec ``chr`` et ``ord``:: |
|
|
|
|
|
|
|
x = chr(98) |
|
|
|
print(x) |
|
|
|
# affiche: b |
|
|
|
|
|
|
|
x = ord('a') |
|
|
|
print(x) |
|
|
|
# affiche: 97 |
|
|
|
|
|
|
|
Affichage des bytearrays en Python |
|
|
|
---------------------------------- |
|
|
|
|
|
|
|
Python utilise ASCII pour afficher les bytearrays si les caractères sont "imprimables":: |
|
|
|
|
|
|
|
data = bytearray([97,98,99]) |
|
|
|
print(data) |
|
|
|
# affiche: bytearray(b"abc") |
|
|
|
|
|
|
|
Et ``\x`` et le code hexa sinon:: |
|
|
|
|
|
|
|
data = bytearray([7, 69, 76, 70]) |
|
|
|
print(data) |
|
|
|
# affiche: bytearray(b"\x07ELF") |
|
|
|
|
|
|
|
Notez bien que ce qu'affiche Python n'est qu'une *interpétation* d'une séquence d'octets. |
|
|
|
|
|
|
|
Types |
|
|
|
----- |
|
|
|
|
|
|
|
La variable `b"abc"` est une "chaîne d'octets", de même que `"abc"` est une "chaîne de caractères". |
|
|
|
|
|
|
|
Python apelle ces types `bytes` et `str`:: |
|
|
|
|
|
|
|
print(type("abc")) |
|
|
|
# affiche: str |
|
|
|
|
|
|
|
print(type(b"abc")) |
|
|
|
# affiche: bytes |
|
|
|
|
|
|
|
|
|
|
|
bytes et bytearray |
|
|
|
------------------ |
|
|
|
|
|
|
|
De la même manière qu'on ne peut pas modifier un caractère à l'intérieur une string, on ne peut |
|
|
|
pas modifier un bit - ou un octet dans une variable de type `bytes`:: |
|
|
|
|
|
|
|
a = "foo" |
|
|
|
# a[0] = "f" => TypeError: 'str' object does not support item assignment |
|
|
|
|
|
|
|
b = b"foo" |
|
|
|
# b[0] = 1 => TypeError: 'bytes' object does not support item assignment |
|
|
|
|
|
|
|
Par contre on peut modifier un bytearray:: |
|
|
|
|
|
|
|
b = bytearray(b"foo") |
|
|
|
b[0] = 103 |
|
|
|
print(b) |
|
|
|
# affiche: bytearray(b"goo") |
|
|
|
|
|
|
|
Conversion bytes - texte |
|
|
|
------------------------ |
|
|
|
|
|
|
|
Avec ``encode()`` et ``decode()``:: |
|
|
|
|
|
|
|
text = "chaise" |
|
|
|
encodé = text.encode("ascii") |
|
|
|
print(encodé) |
|
|
|
# affiche: b"chaise" |
|
|
|
|
|
|
|
bytes = b"table" |
|
|
|
décodé = bytes.decode("ascii") |
|
|
|
print(décodé) |
|
|
|
# affiche: b"table" |
|
|
|
|
|
|
|
|
|
|
|
Notez que dans le deuxième exemple, on est bien en train de "décoder" |
|
|
|
un paquet de 0 et de 1. Il peut s'écrire ainsi: |
|
|
|
|
|
|
|
|
|
|
|
bytes = b"\x74\x61\x62\x6c\x65" |
|
|
|
décodé = bytes.decode("ascii") |
|
|
|
print(décodé) |
|
|
|
# affiche: table |
|
|
|
|
|
|
|
Plus loin que l'ASCII |
|
|
|
--------------------- |
|
|
|
|
|
|
|
Vous avez sûrement remarquer qu'il n'y a pas de caractères accentués dans |
|
|
|
ASCII. Du coup, il existe d'autres *conventions* qu'on appelle "encodage". |
|
|
|
|
|
|
|
On peut spécifier l'encodage quand on appelle la méthode ``decode()``:: |
|
|
|
|
|
|
|
# latin-1: utilisé sur certains vieux sites |
|
|
|
data = bytearray([233]) |
|
|
|
lettre = data.decode('latin-1') |
|
|
|
print(lettre) |
|
|
|
# affiche: 'é' |
|
|
|
|
|
|
|
# cp850: dans l'invite de commande Windows |
|
|
|
data = bytearray([233]) |
|
|
|
lettre = data.decode('cp850') |
|
|
|
print(lettre) |
|
|
|
# affiche: 'Ú' |
|
|
|
|
|
|
|
Notez que la même suite d'octets a donné des résultats différents en fonction |
|
|
|
de l'encodage! |
|
|
|
|
|
|
|
Unicode |
|
|
|
-------- |
|
|
|
|
|
|
|
L'Unicode c'est deux choses: |
|
|
|
|
|
|
|
1. Une **table** qui associe un un "codepoint" à chaque caractère |
|
|
|
2. Un encodage particulier, l'UTF-8, qui permet de convertir une suite |
|
|
|
d'octets en suite de codepoint et donc de caractères |
|
|
|
|
|
|
|
UTF-8 en pratique |
|
|
|
------------------ |
|
|
|
|
|
|
|
D'abord, UTF-8 est compatible avec ASCII:: |
|
|
|
|
|
|
|
encodé = "abc".encode("utf-8") |
|
|
|
print(encodé) |
|
|
|
# Affiche: b'abc' |
|
|
|
|
|
|
|
Ensuite, certains caractères (comme ``é``) sont représentés par 2 octets:: |
|
|
|
|
|
|
|
encodé = "café".encode("utf-8") |
|
|
|
print(encodé) |
|
|
|
# Affiche: b'caf\xc3\xa9" |
|
|
|
|
|
|
|
|
|
|
|
Enfin, certains caractères (comme les emojis) sont représentés par pleins d'octets:: |
|
|
|
|
|
|
|
encodé = "I ❤️ you".encode("utf-8") |
|
|
|
print(encodé) |
|
|
|
b'I \xe2\x9d\xa4\xef\xb8\x8f you' |
|
|
|
|
|
|
|
|
|
|
|
.. warning:: |
|
|
|
|
|
|
|
Toutes les séquences d'octets ne sont pas forcément valides quand on veut |
|
|
|
les décoder en UTF-8 |
|
|
|
|
|
|
|
Conséquences |
|
|
|
------------- |
|
|
|
|
|
|
|
* On peut représenter *tout* type de texte avec UTF-8 (latin, chinois, coréen, langues disparues, ....) |
|
|
|
* On ne peut pas accéder à la n-ème lettre directement dans une chaîne |
|
|
|
encodée en UTF-8, il faut parcourir lettre par lettre (ce qui en pratique est rarement |
|
|
|
un problème). |