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
.
C’est pas plutôt “Array d’octet” ? Bytes = octets non ? 1 byte = 8 bits ?
Après c’est pas la taille du byte qui compte….
:D
Bien vu zed, je me suis mélangé entre bytes et bits.
Merci pour cet article, de la part d’un retardataire encore en Python 2.
Le sodomiseur de coléoptères a noté
s/et ce type a subit un lifting/et ce type a subi un lifting
s/que j’ai pu rencontré/que j’ai pu rencontrer
s/comme une chaînes,/comme une chaîne,
Merci, fixed.
C’est pas pour dire, mais ce bout de code là, “ça fait deux fois ‘chacals’ “.
C’est pas plutôt
isinstance(bytearray, bytes)
que tu voulais tester la deuxième fois ?D’ailleurs ça donnerait quoi ce code ? J’ai pas de python 3 sous la main.
Je crois que je vais finir par essayer de m’y mettre, à ce fameux python 3.
Il ne manquerait pas une inversion, là :
>>> isinstance(bytes, bytearray)
False
>>> isinstance(bytes, bytearray)
False
Ah, téléscopage de commentaire avec Kontre. C’est beau la concomitance, des fois.
Indeed. Je corrige ça.
Merci de mettre en lumière les types bytes.
Et sinon: s/pour obtenir de un array de octets./pour obtenir un array d’octets.
Type byte toi moi-même.
D’ailleurs je viens de tomber sur cet article :
J’avoue que je n’y ai pas compris grand chose. Quelle est la différence entre une liste et un array?
Je crois qu’en Java une liste est un type particulier d’array, mais j’ai dû mal à saisir ce que les listes ont en plus.
Une liste est juste un type d’array
Un array est une structure de données de bas niveau. La distance entre deux adresses de deux éléments voisins est constante dans tout l’array (généralement tous les éléments on le même type pour qu’ils prennent la même place).
Une liste est une séquence d’élément finie.
On voit bien que l’array est une question d’implémentation, la liste est une question de sémantique.
Maintenant, le point de l’auteur de l’article cité est biaisé, car il ne dit pas si il définit la liste du point de vue de l’implémentation (ni de laquelle), de l’API ou du concept sémantique.
A la fin il conclut de manière prétentieuse :
Personnellement, un mec comme ça, je le vire.
Il a visiblement plus de connaissance du monde papier que du code réel.
En php par exemple, le type Array ne représente pas du tout l’implémentation d’un array. Terme mal choisi ? Sans aucun doute. Raison d’écrire un article de 100 lignes et de faire chier ses élèves avec. Err…
Au final, on s’en branle. En Python, on appelle “liste” le type de base qui regrouper et parcourir un ensemble ordonné d’éléments dont la taille n’est pas connue par avance mais qui est finie.
bytearray et bytes sont bien des arrays par contre en Python, dans leurs implémentations et leurs api, bien que l’API soit plus riche que celle d’un array basique. Mais encore une fois, tu ne manipuleras presque jamais un array en Python. Seuls les programmeurs scientifiques ou de libs le feront. Dans la programmation de tous les jours, on utilise des listes, et c’est tout.
@Sam: Le calcul numérique se fait en effet avec des arrays (c’est énormément plus rapide et plus efficace en mémoire), mais je ne pense pas que beaucoup de scientifiques utilisent des bytearray ou des bytes directement. Numpy a sa propre implémentation, qui a une API encore plus riche. Je sais que les données brutes sont stockées dans ce qu’ils appellent un buffer, mais je ne sais pas si ce buffer en question est un bytearray python.
Et le
>>> import array
dans tout ça ?
Sinon pour les listes et les array si pour listes on pense à une implémentation par des listes chaînées
Pour un tableau, l’accès au énième élément est directe pour une liste, il faut parcourir tout les éléments. Ca change des choses pour l’ajout et la suppression si l’élément est en plein milieu ou aux extrémités.
En python pour avoir une liste (doublement) chainée il faut un collections.deque
Merci pour ce site, c’est vraiment sympa.
Ça me fait penser quand j’avais du apprendre le pascal et qu’il fallait mettre deux index dans l’array
@herison : array, c’est un module qui permet la création d’arrays contenant des types différents en Python. Arrays de float, de chars, de long, etc.
Effectivement, on peut imaginer une liste chaînée, sauf que les structures de données Python sont toutes de haut niveau. Même les arrays du module array sont pas de simples arrays.
Typiquement, une liste permet une collection d’objet hétérogènes. De plus, récupérer sa taille est une opération O(1) (https://wiki.python.org/moin/TimeComplexity). Donc les listes ne sont pas juste des arrays, il y a une couche par dessus.
Bref, l’auteur veut juste à tout prix faire son kéké devant ses élèves. J’ai horreur de ces profs.