Skip to content

Instantly share code, notes, and snippets.

@metamatik
Created March 29, 2013 11:15
Show Gist options
  • Save metamatik/5270253 to your computer and use it in GitHub Desktop.
Save metamatik/5270253 to your computer and use it in GitHub Desktop.
magopian t'apprend l'encodage
<magopian> m'en fou je suis en passe de finir le portage de IMAPClient vers python3
<magopian> c'est kiffant l'open source quand même
<mrjmad> :)
<mrjmad> putain je sais pas quand je vais commencer a m'y mettre a py3 bordel
<Linovia> d'ailleurs, qui a commencé à vraiment utiliser python3 ?
<mrjmad> tu tentes aussi le django en py3 magopian ?
<mrjmad> Linovia, moi j'ai mis dans ma todo list 'comprendre comment faire du py3 ' :)
<Linovia> mrjmad: oO
<mrjmad> (par la je veux dire, comprendre toutes les modifs entre les 2 versions, etre capable de savoir exactement les points d'achoppement dans une migration 2.7 -> 3, etre capable de 'penser' en 3.X , etc .. )
<mrjmad> ( je suis encore très light sur pas mal de sujet, genre le utf8 tout ca , pour me dire avec sérénité que je peux faire des migrations 2.7 -> 3.X )
<Linovia> pour l'utf8, c'est simple. C'est compliqué avec python2 et simple avec python3 :)
<magopian> en fait, je me faisais une montagne de py3, c'est à peine une petite colinette
<magopian> Linovia: c'est pas si compliqué avec py2 si tu fais "from __future__ import unicode_literals"
<magopian> et zou
<Linovia> magopian: faire proprement les encode decode en python2, c'est loin d'être simple
<Linovia> enfin, en "pure" python2
<magopian> meuh si
<mrjmad> bon vais me chercher de la doc qui explique les différentes choses a bien comprendre pour faire des migrations 2.X -> 3
<mrjmad> et vais tenter de coder des petits trucs en 3
<mrjmad> :)
<magopian> tu utilise six pour les trucs un peu tricky (genre six.text_type)
<Linovia> magopian: base64.b64encode('toto') <<< un exemple d'erreur commune en python2
<magopian> avec unicode_literals, toto est unicode
<Linovia> et il ne marchera pas :)
<Linovia> normalement, il faut un type str et pas unicode pour base64
<magopian> si tu veux des bytes, soit tu rajoute un "b" devant, b'toto', soit si tu sait que c'est d el'unicode 'toto'.encode('ascii')
<No`> et là, brain melt
<magopian> attention à l'utilisation de "un type str"
<magopian> vaut mieux dire, "un type bytes" :)
<magopian> No`: c'est vrai?
<magopian> j'ai super bien compris maintenant, j'explique à qui veut
<Linovia> magopian: oui, mais tu vois ce que je veux dire, ce n'est pas simple
<magopian> (enfin, je pense avoir super bien compris ^^)
<magopian> Linovia: ah, mais j'ai pas dis que c'était "simple", ça reste une petite colinette à monter, mais pas si pire que ça
<Linovia> alors qu'en python3, ça te pete directement au visage si tu n'as pas exactement le bon type
<magopian> ça le devient quand tu dois faire beaucoup de mix entre bytes/unicode (genre gestion de fichiers, de sockets...)
<magopian> là pour imapclient, c'était un brin tricky, et ça a demandé beaucoup de modifications dans le code
<magopian> Linovia: oui, et ça, c'est de l'amour
<Linovia> indeed
<magopian> au moins, plus d'embrouille entre bytes/string
<mrjmad> faudra que je lise ton code imapclient du coup magopian
<mrjmad> poru comprendre bien
<mrjmad> tu passe avec six ou tu fais carrement un nouvelle version py3 only ?
<Linovia> j'ai le même genre de souci avec raven et on a levé quelques points sur DRF aussi lors du passage au python3
<magopian> mrjmad: c'est du single codebase pour py2 et py3
<magopian> Linovia: DRF ?
<Linovia> django rest framework
<No`> magopian: j'sais pas pourquoi, je bloque sur les encodages / unicode / trucs.
<No`> ça me faisait ça avec les regexp, maintenant, ça va mieux
<No`> mais j'ai pas encore "vu la lumière"
<No`> j'ai lu pas mal d'articles dessus, mais j'imprime pas
<Linovia> l'avantage du python3 est justement qu'il te guide pour ça
<Linovia> tu utilises un byte au lieu d'un str, il va te le dire de suite
<magopian> No`: laisse moi deux minutes et j'essaie de te faire appuyer sur le bouton
<Linovia> pas 2 ans après avec une chaine particulière
<Linovia> la video de jacob kaplan moss sur python3 à la pycon cette année est assez intéressante
<No`> ce qui est dramatique, c'est que quand tout va bien, on se rend compte de rien
<Linovia> No`: avec python2 oui, pas avec python3
<No`> c'est dès que j'ai un UnicodeError, je suis comme une poule devant un couteau
<Linovia> No`: avec python2 oui, pas avec python3 (bis)
<Linovia> No`: http://dpaste.org/o49sS/
<Linovia> tu passes une type str ou unicode, python2 fait une conversion implicite et "mange" les deux
<Linovia> en python3, il te le signale dès le début
<magopian> bon je vais essayer de faire claire No` mais ça sera peut-être pas concis
<No`> Linovia: là, ok, admettons. mais c'est pas une erreur d'encodage, c'est le type attendu qui est explicitement demandé
<Linovia> No`: tes erreurs d'encodage viennent simplement du fait que python fait une conversion implicite entre le str et unicode
<magopian> faut bien comprendre déjà la différence entre bytes et unicode
<magopian> alors, au commencement étaient les entiers
<magopian> et quelqu'un s'est dit: tiens, je vais dire que l'entier 65, ça va être 'A' : ord('A') == 65
<magopian> et il a inventé la table ASCII
<magopian> seulement, dans la table ASCII, y'a que 128 caractères
<magopian> donc un gars a dit, nan mais attends, on va inventer latin-1 (ISO8859-1)
<magopian> donc bref, un gars a dit, moi je veux pouvoir écrire "ÿ" ou "é"
<magopian> donc il a inventé latin-1 qui permet d'associer des nombres à d'autres lettres
<magopian> ord('ÿ') == 255 (en python3, mais on y reviendra, en python2, il faut faire ord(u'ÿ')
<magopian> seulement, y'a un grec qui s'est dit: oui mais bon, moi, le "ÿ" et le "é", je les utilise pas
<magopian> donc j'en ai rien a fiche, par contre j'aimerais bien pouvoir écrire "µ"
<magopian> donc il a inventé une autre table de correspondance
<magopian> entre un nombre et une lettre
<magopian> et il y a eu une prolifération de différent "encodages"
<magopian> un encodage, c'est dire "le nombre 255, ça représente telle lettre"
<magopian> en latin-1, ça représente "ÿ", mais dans un autre encodage ça pourrait être totalement différent
<No`> font chier les grecs
<magopian> donc y'a plusieurs soucis: déjà, quand t'as un entier, il faut aussi que tu aie la table de correspondance pour savoir à quelle lettre ça correspond
<magopian> parce que ça sera des lettres différentes selon la table de correspondance (latin-1, ISO-8859-7 pour le grec, KOI8-R pur le cyrillic...)
<magopian> quand je te parle de nombres/entiers, c'est parce qu'on est bien d'accord qu'à la base, une chaîne de caractères, c'est plusieurs caractères, et que chaque caractère est représenté par un nombre, je pense que tu as suivi jusque là
<magopian> et les nombres sont représentés par un ou plusieurs octets (bytes en anglais)
<magopian> le soucis de tous ces encodages, c'est que ça devient vite compliqué d'avoir une chaine de caractères avec des lettres de différents alphabets
<magopian> comment faire pour représenter la lettre qui corredpond à 255 dans la table de correspondance latin1, et à côté, la lettre qui correspond à 255 dans la table de correspondance du cyrillic ?
<magopian> (faudrait que j'ai un bon exemple sous la main, mais je maitrise pas des masses le cyrllic ;)
<No`> prends le kligon en exemple
<magopian> c'est encore un autre soucis le klingon
<magopian> ned batchelder en parle dans son talk sur "stoping the unicode pain"
<magopian> bon, tant pis pour l'exemple, je continue
<magopian> donc le soucis, c'est de trouver un moyen de représenter une lettre chinoise à côté d'une lettre russe, les deux ayant, dans leur table de correspondance (encoding/charset) le même nombre
<magopian> et puis surtout y'a des gens qui ont commencé à se dire que tous ces encodages c'était bien marrant deux minutes, mais ça serait bien d'avoir un seul encodage, une seule table de correspondance, qui contiennent toutes les lettres
<No`> y'a des doublons dans la base, quoi
<magopian> de tous les alphabets
<magopian> qu'on se pose plus la question de savoir en quoi une suite de nombre doit être décodé
<magopian> qu'on se demande plus sans cesse de savoir si il faut utiliser telle ou telle table de correspondance, la greque ou la chinoise, etc...
<magopian> et là est apparu unicode
<magopian> unicode c'est pas un encodage, c'est pas un charset
<magopian> c'est plus ou moins une table de correspondance
<magopian> entre un nombre et toutes les lettres de tous les alphabets (et y'a pas que des lettres, y'a des trucs du genre "pile of poo", "snowman" ...)
<magopian> unicode, c'est la réponse, c'est 42
<magopian> c'est le moyen d'avoir une correspondance unique entre un nombre et un symbole (une lettre ou un snowman...)
<No`> oooookay
<magopian> tout ce qu'il manquait, c'était un encodage (une table de correspondance) => utf-8 (entre autres, mais c'est le plus répandu, et le plus largement accepté comme étant la meilleure solution)
<No`> ça j'ai suivi. y'a tout dedans, y compris ce qu'on utilisera jamais
<magopian> utf-8, ça "encode" un nombre en un certain nombre de bytes (un certain nombre d'octets)
<magopian> c'est un encodage à taille variable
<magopian> c'est une manière optimisée de stocker l'information, en disant que "les petits nombres peuvent être stockés sur un seul octet, les plus gros sur 4 (de mémoire)"
<magopian> donc on va pas utiliser 4 octects partout si y'a pas beoin
<magopian> besoin
<magopian> genre si on a besoin que des "symboles de base" (de 0 à 256 par exemple), on va pas les stocker sur 4 octets et perdre plein de place
<magopian> donc unicode, ça permet d'avoir une correspondance 1<->1 entre tous les symboles et un nombre
<magopian> deux symboles auront pas le même nombre, deux même nombres ne représentent pas deux symboles différents mais le même
<magopian> donc les gens se sont dit: mais c'est génial, plus besoin de se prendre la tête dans nos programme, quand on en a pas besoin (on va y revenir), avec les encodages/charsets
<magopian> on va tout représenter en unicode, et au moins pas d'embrouille
<magopian> et quand on en aura besoin, on encodera en ce qu'on veut (ascii, latin-1, ou mieux, utf-8, mais utf-8 c'est pas dispo partout pour le moment, dommage, ça simplifierai les choses)
<magopian> maintenant, pour bien clarifier la différence entre "bytes" et "str" en py3 :
<magopian> bytes => ensemble de nombres, on sait pas à quoi ils correspondent comme symboles tant qu'on a pas décidé quel charset utiliser
<magopian> ensemble d'octets
<magopian> str : en py3, c'est de l'unicode: cad un ensemble de symboles représentés sur un ou plusieurs octets
<magopian> bon, alors pourquoi se prendre la tête avec les bytes et les charsets/encodages? PARCE QU'ON A PAS LE CHOIX
<magopian> les fichiers sont stockés en bytes, les sockets transfèrent des bytes
<magopian> donc quand tu veux stocker la chaîne de caractères représentés en unicode par u'test ÿ' dans un fichier, il va falloir que tu décide de l'encodage
<magopian> il va falloir que tu dise soit que tu utilise le charset ascii (ah bah non, pas possible, ÿ c'est 255, or ascii ça va pas plus loin que 128)
<magopian> soit latin-1 (ah, là, coup de bol, ça marche, ça va jusqu'à 256)
<magopian> soit utf-8 (là, pas de soucis à se faire, ça stocke tout unicode, y'aura jamais de soucis)
<magopian> et surtout, il faut que le gars qui lise ton fichier par la suite il sache quel a été le charset utilisé (il a aucun moyen de le deviner, impossible)
<magopian> ce qui est cool avec utf-8, c'est que latin-1 est "inclu" (255 en latin-1 et en utf-8 représente "ÿ")
<magopian> et ce qui est cool avec latin-1 c'est que ascii est inclu (65 en ascii et en latin-1 représente 'A')
<magopian> mais ça, c'est qu'un détail, la plupart du temps on s'en fiche de savoir ça, c'est juste pratique parfois
<No`> d'où la conversion automatique dans python2, c'est ça ?<magopian> pas tout a fait
<magopian> la conversion automatique dans python2 elle dit 'si jamais j'ai besoin de bytes et que j'ai de l'unicode, je convertis en sous-marin sans dire rien à personne en utilisant le charset par défaut de la machine/os/terminal/connexion.... en cours)
<magopian> ça pourrait être utf-8, latin-1, ascii... pour autant que je sache
<magopian> DONC
<magopian> on a un moyen de représenter tout ce qu'on veut dans nos programmes (merci unicode), et de représenter tout ce qu'on veut dans nos fichiers, sockets, DB, pages web... (merci utf-8)
<magopian> le soucis se limite donc à savoir: quand il faut des bytes (cad des symbols encodés), par exemple pour tout ce qui est io
<magopian> et quel charset utiliser
<magopian> pour la deuxième question, c'est compliqué et simple à la fois: c'est pas toi qui décide
<magopian> (enfin, quand tu peux décider, utilise utf-8, au moins tu peut TOUT représenter)
<magopian> mais souvent, tu décide pas, 'est la librairie/server/interface/API qui décide pour toi et qui t'impose
<magopian> par exemple, un serveur IMAP il accepte que de l'ascii (et de l'utf-7 modifié pour le nom de répertoires)
<magopian> donc il faut le savoir, et quand tu veux communiquer avec un serveur IMAP, transformer ton joli unicode parfait en ascii ou en utf-7 modifié
<magopian> parlons maintenant plus spécifiquement de l'encodage/decodage
<magopian> pour que ce soit vraiment clair, je vais utiliser u'' et b''
<magopian> u'test ÿ' c'est de l'unicode, si je veux l'encoder (par exemple en ascii) on a vu que ça marchait pas
<magopian> UnicodeEncodeError: 'ascii' codec can't encode character '\xff' in position 0: ordinal not in range(128)
<magopian> \xff => 255
<magopian> il me dit donc: désolé, ta table de correspondance elle va jusqu'à 128, or tu me file le nombre 255, je sais pas quoi en faire
<magopian> (255 étant le nombre, dans la liste unicode, qui correspond à la lettre ÿ)
<magopian> si j'encode en latin-1, pas de soucis: u'ÿ'.encode('latin-1') == b'\xff'
<magopian> de la même manière, je peux faire l'opération inverse, imaginons que je lise \xff d'un fichier
<magopian> b'\xff'.decode('latin-1') == u'ÿ'
<magopian> b'\xff'.decode('ascii) ==> UnicodeDecodeError: 'ascii' codec can't decode byte 0xff in position 0: ordinal not in range(128)
<magopian> jusque là, tout va ben No` ? ça paraît clair et logique ce que je raconte?
<No`> magopian: ça a l'air logique, oui
<mrjmad> du coup magopian
<mrjmad> si je suis bien
<mrjmad> u'Toto', c'est de l'unicode
<mrjmad> mais pas forcement de l'utf-8 ?
<magopian> mrjmad: u'toto' c'est pas du tout de l'utf-8, c'est de l'unicode
<No`> ah putain, c'est là que je cale, des fois
<No`> le "u" de unicode / utf-8
<mrjmad> No`, pareil
<magopian> utf-8, c'est comme latin-1, iso-8859-7, KOI8-r
<mat> u'' -> unicode
<magopian> c'est une table de correspondance, un encodage, un charset
<mrjmad> mais comment du dis que ton u'', tu le veux en utf-8 alors ?
<mat> faut se dire, que, quand tu as une u'truc', c'est par bonté d'ame que python te l'affiche
<metamatik> Python est bon.
<mrjmad> quand tu fais un print u'toto' , il te fais une conversion implicite en utf-8 en fait c'est ca ?
<magopian> c'est l'algo qui est choisi pour représenter un symbole unicode (et son nombre associé) en un ensemble d'octets
<mrjmad> normalement du devrais faire print u'Toto'.decode('utf-8) ?
<magopian> mrjmad: quand tu fais un pritn, il va encoder avec l'encodage de ton OS/systeme/shell
<magopian> pas décode
<magopian> encode
<mrjmad> ha oui pardon encode
<mrjmad> :)
<No`> ah
<No`> ça me rappelle "moi"
<magopian> c'est un peu confusionnant à cause du U de Unicode et Utf8, et "encode" pour uniCODE
<No`> j'essaie encode
<No`> marche pas
<No`> j'essaie decode
<No`> marche pas
<No`> j'essaie decode().encode()
<No`> et des fois ça marche
<No`> ou l'inverse
<magopian> mais en gros, encoder == transformer en bytes
<magopian> appliquer un charset, un encoding, sur de l'unicode
<magopian> unicode, ça s'encode en bytes, les bytes ça se décode en unicode
<mrjmad> en fait quand tu veux de l'utf8 ( ou du latin1 ) ou autre, tu transformes ton unicode en byte
<mrjmad> c'"est ca ?
<magopian> voilà
<magopian> en faisant .encode('ton charset ici')
<magopian> donc u'test ÿ'.encode('latin-1') => b'test \xff'
<mrjmad> c'est clair qu'avvoir 2 mots qui commencent par la meme lettre et choisir cette lettre pour declarer un truc du type d'un des 2 mots
<mrjmad> c'était pas le mieux a faire :)
<magopian> mrjmad: ça sera plus un soucis en PY3
<magopian> <3
<mat> faut juste penser que savoir qu'un truc est en unicode, de base ça ne donne pas suffisamment d'infos pour l'afficher, il faut faire un choix - c'est plus facile si tu as déjà joué avec utf-16 et autres aussi :)
<No`> remplaçons-les : "unicode" ==> "licorne-code"
<mat> python 3 t'aidera, mais faudra toujours savoir faire la différence bytes/str
<magopian> en fait, py2 a pris plusieurs mauvaises décisions
<magopian> la principale étant de vouloir faire de l'encodage/decodage implicite (alors que dans le zen c'est bien marqué qu'il faut pas)
<mrjmad> (oui et bon après quand tu as fait 20 ans de C++, ca aide pas non plus :) )
<magopian> et d'essayer de deviner l'encodage/charset (alors que dans le zen c'est marqué qu'il faut pas)
<magopian> en gros, les soucis de unicode encode/decode error, c'est principalement embrouillant en py2 parce que ça fait des trucs sous le manteau sans que tu sache
<magopian> prenons un exemple (syntaxiquement incorrect en py2, et qui marche pas en py3):
<magopian> b'test \xff'.encode('utf-8')
<magopian> ça doit faire quoi selon vous?
<metamatik> une erreur ?
<magopian> selon py3, ça jette une erreur: AttributeError: 'bytes' object has no attribute 'encode'
<magopian> en py2, ça marche, mais ça échoue
<magopian> cad qu'il te jette pas un attribute error
<magopian> et si il y avait pas le \xff dedans, ça amrcherait nickel: b'test'.encode('utf-8') => u'test'
<magopian> et c'est là l'horreur
<magopian> parce que un jour, tu vas rajouter un 'ÿ' dedans, et patatra:
<magopian> b'test ÿ'.encode('utf-8') => UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 5: ordinal not in range(128)
<magopian> et là, tu dis LE WAT
<magopian> je veux encoder 'ÿ' en utf-8 (qui peut TOUT représenter de l'unicode! tous les symboles), et il veut pas
<magopian> mais en plus il me parle de ASCII (wat), et en plus je veux ENCODER et il me dit "unicodeDECODEError"
<magopian> nan mais allo, allo quoi, je te dis encode en utf8 tu decode en ascii, nan mais allo quoi
<magopian> mais c'est parce que les "bytes" n'ont pas d'attribut "encode"
<magopian> donc py2 qui veut t'aider, il dit "ah mais en fait, tu voulais me filer de l'unicode, mais tum'as filé des bytes, pas grave, attends, je convertis pour toi, en utilisant le charset par défaut de ta machine"
<magopian> => ascii dans mon cas
<magopian> donc b'test'.encode('utf-8') => b'test'.decode('ascii') => u'test' => u'test'.encode('utf-8')
<magopian> c'est la partie du milieu (faite implicitement en py2) qui pose tous les problèmes, c'est la racine de tous les maux de tête et cheveux blanc en py2
<magopian> No`: mrjmad: vous comprenez cette partie ? le soucis de l'encodage/decodage implicite en py2?
<mrjmad> ouaip
<metamatik> mais mais mais... CAYMAL
<magopian> oui, caymal
<magopian> d'où py3 qui résoud tous les soucis
<magopian> et en fait les galères qu'on se paie à migrer en py3, c'est des galères nécessaires: sans ça, notre programme en py2 aurait pu potentiellement exploser en plein vol un jour ou l'autre
<No`> magopian: disons que je comprends mieux comment l'erreur UnicodeDecodeError débarque
<No`> mais je me sens toujours un peu désarmé quand je veux résoudre mon soucis
<No`> "ah zût, j'ai cette erreur violente... que faire ?"
<magopian> maintenant, je pense que tu as les clés pour comprendre bien à fond la conf de ned batchelder: http://nedbatchelder.com/text/unipain.html
<magopian> mate la vidéo, tu verra, c'est limpide (enfin, j'espère que ça le sera pour toi, sinon j'ai mal fait mon job)
<magopian> alors
<magopian> quand t's cette erreur violente et incohérente (UnicodeDecodeError alors que tu voulais encoder)
<magopian> tu sait que tu avait des bytes en entrée
<mat> perso, pour tous les soucis d'unicodedecode/encodeerror, j'applique la méthode : 1) savoir si c'est une str ou une unicode que j'ai 2) savoir si c'est une unicode ou une str que je veux 3) savoir ce qu'il ya dedans si c'est cohérent 4) rajouter le encode ou le decode qui va bien
<mat> (ou l'enlever, le corriger, etc)
<magopian> alors que tu aurais soit du decoder en unicode au préalable (et fournir de l'unicode), soit ne pas vouloir encoder mais décoder, tout dépends de où tu te place
<magopian> ce que dit mat
<magopian> le tout est de savoir (ce qu'on avait pas vraiment besoin en py2) ce qu'on a en entrée, et ce qu'on veut en sortie
<magopian> en django, tu aura principalement et quasi tout le temps de l'unicode (dans les deux sens, entrée et sortie)
<magopian> après, quand tu ouvre un fichier, ou si tu lis sur une socket, que tu fais de l'io bas niveau, là faut voir
<magopian> ce qui est génial, avec py3, c'est que justement tu pourra plus te tromper sans savoir
<magopian> si on me demande mon avis, le principal changement en py3, c'est cette histoire de unicode/bytes
<magopian> y'en a moultes autres, mais c'est peanuts quand tu fais un portage (enfin, j'ai pas eu de soucis autres que ça perso)
<magopian> et quand tu sait que c'est pour le mieux, tu kiffe
<magopian> et en fait, porter à py3 ça va souvent te lever des bugs que tu soupçonnait même pas ;)
<No`> heh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment