L’encoding en Python, une bonne fois pour toute
J’avais oublié la zik, je rajoute:
Vous avez tous un jour eu l’erreur suivante :
UnicodeDecodeError: 'machine' codec can't decode character 'trucmuche' in position x: ordinal not in range(z)
Et là, pour vous en sortir, vous en avez chié des ronds de paté.
Le problème vient du fait que la plupart du temps, ignorer l’encoding marche : nous travaillons dans des environnements homogènes et toujours avec des données dans le même format, ou un format plus ou moins compatible.
Mais le texte, c’est compliqué, terriblement compliqué, et le jour où ça se gâte, si vous ne savez pas ce que vous faites, vous ne vous en sortirez pas.
C’est d’autant plus vrai en Python car :
- Par défaut, Python plante sur les erreurs d’encoding là où d’autres langage (comme le PHP) se débrouillent pour vous sortir un truc (qui ne veut rien dire, qui peut corrompre toute votre base de données, mais qui ne plante pas).
- Python est utilisé dans des environnements hétérogènes. Quand vous codez en JS sur le navigateur, vous n’avez presque jamais à vous soucier de l’encoding : le browser gère quasiment tout pour vous. En Python dès que vous allez lire un fichier et l’afficher dans un terminal, cela fait potentiellement 3 encoding différents.
- Python 2.7 a des réglages par défaut très stricts, et pas forcément adaptés à notre informatique moderne (fichier de code en ASCII par exemple).
A la fin de cet article, vous saurez vous sortir de toutes les situations merdiques liées aux encodages.
Règle numéro 1 : Le texte brut n’existe pas.
Quand vous avez du texte quelque part (un terminal, un fichier, une base de données…), il est forcément représenté sous forme de 0 et de 1.
La corrélation entre cette suite de 0 et de 1 et la lettre est faite dans un énorme tableau qui contient toutes les lettres d’un côté, et toutes les combinaisons de 0 et de 1 de l’autre. Il n’y a pas de magie. C’est un énorme tableau stocké quelque part dans votre ordinateur. Si vous n’avez pas ce tableau, vous ne pouvez pas lire du texte. Même le texte le plus simple.
Malheureusement, au début de l’informatique, presque chaque pays a créé son propre tableau, et ces tableaux sont incompatibles entre eux : pour la même combinaison de 0 et de 1, ils donnent un caractère différent voir rien du tout.
La mauvaises nouvelle, c’est qu’ils sont encore utilisés aujourd’hui.
Ces tableaux, c’est ce qu’on appelle les encodings, et il y en a beaucoup. Voici la liste de ceux que Python gère :
>>> import encodings >>> print ''.join('- ' + e + '\n' for e in sorted(set(encodings.aliases.aliases.values()))) - ascii - base64_codec - big5 - big5hkscs - bz2_codec - cp037 - cp1026 - cp1140 - cp1250 - cp1251 - cp1252 - cp1253 - cp1254 - cp1255 - cp1256 - cp1257 - cp1258 - cp424 - cp437 - cp500 - cp775 - cp850 - cp852 - cp855 - cp857 - cp858 - cp860 - cp861 - cp862 - cp863 - cp864 - cp865 - cp866 - cp869 - cp932 - cp949 - cp950 - euc_jis_2004 - euc_jisx0213 - euc_jp - euc_kr - gb18030 - gb2312 - gbk - hex_codec - hp_roman8 - hz - iso2022_jp - iso2022_jp_1 - iso2022_jp_2 - iso2022_jp_2004 - iso2022_jp_3 - iso2022_jp_ext - iso2022_kr - iso8859_10 - iso8859_11 - iso8859_13 - iso8859_14 - iso8859_15 - iso8859_16 - iso8859_2 - iso8859_3 - iso8859_4 - iso8859_5 - iso8859_6 - iso8859_7 - iso8859_8 - iso8859_9 - johab - koi8_r - latin_1 - mac_cyrillic - mac_greek - mac_iceland - mac_latin2 - mac_roman - mac_turkish - mbcs - ptcp154 - quopri_codec - rot_13 - shift_jis - shift_jis_2004 - shift_jisx0213 - tactis - tis_620 - utf_16 - utf_16_be - utf_16_le - utf_32 - utf_32_be - utf_32_le - utf_7 - utf_8 - uu_codec - zlib_codec
Et certains ont plusieurs noms (des alias), donc on pourrait en compter plus:
>>> len(encodings.aliases.aliases.keys()) 307
Quand vous affichez du texte sur un terminal avec un simple print
, votre ordinateur va implicitement chercher le tableau qu’il pense être le plus adapté, et fait la traduction. Même pour le texte le plus simple. Même pour un espace tout seul.
Mais surtout, ça veut dire que votre propre code EST dans un encoding. Et vous DEVEZ savoir lequel.
Règle numéro 2 : utf8 est le langage universel, utilisez le
Il existe un encoding qui essaye des regrouper toutes les langues du monde, et il s’appelle unicode. Unicode est un tableau gigantesque qui contient des combinaisons de 1 et de 0 d’un côté, et les caractères de toutes la langues possibles de l’autre : chinois, arabe, français, espagnol, russe…
Bon, il ne contient pas encore absolument tout, mais il couvre suffisamment de terrain pour éliminer 99.999999999 des problèmes de communications de texte entre machines dans le monde.
Le défaut d’unicode est qu’il est plus lent et prend plus de place que d’autres représentations du même texte. Aujourd’hui le téléphone le plus pourri a 10 fois la puissance nécessaire, et ce n’est plus un souci : il peut être utilisé presque partout (sauf peut être dans l’embarqué drastique) sans même réfléchir à la question. Tous les langages les plus importants, tous les services les plus importants, tous les logiciels les plus importants gèrent unicode.
Il y a plusieurs implémentations concrètes d’unicode, la plus célèbre est “UTF 8″.
Moralité, par défaut, utilisez utf-8.
Une fois, à l’entretien d’embauche, un mec m’avait reproché d’utiliser UTF8 parce que “ça posait des problèmes d’encoding”. Comprenez bien qu’utf-8 ne pose aucun problème d’encoding. Ce sont tous les autres codecs du monde qui posent des problèmes d’encoding. UTF-8 est certainement le seul à justement, ne poser aucun problème.
UTF 8 est le seul encoding vers lequel, aujourd’hui, on puisse convertir vers et depuis (pratiquement) n’importe quel autre codec du monde. C’est un espéranto. C’est une pierre de rosette. C’est au texte ce que l’or est à l’économie.
Si UTF8 vous pose “un problème d’encoding”, c’est que vous ne savez pas dans quel encoding votre texte est actuellement ou comment le convertir. C’est tout.
Il n’y a presque aucune raison de ne pas utiliser UTF8 aujourd’hui (à part sur des vieux systèmes ou des systèmes où les ressources sont tellement limitées que vous n’utiliseriez pas Python de toute façon).
Utilisez utf8. Partout. Tout le temps.
Si vous communiquez avec un système qui ne comprend pas UTF8, convertissez.
Mais gardez votre partie en UTF8.
Règle numéro 3 : il faut maîtriser l’encoding de son code
Le fichier dans lequel vous écrivez votre code est dans un encoding et ce n’est pas lié à votre OS. C’est votre éditeur qui s’en occupe. Apprenez à régler votre éditeur pour qu’il utilise l’encoding que vous voulez.
Et l’encoding que vous voulez est UTF8.
Si vous ne savez pas dans quel encoding est votre code, vous ne pouvez pas manipuler du texte et garantir l’absence de bug.
Vous ne POUVEZ PAS.
Donc réflexe : vous configurez votre éditeur de texte pour sauvegarder tous vos nouveaux fichiers par défaut en UTF8. Maintenant. Tout de suite.
Regardez dans la doc de l’éditeur, dans l’aide ou tapez sur Google, mais faites le.
Puis il faut déclarer cet encoding à la première ligne de chaque fichier de code avec l’expression suivante :
# -*- coding: encoding -*-
Par exemple :
# -*- coding: utf8 -*-
C’est une spécificité de Python : si l’encoding du fichier est différent de l’encoding par défaut du langage, il faut le déclarer sinon le programme plantera à la première conversion. En Python 2.7, l’encoding par défaut est ASCII, donc il faut presque toujours le déclarer. En Python 3, l’encoding par défaut est UTF8 et on peut donc l’omettre si on l’utilise. Ce que vous allez faire après la lecture de cet article.
Ensuite, il existe deux types de chaînes de caractères en Python :
- La chaîne de caractères encodée: type ‘str’ en Python 2.7, ‘byte’ en Python 3.
- La chaîne de caractères décodée: type ‘unicode’ en Python 2.7, et ‘str’ en python 3 (sic).
Illustration :
$ python2.7 Python 2.7.3 (default, Aug 1 2012, 05:14:39) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> type('chaine') # bits => encodée <type 'str'> >>> type(u'chaine') # unicode => décodée <type 'unicode'>
$ python3 Python 3.2.3 (default, Oct 19 2012, 20:10:41) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> type("chaine") # unicode => decodée <class 'str'> >>> type(b"chaine") # bits => encodée <class 'bytes'>
Votre but, c’est de n’avoir dans votre code que des chaînes de type ‘unicode’.
En Python 3, c’est automatique. Toutes les chaînes sont de type ‘unicode’ (appelé ‘str’ dans cette version, je sais, je sais, c’est confusionant à mort) par défaut.
En Python 2.7 en revanche, il faut préfixer la chaîne par un u
.
Donc, dans votre code, TOUTES vos chaînes doivent être déclarées ainsi :
u"votre chaîne"
Oui, c’est chiant. Mais c’est indispensable. Encore une fois, il n’y a pas d’alternative (dites le avec la voix de Thatcher si ça vous excite).
Si vous voulez, vous pouvez activer le comportement de Python 3 dans Python 2.7 en mettant ceci au début de CHACUN de vos modules :
from __future__ import unicode_literals
Ceci n’affecte que le fichier en cours, jamais les autres modules.
On peut le mettre au démarrage d’iPython également.
Je résume :
- Réglez votre éditeur sur UTF8.
- Mettez
# -*- coding: utf8 -*-
au début de vos modules. - Préfixez toutes vos chaînes de
u
ou faitesfrom __future__ import unicode_literals
en début de chaque module.
Si vous ne faites pas cela, votre code marchera. La plupart de temps. Et un jour, dans une situation particulière, il ne marchera plus. Plus du tout.
Oh, et ce n’est pas grave si vous avez d’anciens modules dans d’autres encodings. Tant que vous utilisez des objets ‘unicode’ partout, ils marcheront sans problème ensemble.
Règle numéro 4 : décodez toutes les entrées de votre programme
La partie difficile de ce conseil, c’est de savoir ce qu’est une entrée.
Je vais vous donner une définition simple : tout ce qui ne fait pas partie du code de votre programme et qui est traité dans votre programme est une entrée.
Le texte des fichiers, le nom de ces fichiers, le retour des appels système, le retour d’une ligne de commande parsée, la saisie utilisateur sur un terminal, le retour d’une requête SQL, le téléchargement d’une donnée sur le Web, etc.
Ce sont toutes des entrées.
Comme tous les textes du monde, les entrées sont dans un encoding. Et vous DEVEZ savoir lequel.
Comprenez bien, si vous ne connaissez pas l’encoding de vos entrées, ça marchera la plupart du temps, et un jour, ça va planter.
Il n’y a pas d’alternative (bis).
Or, il n’y a pas de moyen de détecter un encoding de façon fiable.
Donc, soit le fournisseur de la donnée vous donne cette information (settings dans la base de données, doc de votre logiciel, configuration de votre OS, spec du client, coup de fils au fournisseur…), soit vous êtes baisés.
On ne peut pas lire un simple fichier si on ne connait pas son encoding. Point.
Si cela a marché jusqu’ici pour vous, c’est que vous avez eu de la chance : la plupart de vos fichiers étaient dans l’encoding de votre éditeur et de votre système. Tant qu’on travaille sur sa machine, tout va bien.
Si vous lisez une page HTML, l’encoding est souvent déclaré dans la balise META ou dans un header.
Si vous écrivez dans un terminal, l’encoding du terminal est accessible avec sys.(stdin|stdout).encoding
.
Si vous manipulez des noms de fichier, on peut récupérer l’encoding du file system en cours avec sys.getfilesystemencoding()
.
Mais parfois il n’y a pas d’autres moyen d’obtenir cette information que de demander à la personne qui a produit la donnée. Parfois même, l’encoding déclaré est faux.
Dans tous les cas, vous avez besoin de cette information.
Et une fois que vous l’avez, il faut décoder le texte reçu.
La manière la plus simple de faire cela est :
votre_chaine = votre_chaine.decode('nom_du_codec')
Le texte sera de type ‘str’, et decode()
retourne (si vous lui fournissez le bon codec ;-)), une version ‘unicode’.
Exemple, obtenir une chaîne ‘unicode’ depuis une chaîne ‘str’ encodée en utf8 :
>>> une_chaine = 'Chaîne' # mon fichier est encodé en UTF8, donc la chaine est en UTF8 >>> type(une_chaine) <type 'str'> >>> une_chaine = une_chaine.decode('utf8') >>> type(une_chaine) <type 'unicode'>
Donc dès que vous lisez un fichier, récupérez une réponse d’une base de données ou parsez des arguments d’un terminal, appelez decode()
sur la chaîne reçue.
Règle numéro 5 : encodez toutes les sorties de votre programme
La partie difficile de ce conseil, c’est de savoir ce qu’est une sortie.
Encore une fois, une définition simple : toute donnée que vous traitez et qui va être lue par autre chose que votre code est une sortie.
Un print
dans un terminal est une sortie, un write()
dans un fichier est une sortie, un UPDATE
en SQL est une sortie, un envoie dans une socket est une sortie, etc.
Le reste du monde ne peut pas lire les objets ‘unicode’ de Python. Si vous écrivez ces objets dans un fichier, un terminal ou dans une base de données, Python va les convertir automatiquement en objet ‘str’, et l’encoding utilisé dépendra du contexte.
Malheureusement, il y a une limite à la capacité de Python à décider du bon encoding.
Donc, tout comme il vous faut connaitre l’encoding d’un texte en entrée, il vous faut connaitre l’encoding attendu par le système avec lequel vous communiquez en sortie : sachez quel est l’encoding du terminal, de votre base de données ou système de fichiers sur lequel vous écrivez.
Si vous ne pouvez pas savoir (page Web, API, etc), utilisez UTF8.
Pour ce faire, il suffit d’appelez encode()
sur tout objet de type ‘unicode’ :
une_chaine = une_chaine.encode('nom_du_codec')
Par exemple, pour convertir un objet ‘unicode’ en ‘str’ utf8:
>>> une_chaine = u'Chaîne' >>> type(une_chaine) <type 'unicode'> >>> une_chaine = une_chaine.encode('utf8') >>> type(une_chaine) <type 'str'>
Résumé des règles
- Le texte brut n’existe pas.
- Utilisez UTF8. Maintenant. Partout.
- Dans votre code, spécifiez l’encoding du fichier et déclarez vos chaînes comme ‘unicode’.
- À l’entrée, connaissez l’encoding de vos données, et décodez avec
decode()
. - A la sortie, encodez dans l’encoding attendu par le système qui va recevoir la données, ou si vous ne pouvez pas savoir, en UTF8, avec
encode()
.
Je sais que ça vous démange de voir un cas concret, alors voici un pseudo programme (téléchargeable ici) :
# -*- coding: utf-8 -*- # toutes les chaines sont en unicode (même les docstrings) from __future__ import unicode_literals """ Un script tout pourri qui télécharge plein de page et les sauvegarde dans une base de données sqlites. On écrit dans un fichier de log les opérations effectuées. """ import re import urllib2 import sqlite3 pages = ( ('Snippets de Sebsauvage', 'http://www.sebsauvage.net/python/snyppets/'), ('Top 50 de bashfr', 'http://danstonchat.com/top50.html'), ) # création de la base de données conn = sqlite3.connect(r"backup.db") c = conn.cursor() try: c.execute(''' CREATE TABLE pages ( id INTEGER PRIMARY KEY, nom TEXT, html TEXT )''' ) except sqlite3.OperationalError: pass log = open('backup.log', 'wa') for nom, page in pages: # ceci est une manière très fragile de télécharger et # parser du HTML. Utilisez plutôt scrapy et beautifulsoup # si vous faites un vrai crawler response = urllib2.urlopen(page) html = response.read(100000) # je récupère l'encoding à l'arrache encoding = re.findall(r'<meta.*?charset=["\']*(.+?)["\'>]', html, flags=re.I)[0] # html devient de l'unicode html = html.decode(encoding) # ici je peux faire des traitements divers et varié avec ma chaîne # et en fin de programme... # la lib sqlite convertie par défaut tout objet unicode en UTF8 # car c'est l'encoding de sqlite par défaut donc passer des chaînes # unicode marche, et toutes les chaînes de mon programme sont en unicode # grace à mon premier import c.execute("""INSERT INTO pages (nom, html) VALUES (?, ?)""", (nom, html)) # j'écris dans mon fichier en UTF8 car c'est ce que je veux pouvoir lire # plus tard msg = u"Page '{}' sauvée\n".format(nom) log.write(msg.encode('utf8')) # notez que si je ne fais pas encode(), soit: # - j'ai un objet 'unicode' et ça plante # - j'ai un objet 'str' et ça va marcher mais mon fichier contiendra # l'encoding de la chaîne initiale (qui ici serait aussi UTF8, mais # ce n'est pas toujours le cas) conn.commit() c.close() log.close()
Quelques astuces
Certaines bibliothèques acceptent indifféremment des objets ‘unicode’ et ‘str’ :
>>> from logging import basicConfig, getLogger >>> basicConfig() >>> log = getLogger() >>> log.warn("Détécé") WARNING:root:Détécé >>> log.warn(u"Détécé") WARNING:root:Détécé
Et ce n’est pas forcément une bonne chose car si il y a derrière écriture dans un fichier de log, cela peut poser problème.
D’autres ont besoin qu’on leur précise:
>>> import re >>> import re >>> re.search('é', 'télé') <_sre.SRE_Match object at 0x7fa4d3f77238> >>> re.search(u'é', u'télé', re.UNICODE) <_sre.SRE_Match object at 0x7fa4d3f772a0>
Le module re
par exemple aura des résultats biaisés sur une chaîne ‘unicode’ si on ne précise pas le flag re.UNICODE
.
D’autres n’acceptent pas d’objet ‘str’:
>>> import io >>> >>> io.StringIO(u'é') <_io.StringIO object at 0x14a96d0> >>> io.StringIO(u'é'.encode('utf8')) Traceback (most recent call last): File "<ipython-input-5-16988a0d4ac4>", line 1, in <module> io.StringIO('é'.encode('utf8')) TypeError: initial_value must be unicode or None, not str
D’autres encore n’acceptent pas d’objet ‘unicode’:
>>> import base64 >>> base64.encodestring('é'.encode('utf8')) 'w6k=\n' >>> base64.encodestring(u'é') Traceback (most recent call last): File "<ipython-input-3-1714982ca68e>", line 1, in <module> base64.encodestring('é') File "/usr/lib/python2.7/base64.py", line 315, in encodestring pieces.append(binascii.b2a_base64(chunk)) UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 0: ordinal not in range(128)
Cela peut être pour des raison de performances (certaines opérations sont plus rapides sur un objet ‘str’), ou pour des raisons historiques, d’ignorance ou de paresse.
Vous ne pouvez pas le deviner à l’avance. Souvent c’est marqué dans la doc, sinon il faut tester dans le shell.
Une bibliothèque bien faite demandera de l’unicode et vous retournera de l’unicode, vous libérant l’esprit. Par exemple, requests et l’ORM Django le font, et communiquent avec le reste du monde (en l’occurence le Web et les bases de données) dans le meilleur encoding possible automatiquement de manière transparente. Quand c’est possible bien entendu, parfois il faudra forcer l’encoding car le fournisseur de votre donnée déclare le mauvais. Vous n’y pouvez rien, c’est pareil pour tous les langages du monde.
Enfin il existe des raccourcis pour certaines opérations, utilisez les autant que possible. Par exemple, pour lire un fichier, au lieu de faire un simple open()
, vous pouvez faire :
from codecs import open # open() de codec à exactement la même API, y compris avec "with" f = open('fichier', encoding='encoding')
Les chaînes récupérées seront automatiquement sous forme d’objet ‘unicode’ au lieu d’objet ‘str’ qu’il vous aurait fallut convertir à la main.
Les outils de la dernières chance
Je vous ai menti, si vous ne connaissez pas l’encoding de vos entrées ou de vos sorties, il vous reste encore quelques options.
Sachez cependant que ces options sont des hacks, des trucs à tenter quand tout ce qui a été décrit plus haut a foiré.
Si vous faites bien votre boulot, ça ne doit pas arriver souvent. Une à deux fois max dans votre année, sauf environnement de travail très très merdique.
D’abord, parlons de l’entrée.
Si vous recevez un objet et qu’il vous est impossible de trouver l’encoding, vous pouvez forcer un décodage imparfait avec decode()
en spécifiant le paramètre error
.
Il peut prendre les valeurs suivantes :
'strict'
: lever une exception en cas d’erreur. C’est le comportement par défaut.'ignore'
: tout caractère qui provoque une erreur est ignoré.'replace'
: tout caractère qui provoque une erreur est remplacé par un point d’interrogation.
>>> print 'Père Noël'.decode('ascii') Traceback (most recent call last): File "<stdin>", line 1, in <module> UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 1: ordinal not in range(128) >>> print 'Père Noël'.decode('ascii', errors='ignore') Pre Nol >>> print 'Père Noël'.decode('ascii', errors='replace') P��re No��l
Mozilla vient également à la rescousse avec sa lib chardet qu’il faut donc installer :
pip install chardet
Et qui TENTE (du verbe ‘tenter’, “qui essaye”, et qui donc peut échouer et se tromper) de détecter l’encoding utilisé.
>>> chardet.detect(u'Le Père Noël est une ordure'.encode('utf8')) {'confidence': 0.8063275188616134, 'encoding': 'ISO-8859-2'} >>> chardet.detect(u"Le Père Noël est une ordure j'ai dis enculé".encode('utf8')) {'confidence': 0.87625, 'encoding': 'utf-8'}
Cela marche pas trop mal, mais n’attendez pas de miracles. Plus il y a de texte, plus c’est précis, et plus le paramètre confidence
est proche de 1.
Parlons maintenant de la sortie, c’est à dire le cas où le système qui va recevoir vos données est une grosse quiche qui plante dès qu’on lui donne autre chose que de l’ASCII.
Je ne veux balancer personne, mais mon regard se tourne vers l’administration américaine. Subtilement. De manière insistante.
D’abord, encode()
accepte les mêmes valeurs pour errors
que decode()
. Mais en prime, il accepte 'xmlcharrefreplace'
, très pratique pour les fichiers XML :
>>> u"Et là-bas, tu vois, c'est la coulée du grand bronze".encode('ascii', errors='xmlcharrefreplace') "Et là-bas, tu vois, c'est la coulée du grand bronze"
Enfin, on peut essayer d’obtenir un texte potable en remplaçant les caractères spéciaux par leur équivalent ASCII le plus proche.
Avec l’alphabet latin, c’est très facile :
>>> unicodedata.normalize('NFKD', u"éçûö").encode('ascii', 'ignore') 'ecuo'
Pour des trucs plus avancés comme le cyrilique ou le mandarin, il faut installer unidecode :
pip install unidecode
>>> from unidecode import unidecode >>> print unidecode(u"En russe, Moscou s'écrit Москва") En russe, Moscou s'ecrit Moskva
No related posts.
Merci, merci, merci, et…MERCI.
Ta statue sera équestre. Pas moins.
Fait gaffe aux nombres de pieds en l’air pour le cheval. J’ai pas envie qu’on me prenne pour un macabé.
Pour la matière, le bronze est mon meilleur profile.
s/et le jour où ça se gatte/et le jour où ça se gâte
s/à ou d’autres langage/à où d’autres langages
s/Et certains on plusieurs noms/Et certains ont plusieurs noms
s/c’est ce qu’on appelles les encodings/c’est ce qu’on appelle les encodings
s/Certaines bibliothèque /Certaines bibliothèques
Le sodomiseur de coléoptères :-)
Merci pour l’article, je pensais pas trop mal connaître le sujet, mais visiblement non.
Bah tu sais moi je me dis après chaque article que j’ai bien tout relu et qu’il reste pas de faute. Mais visiblement non.
Franchement merci.
Pour avoir lu plein de choses sur le sujet, mal compris, confondu le vocabulaire, oublié le lendemain, etc… c’est l’article qui explique le mieux et le plus concrètement l’encoding. C’est de la même veine que les articles sur la programmation orientée objet qui sont d’un niveau pédagogique inégalé parmis tout ce que j’ai pu lire. La théorie et les bases sont parfaitement expliquées avec toujours une application concrète. C’est limpide et efficace.
Cela fait un petit moment que je suis régulièrement votre blog, c’est une petite pépite, donc bravo et surtout un grand merci pour le partage car mine du rien c’est du boulot.
\ o /
Yeah, ça part dans mes favoris ! Merci pour l’article !
Premier commentaire constructif de la journée :
Vous avez déjà utilisé cette image d’entête d’article pour un autre article, c’est trop nul, vous perdez votre originalité.
<3
Meeeeeeeeeeeeerde, j’ai eu un doute en plus.
EDIT: m’en fout, je l’ai changé.
Sinon plus sérieusement … c’est un peu perturbant, vous parlez un peu des différences avec Python3 mais la majorité des exemples sont en Python2 et vu que les différences entre python2 et 3 (à ce niveau là en tout cas) sont super chiantes et prêtent à confusion, bah du coup c’est un peu plus dur de bien vous suivre partout (moi qui suis 100% Python3), dommage … Article bien complet sinon :) Merci.
Ah et si l’un de vous à une solution simple pour faire en sorte que les encodings de stdout et stdin se mettent en UTF-8 par défaut, ça serait pas mal. (Surtout que ce sont des propriétés en read only donc, à part des méthodes bizarroïde au lancement de Python (en console), je ne sais même pas si y a moyen de les modifier en live).
Au fait,
je veuxj’aurais bien voulu un tampon :( (tout ça pour ça bitch !)le tampon ça se mérite jeune homme ! N’est pas tamponné qui veut.
@JeromeJ: pour Python3, c’est pareil, exactement le même article sauf que:
- le type ‘unicode’ est appelé ‘str’ et est celui créé par défaut quand on fabrique une chaîne
- le type décodé est appelé ‘byte’
- pas besoin de déclarer “# -*- coding: utf-8 -*-” si ton fichier est en UTF8 car c’est maintenant la valeur par defaut
C’est tout. Tout le reste est pareil.
Quand à s’assurer que toute écriture sur sdtout est en UTF8, c’est sans doute possible en faisant un truc du genre :
Mais je ne le recommanderais pas car :
- ça remplace stout pour tout le code, y compris les libs tierces parties et qui sait ce que ça peut provoquer
- ça rend le code importable (stdout n’est pas en utf8 sous windows, et ça printera un truc illisible)
- ton script peut changer de context en cours d’éxécution, et sdtout ne sera alors plus le même, avec potentiellement un encoding différent
D’une manière générale il vaut mieux spécifier l’encoding par défaut directement sur les fonctions qui font l’opération d’écriture. Par exemple:
Une alternative est de setter la bonne variable d’environnement pour que Python remplisse stdout avec l’encoding que l’on souhaite. Par exemple sous Ubuntu :
http://drj11.wordpress.com/2007/05/14/python-how-is-sysstdoutencoding-chosen/
(ou PYTHONIOENCODING=utf-8:surrogateescape python somescript.py selon les versions)
Cela conditionne par ailleur le résultat de :
@Max: J’aurais essayé !
@Sam: Bien ce que je craignais … Pas mal l’idée de wrapper print avec functools.partial, j’aime !
(C’est bien le prob c’est que je travaille à moitié sous Windows)
Le terminal de windows n’est pas configuré pour afficher de l’UTF8, Python ne pourra rien y changer. Il faut soit y printer l’encoding qu’il utilise actuellement (et se limiter aux caractères qu’il accepte du coup). On peut le trouver en faisant (il me semble):
Mais print devrait se charger de le faire automatiquement en Python3 donc ça devrait pas poser de problème. Simplement tu oublis les caractères chinois.
Soit setter l’encoding avant avec une commande :
http://stackoverflow.com/questions/388490/unicode-characters-in-windows-command-line-how
Ce n’est pas particulièrement lié à Python, c’est pareil dans tous les langages sous Windows car cet OS n’a pas adopté utf8 par default.
“Par défaut, Python plante sur les erreurs d’encoding là ou d’autres langage (comme le PHP) se débrouillent pour vous sortir un truc (qui ne veut rien dire, qui peut corrompre toute votre base de données, mais qui ne plante pas).”
Ouais… bah vaut mieux que ça plante, au moins ça donne l’occasion de réparer proprement.
Merci pour cet excellent résumé.
Il y a encore des choses plus rigolotes à faire avec l’encoding. Par exemple ce fichier est tout à fait valide :
# -*- coding: rot13 -*-
cevag h'Obawbhe fnz'
j’avais découvert ça il y a quelques temps sur linuxfr, avec même en prime l’encodage en brainfuck, mais on peut également imaginer encoder son fichier en whitespace ou tout autre encodage hyper pratique …
Tristement, ça ne marche plus avec Python 3. On pouvait aussi utiliser base64, zlib, etc.
Uhu : http://sebsauvage.net/links/?Q3zlyg
Coïncidence ?
C’est la première fois que j’ai l’espoir de comprendre et d’utiliser correctement les fichues chaînes de caractère en python. Chapeau les mecs ! Mais je flippe quand même de modifier mon code sur ce sujet, qui fonctionne actuellement à peu près.
Par rapport à codec.open, il y a ça dans la doc :
Ça peut être emmerdant ça ? Il y a un moyen simple de faire la conversion à la main ?
Je dirais qu’un bon strip() fera bien l’affaire :-)
En effet, ça marche bien à la lecture, mais du coup pas à l’écriture. Y’a plus qu’à utiliser la version standard sans oublier de convertir, j’ai l’impression.
Autre différence python2/3 : les exceptions. En python3 on peut leur passer un message en unicode, alors qu’en python2 il faut le .encode(“utf-8). Et ça chie avec les docstrings, après…
Bel article, bravo, j’aime bien la structuration en entrée (eh eh les entrées, règle #4, plat (le traitement python), dessert (les sorties, règle #5).
Cependant je vois souvent comme directivepython, les 2 écritures pour UTF8:
# -*- coding: utf8 -*-
ou
# -*- coding: utf-8 -*-
Est-ce la même chose ? Laquelle est à privilégiée ?
C’est kif kif.
Copié collé direct dans mes notes, à côté de : http://sebsauvage.net/python/charsets_et_encoding.html
Merc p〇ʋr cごt art%cle tr⁋s instrucⁱf !
stdin et pas stding dans :
sys.(stding|stdout).encoding.
A part çà, rien à redire, c’est nickel comme d’hab !
C’est l’acceng, c’est pour ça.
Alors là, très bon article !
Quelque soit le langage, le problème le plus récurrent est toujours celui de la manipulation des différents types d’encoding.
Je ne te parle pas de ceux qui développent sous Windows avec un CP-1252 par défaut dans tous les logiciels et qui même avec un réglage de l’IDE finit un jour où l’autre par te ressortir l’encoding par défaut genre .. à la réouverture.
Bref, éternel problème que tu as très bien mis en évidence. Tout le problème est d’origine interface chaise-clavier. Il faut se sortir les doigts du boule pour faire du propre.
Juste merci
Merci grandement!
2 jours que je plante sur ces %?!/%££!! d’encoding, et en 15 minutes… Réglé
Thanx
Continue!!!
C’est laborieux chez leaseweb :/
Ensuite ça a été un probleme de varnish qui s’est bien lancé quand leaseweb a reboot les VPS mais qui pour une raison mystérieuse ne voulait rien savoir :/
Merci pour l’article qui m’a bien aidé sur certains points de blocage mais il y a toujours quelque chose que je ne parviens pas à résoudre : le parsing (avec os.walk) de fichiers qui contiennent des accents. Ce qui me déroute surtout c’est la différence entre Windows et Ubuntu.
Dans mon programme j’ai une fonction (li_geofiles()) chargée de lister les fichiers compatibles (selon leur extension et l’existence de leurs dépendances) contenus dans une arborescence à partir d’un dossier indiqué par l’utilisateur (foldertarget retourné via tkMessageBox.askdirectory) et qui bloque sur les noms de fichiers qui contiennent un accent…mais seulement sur Ubuntu ! Ça fait 2 jours que je me flagelle là-dessus et j’ai essayé qqls solutions :
- un try/except sur le nom de fichier et réencodage à la volée (solution choisie) mais qui a le (gros) défaut de ne pas lister les fichiers avec accents (os.path.isfile ne les reconnaît pas…)
- passer foldertarget en unicode mais ça marche pô (idem avec path.normpath(foldertarget)
Voici la fonction en question :
Si quelqu’un voit le problème…et surtout la solution !
PS : je suis géographe à la base donc pas vraiment un développeur… #indulgence
Un grand merci, enfin un tuto qui va à l’essentiel. J’ai enfin compris comment ça fonctionne.
Juste au Top!
Un super cadeau de Noel qui vient de m’offrir une ou deux nuit planche de moins
Article auquel je me réfère régulièrement, car à chaque fois que je résout mes problèmes d’encodage, je ne me souviens plus comment je les ais résolus et je dois refaire le raisonnement dans mon cerveau.
Juste pour info : y’a des fois, syt.stdout.encoding renvoie None.
Je suis en python 2.7 sous Windows (oui, je sais), quand j’exécute mon script dans la console, j’ai “cp850″. Quand je l’exécute avec Ctrl-B dans Sublime Text, j’ai None.
Du coup, je fais le bourrin comme ça pour écrire sur stdout :
Cela va sans dire, mais je précise que dans mon code précédent, j’ai bien évidemment mis le “from __future__ import unicode_literals” au début de mes fichiers.
Du coup la chaîne “é mon cul cé du poulé” est de l’unicode.
Et ça marche, je vois mes accents, aussi bien dans la console DOS que dans la console Sublime Text.