web-dev-qa-db-fra.com

Pourquoi Python imprime-t-il des caractères Unicode lorsque le codage par défaut est ASCII?

Depuis le 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'instruction print, car le caractère "é" ne fait pas partie de ASCII et je n'ai pas spécifié de codage. Je suppose que je ne comprends pas ce que ASCII étant le codage par défaut.

MODIFIER

J'ai déplacé le montage dans la section Answers et l'ai accepté comme suggéré.

132
Michael Ekoka

Grâce aux fragments de réponses diverses, je pense que nous pouvons coudre une explication.

En essayant d'imprimer une chaîne unicode, u '\ xe9', Python tente implicitement de coder cette chaîne en utilisant le schéma de codage actuellement stocké dans sys.stdout.encoding. Python récupère ce paramètre dans l'environnement d'origine. S'il ne parvient pas à trouver un codage approprié dans l'environnement, il ne revient alors qu'à son défaut, ASCII.

Par exemple, j'utilise un shell bash dont le codage par défaut est UTF-8. Si je lance Python à partir de celui-ci, il récupère et utilise ce paramètre:

$ python

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

Lançons un instant le shell Python et définissons l'environnement de bash avec un encodage factice:

$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.

Relancez ensuite le shell python et vérifiez qu'il est bien rétabli dans son codage 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 recevoir un message d'erreur Nice

>>> 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 rejeter le shell bash.

Nous allons maintenant observer ce qui se passe après que Python ait généré des chaînes. Pour cela, nous allons d’abord démarrer un shell bash dans un terminal graphique (j’utilise Gnome Terminal), puis nous allons configurer le terminal pour qu'il décode la sortie avec ISO-8859-1, aussi appelé latin-1 (les terminaux graphiques ont généralement la possibilité de - Définir le codage des caractères dans l’un de leurs menus déroulants). Notez que cela ne change pas l'encodage de l'environnement Shell, cela ne change que la façon dont le terminal décodera la sortie qui lui est donnée, un peu comme le fait un navigateur Web. Vous pouvez donc modifier le codage 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 le codage 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 génère une chaîne binaire en l'état, le terminal la reçoit et tente de faire correspondre sa valeur à la mappe de caractères latin-1. Dans latin-1, 0xe9 ou 233 donne le caractère "é" et c’est ce que le terminal affiche.

(2) python tente de implicitement ​​encoder la chaîne Unicode avec le schéma actuellement défini dans sys.stdout.encoding, en l'occurrence, il s'agit de "UTF-8". Après le codage 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 tente de décoder 0xc3a9 en utilisant latin-1, mais latin-1 va de 0 à 255 et ne décode donc que les flux d'un octet à la fois. 0xc3a9 ayant une longueur de 2 octets, le décodeur latin-1 l’interprète donc comme 0xc3 (195) et 0xa9 (169), ce qui donne 2 caractères: Ã et ©.

(3) python code le point de code Unicode u '\ xe9' (233) avec le schéma latin-1. La plage de points de code latin-1 est comprise entre 0 et 255 et pointe exactement sur le même caractère qu'unicode dans cette plage. Par conséquent, les points de code Unicode dans cette plage produiront la même valeur lorsqu'ils sont codé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 tente de la faire correspondre sur la carte de caractères latin-1. Tout comme le cas (1), cela donne "é" et c'est ce qui est affiché.

Modifions maintenant les paramètres de codage du terminal en UTF-8 dans le menu déroulant (comme vous le feriez pour changer les paramètres de codage 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 génère une chaîne binaire 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 plus loin) et ne peut donc pas la convertir en un 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 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 carte de caractères Unicode, pointe vers le symbole "é". Le terminal affiche "é".

(6) python code une chaîne Unicode avec latin-1, il génère 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 l'affaire (4).

Conclusions: - Python génère des chaînes non-unicode sous forme de données brutes, sans tenir compte de son codage par défaut. Il arrive au terminal de les afficher si son codage actuel correspond aux données. - Python génère des chaînes Unicode après les avoir codé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 de codage. - l'encodage du terminal est indépendant de celui du Shell.


Plus de détails sur l'unicode, UTF-8 et latin-1:

Unicode est fondamentalement une table de caractères où certaines clés (points de code) ont été classiquement assignées pour pointer sur 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, de même que latin-1 et Unicode de 0 à 255. En d'autres termes, 0x41 pointe vers 'A' en ASCII, latin-1 et Unicode, 0xc8. pointe vers 'Ü' en 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 pour être représentés électroniquement. C'est ce que sont les schémas d'encodage. Il existe différents schémas de codage Unicode (utf7, UTF-8, UTF-16, UTF-32). La méthode de codage la plus intuitive et la plus simple consiste à utiliser simplement la valeur d’un point de code de la carte Unicode pour sa forme électronique, mais Unicode compte actuellement plus d’un million de points de code, ce qui signifie que certains d'entre eux nécessitent 3 octets pour être remplacés. exprimé. Pour travailler efficacement avec du texte, un mappage 1: 1 serait plutôt peu pratique, car il faudrait 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, quel que soit leur besoin réel.

La plupart des schémas d'encodage présentent des lacunes en termes d'encombrement. Les plus économiques ne couvrent pas tous les points de code Unicode, par exemple, ascii ne couvre que les 128 premiers, tandis que le latin-1 en couvre les 256 premiers. D'autres finissent par être plus complets. 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 est égal à 65, nécessite encore 2 octets de stockage en UTF-16). UTF-32 est encore plus inutile 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 octets. Dans le cadre de sa stratégie de codage, UTF-8 met en code les points de code avec des bits de drapeau qui indiquent (vraisemblablement aux décodeurs) leurs besoins en espace et leurs limites.

codage UTF-8 de 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 le codage
  • Le 0 initial est un drapeau qui indique au décodeur UTF-8 que ce point de code ne nécessitera qu'un octet.
  • lors du codage, UTF-8 ne modifie pas la valeur des points de code dans cette plage spécifique (c’est-à-dire que 65 codé en UTF-8 vaut également 65). Étant donné que Unicode et ASCII sont également compatibles dans la même plage, UTF-8 et ASCII sont é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 supérieurs à 127 (non ASCII):

110x xxxx 10xx xxxx            <-- (from 128 to 2047)
1110 xxxx 10xx xxxx 10xx xxxx  <-- (from 2048 to 65535)
  • les bits "110" en tête indiquent au décodeur UTF-8 le début d'un point de code codé sur 2 octets, alors que "1110" en indique 3, 11110 en indique 4, etc.
  • les bits de drapeau internes '10' servent à signaler le début d'un octet intérieur.
  • de nouveau, les x marquent l'espace où la valeur du point de code Unicode est stockée après le codage.

par exemple. Le code Unicode est 0xe9 (233).

1110 1001    <-- 0xe9

Lorsque UTF-8 code cette valeur, il détermine que la valeur est supérieure à 127 et inférieure à 2048. Il doit donc être codé 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 et devient 0xc3a9. Ce qui est exactement comment le terminal le reçoit. Si votre terminal est configuré pour décoder les chaînes à l'aide de latin-1 (un des codages hérités non-unicode), vous verrez alors ©, car il se trouve que 0xc3 dans le latin-1 pointe vers Å et 0xa9 vers ©.

101
Michael Ekoka

Lorsque les caractères Unicode sont imprimés sur la sortie standard, sys.stdout.encoding est utilisé. Un caractère non-Unicode est supposé être dans sys.stdout.encoding et vient d'être 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 version ultérieure ignore les codages sous Windows et utilise les API Unicode pour écrire Unicode sur le terminal. Aucun avertissement UnicodeEncodeError et le caractère correct ne sont affichés si la police le prend en charge. Même si la police not - ne la prend pas en charge, les caractères peuvent toujours être copiés-collés depuis le terminal vers une application avec une police prise en charge et elle sera correcte. Améliorer!

25
Mark Tolonen

Le Python REPL essaie de choisir quel encodage utiliser dans votre environnement. S'il trouve quelque chose de sain, alors tout fonctionne. C'est quand il ne peut pas comprendre ce qui se passe qu'il tombe en panne.

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

Vous avez / spécifié un codage en entrant une chaîne Unicode explicite. Comparez les résultats de ne pas utiliser le préfixe u.

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

>>> 

Dans le cas de \xe9, Python assume votre codage par défaut (Ascii), ce qui permet d’imprimer ... quelque chose de blanc.

4
Mark Rushakoff

Ça marche pour moi:

import sys
stdin, stdout = sys.stdin, sys.stdout
reload(sys)
sys.stdin, sys.stdout = stdin, stdout
sys.setdefaultencoding('utf-8')
0
user3611630

Selon Encodages et conversions de chaîne implicite/implicite Python :

  • Lorsque printing unicode, c'est encoded avec <file>.encoding.
    • lorsque encoding n'est pas défini, unicode est implicitement converti en str (étant donné que le codec utilisé est sys.getdefaultencoding(), c'est-à-dire ascii, tout caractère national provoquerait un UnicodeEncodeError)
    • pour les flux standard, la variable encoding est déduite de l'environnement. Il est généralement défini pour les flux tty (à partir des paramètres régionaux du terminal), mais ne sera probablement pas défini pour les canaux
      • donc, un print u'\xe9' est susceptible de réussir lorsque la sortie est vers un terminal, et échoue s'il est redirigé. Une solution consiste à encode() la chaîne avec le codage souhaité avant printing.
  • Lorsque printing str, les octets sont envoyés au flux tels quels. Les glyphes affichés par le terminal dépendent de ses paramètres régionaux.
0
ivan_pozdeev