Pourquoi Python imprime-t-il des caractères unicode lorsque l'encodage par défaut est ASCII?

du shell python 2.6:

>>> import sys
>>> print sys.getdefaultencoding()
ascii
>>> print u'xe9'
é
>>> 

Je m'attendais à avoir du charabia ou une erreur après l'impression, puisque le caractère" é " ne fait pas partie de ASCII et que je n'ai pas spécifié d'encodage. Je suppose que je ne comprends pas ce que signifie ASCII étant l'encodage par défaut.

MODIFIER

j'ai déplacé l'édition à la section réponses et l'ai acceptée comme le suggère.

130
demandé sur Michael Ekoka 2010-04-08 04:03:08

6 réponses

grâce à des bribes de réponses diverses, je pense que nous pouvons trouver 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.la sortie standard stdout.encodage. Python récupère en fait ce paramètre dans l'environnement d'où il a été initié. S'il ne peut pas trouver un encodage approprié à partir de l'environnement, ce n'est qu'alors qu'il retourne à son défaut , ASCII.

par exemple, j'utilise un shell bash qui code par défaut UTF-8. Si je démarre Python à partir de lui, il prend et utilise ce paramètre:

$ python

>>> import sys
>>> print sys.stdout.encoding
UTF-8

sortons un instant du 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.

redémarrez alors le shell python et vérifiez qu'il retourne bien à son encodage ASCII par défaut.

$ python

>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968

Bingo!

si vous essayez maintenant de produire un caractère unicode en dehors de ascii, vous devriez obtenir un message d'erreur agréable

>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' 
in position 0: ordinal not in range(128)

permet de sortir de Python et de rejeter le shell bash.

nous allons maintenant observer ce qui se passe après la sortie des chaînes de Python. Pour cela, nous allons d'abord lancer un shell bash à l'intérieur d'un terminal graphique (J'utilise Gnome Terminal) et nous allons régler le terminal pour décoder la sortie avec ISO-8859-1 alias latin-1 (les terminaux graphiques ont généralement l'option de définir L'encodage des caractères dans l'un de leurs menus déroulants). Notez que cela ne change pas l'encodage actuel de l'environnement shell , il change seulement la façon dont le terminal lui-même va décoder la sortie qu'il est donné, un peu comme le fait un navigateur web. Vous pouvez donc changer l'encodage du terminal, indépendamment de l'environnement du shell. Pour cela, nous allons commencer Python à partir du shell et de vérifier que sys.la sortie standard stdout.l'encodage est défini à l'encodage de l'environnement 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 sorties chaîne binaire comme c'est, le terminal reçoit et tente de faire correspondre sa valeur avec des caractères latin-1 carte. En latin-1, 0xe9 ou 233 donne le caractère "é" et donc c'est ce que le terminal affiche.

(2) python tente de implicitement encoder la chaîne Unicode avec quel que soit le schéma actuellement défini dans LA SAJR.la sortie standard stdout.encodage, dans ce cas, C'est "UTF-8". Après l'encodage UTF-8, la chaîne binaire résultante est '\xc3\xa9' (voir explication plus loin). Le Terminal reçoit le flux en tant que tel et essaie de décoder 0xc3a9 en utilisant le latin-1, mais le latin-1 va de 0 à 255 et donc, ne décode que les flux 1 octet à la fois. 0xc3a9 est long de 2 Octets, le décodeur latin-1 l'interprète donc comme 0xc3 (195) et 0xa9 (169) et qui donne 2 caractères: Ã et ©.

(3) python code unicode point u '\xe9 ' (233) avec le schéma latin-1. Il s'avère que la plage de points de code latin-1 est 0-255 et les points au même caractère exact 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. Donc u ' \xe9 '(233) encodé en latin-1 donnera aussi la chaîne binaire'\xe9'. 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 afficher.

changeons maintenant les paramètres d'encodage du terminal en UTF-8 à partir du menu déroulant (comme vous changeriez les paramètres d'encodage de votre navigateur web). Pas besoin d'arrêter Python ou de redémarrer le shell. L'encodage 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 affiche une chaîne de caractères binaire telle quelle. Le Terminal tente de décoder ce flux avec L'UTF-8. Mais L'UTF-8 ne comprend pas la valeur 0xe9 (voir explication plus loin) et est donc incapable de la convertir en point de code unicode. Aucun point de code trouvé, aucun caractère imprimé.

(5) python tente de implicitement encoder la chaîne Unicode avec tout ce qui est dans sys.la sortie standard stdout.encodage. 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 aussi UTF-8. Il renvoie la valeur de code 0xe9 (233), qui sur L'Unicode carte de caractères pointant vers le symbole "é". Le Terminal affiche "é".

(6) python code la 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 dans le cas (4).

Conclusions: - Python affiche des chaînes non unicode comme données brutes, sans prendre en compte son encodage par défaut. Le terminal ne fait que les afficher si son encodage actuel correspond aux données. - Sorties Python Les chaînes Unicode après les avoir encodées en utilisant le schéma spécifié dans sys.la sortie standard stdout.encodage. - Python obtient ce paramètre de l'environnement de l'interpréteur de commandes. - le terminal affiche la sortie selon 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 où certaines touches (code points) ont été assignés conventionnellement pour pointer vers certains symboles. 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, de même que latin-1 et Unicode de 0 à 255. C'est-à-dire, 0x41 points à " A "en ASCII, latin-1 et Unicode, 0xc8 points à" Ü "en latin-1 et Unicode, 0xe9 points à" é " en latin-1 et Unicode.

lorsqu'on travaille avec des appareils électroniques, points de code Unicode besoin d'une façon efficace d'être représenté par voie électronique. C'est ce que le système de codage. Divers schémas D'encodage Unicode existent (utf7, UTF-8, UTF-16, UTF-32). L'approche d'encodage 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 possède actuellement plus d'un million de points de code, ce qui signifie que certains d'entre eux nécessitent 3 octets pour être exprimés. Pour travailler efficacement avec le texte, une mise en correspondance de 1 à 1 serait plutôt impraticable, puisque cela exigerait que tous les points de code soient stockés dans exactement la même quantité d'espace, avec un minimum de 3 octets par caractère, indépendamment de leur besoin réel.

la plupart des schémas d'encodage comportent des lacunes en ce qui concerne les besoins 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 complet finissent aussi par être gaspilleurs, puisque 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 dans la gamme ascii ("B" qui est de 65, nécessite encore 2 octets de stockage dans UTF-16). UTF-32 est encore plus inutile car il stocke tous les caractères en 4 octets.

UTF-8 s'avère avoir résolu habilement le dilemme, avec un schéma capable de stocker des points de code avec une quantité variable d'espaces byte. Dans le cadre de son encodage stratégie, UTF-8 laces points de code avec des bits de drapeau qui indiquent (présumément aux décodeurs) leurs besoins d'espace et leurs limites.

l'encodage UTF-8 de points de code unicode en ascii (plage de 0 à 127):

0xxx xxxx  (in binary)
  • les x montrent l'espace réel réservé pour "stocker" le point de code lors de l'encodage
  • le 0 de tête est un drapeau qui indique au décodeur UTF-8 que ce le point de code ne requerra qu'un octet.
  • lors de l'encodage, UTF-8 ne change pas la valeur des points de code dans cette plage spécifique (i.e. 65 encodé dans UTF-8 est également 65). Etant donné Qu'Unicode et ASCII sont également compatibles dans la même gamme, il rend accessoirement UTF-8 et ASCII également compatibles dans cette gamme.

e.g. le point de code Unicode pour 'B' est '0x42' ou 0100 0010 en binaire (comme nous l'avons dit, c'est le même en ASCII). Après l'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)

l'encodage UTF-8 de points de code Unicode ci-dessus 127 (non ascii):

110x xxxx 10xx xxxx            <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx  <-- (from 2048 to 65535)
  • les bits principaux '110' indiquent au décodeur UTF-8 le début d'un point de code encodé en 2 octets, tandis que '1110' indique 3 octets, 11110 indiquerait 4 octets et ainsi de suite.
  • les bits internes du drapeau " 10 " sont utilisés pour signaler le début d'un octet interne.
  • encore une fois, les x marquent l'espace où la valeur du point du code Unicode est stockée après encodage.

par exemple, 'é' point de code Unicode est 0xe9 (233).

1110 1001    <-- 0xe9

quand UTF-8 Code cette valeur, il détermine que la valeur est supérieure à 127 et inférieure à 2048, donc devrait être encodé en 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

les points de code Unicode 0xe9 après encodage UTF-8 deviennent 0xc3a9. C'est exactement comme ça que le terminal le reçoit. Si votre terminal est configuré pour décoder les chaînes en utilisant latin-1 (un des encodages hérités non unicode), vous verrez é, parce qu'il se trouve que 0xc3 en latin-1 pointe vers à et 0xa9 vers ©.

92
répondu Michael Ekoka 2014-02-23 13:09:38

lorsque les caractères Unicode sont imprimés sur stdout, sys.stdout.encoding est utilisé. Un caractère non Unicode est supposé être dans sys.stdout.encoding et est simplement envoyé au terminal. Sur mon système (Python 2):

>>> import unicodedata as ud
>>> import sys
>>> sys.stdout.encoding
'cp437'
>>> ud.name(u'\xe9') # U+00E9 Unicode codepoint
'LATIN SMALL LETTER E WITH ACUTE'
>>> ud.name('\xe9'.decode('cp437')) 
'GREEK CAPITAL LETTER THETA'
>>> '\xe9'.decode('cp437') # byte E9 decoded using code page 437 is U+0398.
u'\u0398'
>>> ud.name(u'\u0398')
'GREEK CAPITAL LETTER THETA'
>>> print u'\xe9' # Unicode is encoded to CP437 correctly
é
>>> print '\xe9'  # Byte is just sent to terminal and assumed to be CP437.
Θ

sys.getdefaultencoding() n'est utilisé que lorsque Python n'a pas d'autre option.

notez que Python 3.6 ou plus tard ignore les encodages sur Windows et utilise les API Unicode pour écrire Unicode sur le terminal. Pas de mise en garde UnicodeEncodeError et le caractère correct est affiché si la police prend en charge. Même si la police ne supporte pas la police , les caractères peuvent toujours être coupés-n-collés du terminal à une application avec une police de soutien et ce sera correct. De mise à niveau!

24
répondu Mark Tolonen 2018-03-29 02:25:17

le REPL Python essaie de récupérer ce qui encodage à utiliser dans votre environnement. S'il trouve quelque chose de sain, tout fonctionne. C'est quand il ne peut pas comprendre ce qu'il se passe qu'il dérange.

>>> print sys.stdout.encoding
UTF-8
8
répondu Ignacio Vazquez-Abrams 2010-04-08 00:07:43

Vous ont , a précisé un encodage en entrant explicite d'une chaîne Unicode. Comparez les résultats de la non-utilisation du préfixe u .

>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> '\xe9'
'\xe9'
>>> u'\xe9'
u'\xe9'
>>> print u'\xe9'
é
>>> print '\xe9'

>>> 

dans le cas de \xe9 alors Python assume votre encodage par défaut (Ascii), donc l'impression ... quelque chose de blanc.

4
répondu Mark Rushakoff 2010-04-08 00:08:07

selon par défaut Python/encodages de chaînes implicites et conversions :

  • quand print ing unicode , c'est encode d avec <file>.encoding .
    • lorsque le encoding n'est pas défini, le unicode est implicitement converti en str (puisque le codec pour cela est sys.getdefaultencoding() , c.-à-d. ascii , tout caractère national provoquerait un UnicodeEncodeError )
    • pour les cours d'eau standards, le encoding est déduit de l'environnement. Il est typiquement défini pour les flux tty (à partir des paramètres locaux du terminal), mais il est probable qu'il ne sera pas défini pour les pipes
      • ainsi un print u'\xe9' est susceptible de réussir lorsque la sortie est à un terminal, et échouer si elle est redirigée. Une solution est de encode() la chaîne avec le codage désiré avant print ing.
  • quand print ing str , les octets sont envoyés au flux tel quel. Les glyphes que le terminal affiche dépendent de ses paramètres de localisation.
0
répondu ivan_pozdeev 2018-05-01 06:35:25

Ça marche pour moi:

import sys
stdin, stdout = sys.stdin, sys.stdout
reload(sys)
sys.stdin, sys.stdout = stdin, stdout
sys.setdefaultencoding('utf-8')
-1
répondu user3611630 2014-08-12 10:12:27