Sam & Max » bytes http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 En Python 3, le type bytes est un array d’entiers 16 http://sametmax.com/en-python-3-le-type-bytes-est-un-array-dentiers/ http://sametmax.com/en-python-3-le-type-bytes-est-un-array-dentiers/#comments Thu, 05 Dec 2013 16:00:32 +0000 http://sametmax.com/?p=8160 Le plus gros changement quand on passe de Python 2 à Python 3, c’est la gestion des chaînes de caractères.

Pour rappel :

  • En 2.7, les chaînes sont par défaut des arrays d’octets, et il faut les décoder pour obtenir de l’unicode.
  • En 3, les chaînes sont par défaut de type ‘unicode’, et il faut les encoder pour obtenir de un array d’octets.

Si vous avez besoin d’une mise à jour sur l’encoding en Python, on a un article pour ça.

Comme toute entrée ou sortie est forcément un flux d’octets, mais pas forcément dans le même encodage, Python 2.7 pouvait poser problème pour le débutant qui essayait de comprendre pourquoi son programme plantait, bordel de merde.

La version 3 prend plusieurs mesures pour éviter les bugs vicieux liés à l’encodage de caractères:

  • L’encodage par défaut du code est UTF8.
  • L’encodage par défaut de lecture et d’écriture est UTF8.
  • On ne peut plus mélanger ‘bytes’ et ‘unicode’.
  • Les messages d’erreur expliquent clairement et tôt tout problème.

La plupart du temps, quand on va manipuler du texte, on va donc toujours manipuler de l’unicode, en Python 3. Ce dernier va nous forcer à faire le décodage / encodage au bon moment.

Mais il restera quelques fois le besoin de manipuler du bytes, et ce type a subi un lifting…

La base

Créer un array d’octets (le type bytes‘, en Python 3) demande de préfixer une chaîne avec ‘b’ :

>>> s = b'I am evil, stop laughing!'
>>> type(s)
<class 'bytes'>
>>> print(s)
b'I am evil, stop laughing!'

Première remarque, on ne peut plus utiliser ce type pour afficher quoi que ce soit, puisque l’affichage est une représentation du type (appel à __repr__), et pas du texte mis en forme. Déjà Python vous indique la couleur : si vous voulez manipulez du texte, n’utilisez pas ce type.

Comparez avec le type unicode :

>>> u = s.decode('utf8')
>>> type(u)
<class 'str'>
>>> print(u)
I am evil, stop laughing!

L’affichage marche comme on s’y attend. Bref, vous êtes forcé de toujours rester sur de l’unicode (le type str en Python 3, ce qui porte à confusion) si vous manipulez du texte. Heureusement, c’est quasiment toujours ce que vous aurez.

Par exemple, si vous ouvrez un fichier en Python 3 :

>>> content = open('/etc/fstab').read()
>>> type(content)
<class 'str'>

C’est du texte. A moins de demander qu’il soit ouvert en mode binaire :

>>>> content = open('/etc/fstab', 'rb').read()
>>> type(content)
<class 'bytes'>

Une autre différence MAJEURE, c’est que, si dans Python 2.7, les arrays d’octets pouvaient être manipulés comme un array de lettres :

>>> s = 'I put the goal in golem...' 
>>> s[0] # en Python 2.7
>>> 'I'

En Python 3, les array d’octets sont au mieux manipulables comme un array d’entiers :

>>> s = b'I put the goal in golem...'
>>> s[0] # en Python 3
73

La représentation sous forme de lettre est gardée pour l’initialisation pour des raisons pratiques, mais sous le capot, il se passe ça:

>>> bytes([73, 32, 112, 117, 116, 32, 116, 104, 101, 32, 103, 111, 97, 108, 32, 105, 110, 32, 103, 111, 108, 101, 109, 46, 46, 46])
b'I put the goal in golem...'

D’ailleurs, on ne peut même plus faire d’opérations de formatage avec des octets comme en Python 2.7 :

>>> b"Welcome to the league of %s" % input('')
Draven
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for %: 'bytes' and 'str'

format() ne marche pas non plus. On est assez proche du tableau d’octets en C, sauf qu’en plus, on ne peut pas le modifier :

>>> s = b"My right arm is a lot stronger than my left arm."
>>> s[0] = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment

Les arrays d’octets sont donc maintenant essentiellement des outils de communication avec le monde extérieur.

Bytearray

Il existe encore des raisons de manipuler des arrays d’octets : les applications scientifiques. Typiquement, les algos de crypto opérent sur des arrays d’octets.

Pour cette raison, Python 3 vient également avec un nouveau type de base : bytearray, un array d’octets modifiable.

>>> s = bytearray(b"this tasted purple !")
>>> s[2:4] = b'at'
>>> print(s)
bytearray(b'that tasted purple !')

Et on a toutes les opérations de liste dessus, comme append, pop(), etc :

>>> for x in b' ,puY':
...     s.insert(0, x)
... 
>>> print(s)
bytearray(b'Yup, that tasted purple !')

Attention par contre, ces opérations attendent un entier en paramètres et NON un array d’octets.

Et un dernier détail :

>>> isinstance(bytes, bytearray)
False
>>> isinstance(bytearray, bytes)
False

Différence entre string et array d’octets

Il est facile de confondre tout ce merdier.

En Python 2.7, le type str était un array d’octets, et on le manipulait comme une chaîne, d’où la difficulté de transition.

En Python 3, bien qu’on puisse créer un array d’octets avec une syntaxe utilisant des lettres, ils ne sont plus du tout utilisés pour la manipulation de texte. Si vous voulez manipuler du texte qui vient de l’extérieur de votre programme, il faudra toujours le décoder pour obtenir un type str (qui est l’ancien type unicode de Python 2.7).

Le décodage sera fait automatiquement dans la plupart des cas, et plantera si on tombe sur un cas où vous devez le faire à la main et que vous ne le faites pas. Du coup, plus de difficulté à trouver d’où vient ce bug d’encoding, car on a toujours l’erreur à la source.

En ce sens, Python 3 est beaucoup plus clair : les octets d’un côté, le texte de l’autre. Bon, tout ça c’est de la surcouche, au final, tout est octet. Mais on a rarement envie de manipuler un octet directement, sinon on coderait encore en assembleur.

Avec ce système, Python 3 est le langage le plus sain que j’ai pu rencontrer dans sa gestion de l’encodage : il ne cache rien, oblige l’utilisateur à coder avec de bonnes habitudes, facilite le débugage et met sur le devant de la scène la problématique de l’encoding, qui est le plus souvent cachée vite fait sous le tapis.

L’alternative intelligente la plus proche étant celle de node.js, qui interdit tout simplement la plupart des encodings dans son API.

La bonne nouvelle ? 99% du temps, vous n’aurez même pas à vous en soucier, car ASCII est inclus dans UTF8, et ce sont les encodings les plus utilisés. Avec Python 3 forçant UTF8 par défaut partout et des chaînes en unicode dès le départ, il n’y a presque rien à faire. Je doute que la plupart des gens aient même à manipuler le type bytes.

]]>
http://sametmax.com/en-python-3-le-type-bytes-est-un-array-dentiers/feed/ 16