Grâce à des bribes de diverses réponses, je pense que nous pouvons recoudre une explication.
En essayant d'imprimer une chaîne Unicode, u '\ xe9', Python essaie implicitement d'encoder cette chaîne en utilisant le schéma d'encodage actuellement stocké dans sys.stdout.encoding. Python récupère en fait ce paramètre à partir de l'environnement à partir duquel il a été lancé. S'il ne trouve pas de codage approprié dans l'environnement, ce n'est qu'alors qu'il revient à sa valeur par défaut , ASCII.
Par exemple, j'utilise un shell bash dont le codage par défaut est UTF-8. Si je démarre Python à partir de celui-ci, il récupère et utilise ce paramètre:
$ python
>>> import sys
>>> print sys.stdout.encoding
UTF-8
Quittons un instant le shell Python et définissons l'environnement de bash avec un faux encodage:
$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.
Ensuite, redémarrez le shell python et vérifiez qu'il revient bien à son encodage ascii par défaut.
$ python
>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968
Bingo!
Si vous essayez maintenant de sortir un caractère unicode en dehors de ascii, vous devriez obtenir un joli message d'erreur
>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9'
in position 0: ordinal not in range(128)
Permet de quitter Python et de supprimer le shell bash.
Nous allons maintenant observer ce qui se passe après que Python a produit des chaînes. Pour cela, nous allons d'abord démarrer un shell bash dans un terminal graphique (j'utilise Gnome Terminal) et nous allons configurer le terminal pour décoder la sortie avec ISO-8859-1 aka latin-1 (les terminaux graphiques ont généralement une option pour Set Character Encodage dans l'un de leurs menus déroulants). Notez que cela ne change pas le codage de l'environnement shell réel , cela ne change que la façon dont le terminal lui-même décodera la sortie qui lui est donnée, un peu comme le fait un navigateur Web. Vous pouvez donc changer l'encodage du terminal, indépendamment de l'environnement du shell. Commençons ensuite Python à partir du shell et vérifions que sys.stdout.encoding est défini sur l'encodage de l'environnement du shell (UTF-8 pour moi):
$ python
>>> import sys
>>> print sys.stdout.encoding
UTF-8
>>> print '\xe9' # (1)
é
>>> print u'\xe9' # (2)
é
>>> print u'\xe9'.encode('latin-1') # (3)
é
>>>
(1) python renvoie la chaîne binaire telle quelle, le terminal la reçoit et essaie de faire correspondre sa valeur avec la table de caractères latin-1. En latin-1, 0xe9 ou 233 donne le caractère "é" et c'est donc ce que le terminal affiche.
(2) python tente de coder implicitement la chaîne Unicode avec le schéma actuellement défini dans sys.stdout.encoding, dans ce cas, c'est "UTF-8". Après le codage UTF-8, la chaîne binaire résultante est '\ xc3 \ xa9' (voir l'explication ultérieure). Le terminal reçoit le flux en tant que tel et essaie de décoder 0xc3a9 en utilisant latin-1, mais latin-1 passe de 0 à 255 et donc, ne décode que les flux 1 octet à la fois. 0xc3a9 fait 2 octets de long, le décodeur latin-1 l'interprète donc comme 0xc3 (195) et 0xa9 (169) et cela donne 2 caractères: Ã et ©.
(3) python encode le point de code unicode u '\ xe9' (233) avec le schéma latin-1. Il s'avère que la plage de points de code latin-1 est comprise entre 0 et 255 et pointe exactement le même caractère que Unicode dans cette plage. Par conséquent, les points de code Unicode dans cette plage donneront la même valeur lorsqu'ils sont encodés en latin-1. Ainsi, u '\ xe9' (233) encodé en latin-1 donnera également la chaîne binaire '\ xe9'. Le terminal reçoit cette valeur et essaie de la faire correspondre sur la carte de caractères latin-1. Tout comme le cas (1), il donne "é" et c'est ce qui est affiché.
Modifions maintenant les paramètres d'encodage du terminal en UTF-8 dans le menu déroulant (comme si vous changiez les paramètres d'encodage de votre navigateur Web). Pas besoin d'arrêter Python ou de redémarrer le shell. Le codage du terminal correspond maintenant à celui de Python. Essayons d'imprimer à nouveau:
>>> print '\xe9' # (4)
>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)
>>>
(4) python génère un binaire chaîne telle quelle. Le terminal tente de décoder ce flux avec UTF-8. Mais UTF-8 ne comprend pas la valeur 0xe9 (voir l'explication ultérieure) et est donc incapable de la convertir en un point de code Unicode. Aucun point de code trouvé, aucun caractère imprimé.
(5) python tente d' encoder implicitement la chaîne Unicode avec tout ce qui se trouve dans sys.stdout.encoding. Toujours "UTF-8". La chaîne binaire résultante est '\ xc3 \ xa9'. Le terminal reçoit le flux et tente de décoder 0xc3a9 en utilisant également UTF-8. Il renvoie la valeur de code 0xe9 (233), qui sur la table de caractères Unicode pointe vers le symbole «é». Le terminal affiche "é".
(6) python encode une chaîne unicode avec latin-1, il donne une chaîne binaire avec la même valeur '\ xe9'. Encore une fois, pour le terminal, c'est à peu près la même chose que le cas (4).
Conclusions: - Python génère des chaînes non-Unicode sous forme de données brutes, sans tenir compte de son encodage par défaut. Le terminal les affiche juste si son encodage actuel correspond aux données. - Python génère des chaînes Unicode après les avoir encodées à l'aide du schéma spécifié dans sys.stdout.encoding. - Python obtient ce paramètre de l'environnement du shell. - le terminal affiche la sortie en fonction de ses propres paramètres d'encodage. - l'encodage du terminal est indépendant de celui du shell.
Plus de détails sur unicode, UTF-8 et latin-1:
Unicode est essentiellement une table de caractères dans laquelle certaines touches (points de code) ont été classiquement assignées pour pointer vers certains symboles. par exemple, par convention, il a été décidé que la clé 0xe9 (233) est la valeur pointant vers le symbole «é». ASCII et Unicode utilisent les mêmes points de code de 0 à 127, comme le font latin-1 et Unicode de 0 à 255. Autrement dit, 0x41 pointe vers «A» en ASCII, latin-1 et Unicode, 0xc8 pointe vers «Ü» dans latin-1 et Unicode, 0xe9 pointe vers «é» en latin-1 et Unicode.
Lorsque vous travaillez avec des appareils électroniques, les points de code Unicode ont besoin d'un moyen efficace d'être représentés électroniquement. C'est ce que sont les schémas d'encodage. Différents schémas de codage Unicode existent (utf7, UTF-8, UTF-16, UTF-32). L'approche de codage la plus intuitive et la plus simple serait d'utiliser simplement la valeur d'un point de code dans la carte Unicode comme valeur pour sa forme électronique, mais Unicode a actuellement plus d'un million de points de code, ce qui signifie que certains d'entre eux nécessitent 3 octets pour être exprimé. Pour travailler efficacement avec du texte, un mappage 1 à 1 serait plutôt peu pratique, car il exigerait que tous les points de code soient stockés exactement dans la même quantité d'espace, avec un minimum de 3 octets par caractère, quel que soit leur besoin réel.
La plupart des schémas d'encodage présentent des lacunes en termes d'espace, les plus économiques ne couvrent pas tous les points de code unicode, par exemple ascii ne couvre que les 128 premiers, tandis que latin-1 couvre les 256 premiers. D'autres qui essaient d'être plus complets finissent également par étant un gaspillage, car ils nécessitent plus d'octets que nécessaire, même pour les caractères «bon marché» courants. UTF-16 par exemple, utilise un minimum de 2 octets par caractère, y compris ceux de la plage ascii («B» qui vaut 65, nécessite toujours 2 octets de stockage en UTF-16). UTF-32 est encore plus coûteux car il stocke tous les caractères sur 4 octets.
UTF-8 a résolu intelligemment le dilemme, avec un schéma capable de stocker des points de code avec une quantité variable d'espaces d'octets. Dans le cadre de sa stratégie de codage, UTF-8 associe des points de code à des bits d'indicateur qui indiquent (vraisemblablement aux décodeurs) leurs besoins en espace et leurs limites.
Codage UTF-8 des points de code unicode dans la plage ascii (0-127):
0xxx xxxx (in binary)
- les x indiquent l'espace réel réservé pour "stocker" le point de code pendant l'encodage
- Le 0 en tête est un indicateur qui indique au décodeur UTF-8 que ce point de code ne nécessitera qu'un octet.
- lors du codage, UTF-8 ne change pas la valeur des points de code dans cette plage spécifique (c'est-à-dire que 65 encodés en UTF-8 valent également 65). Étant donné que Unicode et ASCII sont également compatibles dans la même plage, cela rend d'ailleurs UTF-8 et ASCII également compatibles dans cette plage.
Par exemple, le point de code Unicode pour 'B' est '0x42' ou 0100 0010 en binaire (comme nous l'avons dit, c'est la même chose en ASCII). Après encodage en UTF-8, il devient:
0xxx xxxx <-- UTF-8 encoding for Unicode code points 0 to 127
*100 0010 <-- Unicode code point 0x42
0100 0010 <-- UTF-8 encoded (exactly the same)
Codage UTF-8 des points de code Unicode au-dessus de 127 (non-ascii):
110x xxxx 10xx xxxx <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx <-- (from 2048 to 65535)
- les bits de tête «110» indiquent au décodeur UTF-8 le début d'un point de code codé sur 2 octets, alors que «1110» indique 3 octets, 11110 indiquerait 4 octets et ainsi de suite.
- les bits d'indicateur internes «10» sont utilisés pour signaler le début d'un octet interne.
- à nouveau, les x marquent l'espace où la valeur du point de code Unicode est stockée après le codage.
Par exemple, le point de code Unicode 'é' est 0xe9 (233).
1110 1001 <-- 0xe9
Lorsque UTF-8 encode cette valeur, il détermine que la valeur est supérieure à 127 et inférieure à 2048 et doit donc être encodée sur 2 octets:
110x xxxx 10xx xxxx <-- UTF-8 encoding for Unicode 128-2047
***0 0011 **10 1001 <-- 0xe9
1100 0011 1010 1001 <-- 'é' after UTF-8 encoding
C 3 A 9
Le code Unicode 0xe9 pointe après le codage UTF-8 devient 0xc3a9. C'est exactement ainsi que le terminal le reçoit. Si votre terminal est configuré pour décoder des chaînes en utilisant latin-1 (l'un des encodages hérités non-unicode), vous verrez à ©, car il se trouve que 0xc3 en latin-1 pointe vers à et 0xa9 vers ©.