2010-04-08 11 views
126

Desde la terminal de Python 2.6:¿Por qué Python imprime caracteres Unicode cuando la codificación predeterminada es ASCII?

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

que esperaba tener ya sea un poco de jerga o un error después de la declaración de impresión, ya que el carácter "e" no es parte de ASCII y no he especificado una codificación . Supongo que no entiendo por qué ASCII es la codificación predeterminada.

EDITAR

I moved the edit to the Answers section and accepted it as suggested.

+5

que sería muy bueno si pudiera convertir esa edición * * en una respuesta en vez y aceptarlo. – mercator

+2

Al imprimir ''\ xe9'' en un terminal configurado para UTF-8, ** no ** se imprimirá' é'. Imprimirá un carácter de reemplazo (generalmente un signo de interrogación) ya que '\ xe9' no es una secuencia UTF-8 válida (le faltan dos bytes que deberían haber seguido ese byte inicial). Sin duda, ** no ** se interpretará como Latin-1 en su lugar. –

+2

@MartijnPieters Sospecho que es posible que haya rozado la parte donde especifiqué que el terminal está configurado para decodificar en ISO-8859-1 (latin1) cuando saqué '\ xe9' para imprimir' é'. –

Respuesta

84

Gracias a las piezas de varias respuestas, creo que podemos sumar una explicación.

Al tratar de imprimir una cadena Unicode, u '\ xe9', Python implícitamente intenta codificar esa cadena utilizando el esquema de codificación actualmente almacenado en sys.stdout.encoding. Python realmente recoge esta configuración del entorno desde el que se inició. Si no puede encontrar una codificación adecuada del entorno, solo volverá a su predeterminado, ASCII.

Por ejemplo, utilizo un shell bash cuya codificación predeterminada es UTF-8. Si comienzo del pitón de ella, se recoge y utiliza esa configuración:

$ python 

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

Let de una salida momento en que el terminal de Python y configurar el entorno de fiesta con un poco de codificación falsa:

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

continuación, inicie el pitón shell de nuevo y verificar que efectivamente revierte a su codificación ascii predeterminada.

$ python 

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

Bingo!

Si ahora hay que intentar sacar algo de carácter Unicode fuera del ASCII que debería obtener un mensaje de error agradable

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

Permite la salida de Python y desechar la cáscara del golpe.

Ahora vamos a observar lo que ocurre después de que Python emita cadenas. Para esto, primero comenzaremos un shell bash dentro de un terminal gráfico (uso el terminal Gnome) y configuraremos el terminal para decodificar la salida con ISO-8859-1 también conocido como latin-1 (los terminales gráficos usualmente tienen una opción para Establecer Codificación de caracteres en uno de sus menús desplegables). Tenga en cuenta que esto no cambia la actual codificación del entorno de shell, solo cambia la forma en que el terminal mismo decodificará la salida que se le da, de forma similar a como lo hace un navegador web. Por lo tanto, puede cambiar la codificación del terminal, independientemente del entorno del shell. Vamos a empezar entonces Python de la concha y verificar que sys.stdout.encoding se establece en la codificación del entorno de shell (UTF-8 para mí):

$ python 

>>> import sys 

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

>>> print '\xe9' # (1) 
é 
>>> print u'\xe9' # (2) 
é 
>>> print u'\xe9'.encode('latin-1') # (3) 
é 
>>> 

(1) salidas pitón cadena binaria como es, el terminal recibe e intenta hacer coincidir su valor con el mapa de caracteres latin-1. En latin-1, 0xe9 o 233 produce el caracter "é" y eso es lo que muestra la terminal.

(2) intentos de pitón a implícitamente codificar la cadena Unicode con cualquier esquema se encuentra actualmente en sys.stdout.encoding, en este caso es "UTF-8". Después de la codificación UTF-8, la cadena binaria resultante es '\ xc3 \ xa9' (ver explicación posterior). El terminal recibe el flujo como tal e intenta decodificar 0xc3a9 usando latin-1, pero latin-1 va de 0 a 255, por lo que solo decodifica las secuencias de 1 byte a la vez. 0xc3a9 tiene 2 bytes de longitud, el decodificador latin-1 lo interpreta como 0xc3 (195) y 0xa9 (169) y eso produce 2 caracteres: Ã y ©.

(3) python codifica el punto de código unicode u '\ xe9' (233) con el esquema latin-1. Resulta que el rango de los puntos de código latin-1 es 0-255 y apunta al mismo caracter exacto que Unicode dentro de ese rango. Por lo tanto, los puntos de código Unicode en ese rango producirán el mismo valor cuando se codifiquen en latin-1. Así que u '\ xe9' (233) codificado en latin-1 también producirá la cadena binaria '\ xe9'. El terminal recibe ese valor e intenta hacer coincidirlo en el mapa de caracteres latin-1. Al igual que en el caso (1), arroja "é" y eso es lo que se muestra.

Ahora cambiemos la configuración de codificación del terminal a UTF-8 en el menú desplegable (como si cambiara la configuración de codificación de su navegador web). No es necesario detener Python o reiniciar el shell. La codificación del terminal ahora coincide con la de Python. Vamos a intentar imprimir de nuevo:

>>> print '\xe9' # (4) 

>>> print u'\xe9' # (5) 
é 
>>> print u'\xe9'.encode('latin-1') # (6) 

>>> 

(4) emite una pitón binario cadena como es. La terminal intenta descifrar esa secuencia con UTF-8. Pero UTF-8 no entiende el valor 0xe9 (ver explicación posterior) y por lo tanto no puede convertirlo a un punto de código Unicode. No se ha encontrado ningún punto de código, no se ha impreso ningún carácter.

(5) python intenta implícitamente codificar la cadena Unicode con lo que está en sys.stdout.encoding. Aún "UTF-8". La cadena binaria resultante es '\ xc3 \ xa9'. El terminal recibe la transmisión e intenta decodificar 0xc3a9 también usando UTF-8. Produce el valor de código de retorno 0xe9 (233), que en el mapa de caracteres Unicode apunta al símbolo "é". El terminal muestra "é".

(6) python codifica una cadena unicode con latin-1, produce una cadena binaria con el mismo valor '\ xe9'. De nuevo, para el terminal, esto es más o menos lo mismo que caso (4).

Conclusiones: - Python genera cadenas no unicode como datos sin procesar, sin considerar su codificación predeterminada. El terminal simplemente los muestra si su codificación actual coincide con los datos. - Python genera cadenas Unicode después de codificarlas usando el esquema especificado en sys.stdout.encoding. - Python obtiene esa configuración del entorno del shell. - el terminal muestra la salida de acuerdo con su propia configuración de codificación. - la codificación del terminal es independiente de la del shell.


Más detalles sobre Unicode, UTF-8 y latino-1:

Unicode es básicamente una tabla de caracteres, donde algunas teclas (código de puntos) han sido asignados convencionalmente a señalar algunos símbolos. p.ej. por convención se ha decidido que la clave 0xe9 (233) es el valor que apunta al símbolo 'é'. ASCII y Unicode usan los mismos puntos de código de 0 a 127, al igual que latin-1 y Unicode de 0 a 255. Es decir, 0x41 puntos a 'A' en ASCII, latin-1 y Unicode, 0xc8 apunta a 'Ü' en latin-1 y Unicode, 0xe9 apunta a 'é' en latin-1 y Unicode.

Al trabajar con dispositivos electrónicos, los puntos de código Unicode necesitan una forma eficiente de ser representados electrónicamente. De eso se tratan los esquemas de codificación. Existen varios esquemas de codificación Unicode (utf7, UTF-8, UTF-16, UTF-32). El enfoque de codificación más intuitivo y directo sería simplemente usar el valor de un punto de código en el mapa Unicode como su valor para su forma electrónica, pero Unicode actualmente tiene más de un millón de puntos de código, lo que significa que algunos de ellos requieren 3 bytes para ser expresado Para trabajar de manera eficiente con el texto, una asignación de 1 a 1 sería bastante impracticable, ya que requeriría que todos los puntos de código se almacenaran exactamente en la misma cantidad de espacio, con un mínimo de 3 bytes por carácter, independientemente de su necesidad real.

La mayoría de los esquemas de codificación tienen deficiencias con respecto al espacio requerido, los más económicos no cubren todos los puntos de códigos Unicode, por ejemplo, ASCII solo cubre los primeros 128, mientras que latin-1 cubre los primeros 256. Otros que intentan ser más el extremo integral también es un desperdicio, ya que requieren más bytes de los necesarios, incluso para los caracteres "baratos" comunes. UTF-16, por ejemplo, utiliza un mínimo de 2 bytes por carácter, incluidos aquellos en el rango de ascii ('B' que es 65, aún requiere 2 bytes de almacenamiento en UTF-16). UTF-32 es aún más derrochador ya que almacena todos los caracteres en 4 bytes.

UTF-8 parece haber resuelto hábilmente el dilema, con un esquema capaz de almacenar puntos de código con una cantidad variable de espacios de bytes. Como parte de su estrategia de codificación, UTF-8 encadena puntos de código con bits indicadores que indican (presumiblemente a los decodificadores) sus requisitos de espacio y sus límites.

codificación UTF-8 de los puntos de código Unicode en el rango ASCII (0-127):

0xxx xxxx (in binary) 
  • las x muestra el espacio real reservado para "almacenar" el punto de código durante la codificación
  • El 0 inicial es un indicador que indica al decodificador UTF-8 que este punto de código solo requerirá 1 byte.
  • al codificar, UTF-8 no cambia el valor de los puntos de código en ese rango específico (es decir, 65 codificados en UTF-8 también es 65).Teniendo en cuenta que Unicode y ASCII también son compatibles en el mismo rango, incidentalmente hace que UTF-8 y ASCII también sean compatibles en ese rango.

e.g. El punto de código Unicode para 'B' es '0x42' o 0100 0010 en binario (como dijimos, es lo mismo en ASCII). Después de la codificación en UTF-8 se convierte en:

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) 

codificación UTF-8 de los puntos de código Unicode encima de 127 (no ASCII):

110x xxxx 10xx xxxx   <-- (from 128 to 2047) 
1110 xxxx 10xx xxxx 10xx xxxx <-- (from 2048 to 65535) 
  • los bits iniciales '110' indica al decodificador UTF-8 el comienzo de un punto de código codificado en 2 bytes, mientras que "1110" indica 3 bytes, 11110 indicaría 4 bytes y así sucesivamente.
  • los bits de bandera interiores '10' se utilizan para señalar el comienzo de un byte interno.
  • otra vez, las x marcan el espacio donde se almacena el valor del punto de código Unicode después de la codificación.

e.g. 'é' El punto de código Unicode es 0xe9 (233).

1110 1001 <-- 0xe9 

Cuando UTF-8 codifica este valor, se determina que el valor es mayor que 127 y menor que 2,048, por lo tanto debe ser codificado en 2 bytes:

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 

los puntos de código 0xE9 Unicode después de La codificación UTF-8 se convierte en 0xc3a9. Que es exactamente cómo la recibe el terminal. Si tu terminal está configurada para decodificar cadenas usando latin-1 (una de las codificaciones heredadas no unicode), verás à ©, porque sucede que 0xc3 en latin-1 apunta a à y 0xa9 a ©.

+5

Excelente explicación. Ahora entiendo UTF-8! –

+2

Finalmente, podría irme a dormir esta noche ... – Alan

+0

De acuerdo, leí toda tu publicación en unos 10 segundos. Decía: "Python apesta cuando se trata de codificación". – Andrew

8

El REPL Python trata de recoger lo que la codificación a usar de su entorno. Si encuentra algo sano, entonces todo solo funciona. Es cuando no puede entender lo que está pasando que se produce un error.

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

solo por curiosidad, ¿cómo cambiaría sys.stdout.encoding a ascii? –

+1

No lo harías. Utilizarías 'codecs.EncodedFile()' para envolverlo. –

+2

@TankorSmash Obtengo 'TypeError: readonly attribute' en 2.7.2 – Kos

4

Usted tienen especifica una codificación mediante la introducción de una cadena Unicode explícita. Compare los resultados de no usar el prefijo u.

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

>>> 

En el caso de \xe9 entonces Python asume su codificación por defecto (ASCII), por lo que la impresión en blanco ... algo.

+1

así que si lo entiendo bien, cuando imprima cadenas unicode (los puntos de código), python asume que quiero una salida codificada en utf-8, en su lugar de solo tratar de darme lo que * podría * haber sido en ascii? –

+1

@mike: AFAIK lo que dijiste es correcto. Si * no * imprimiera los caracteres Unicode pero codificados como ASCII, todo saldría distorsionado y probablemente todos los principiantes se preguntarían, "¿Cómo es que no puedo imprimir texto Unicode?" –

+2

Gracias. En realidad, soy uno de esos principiantes, pero viniendo del lado de personas que sí entienden el unicode, por lo que este comportamiento me desanima un poco. –

24

Cuando los caracteres Unicode se imprimen en stdout, se usa sys.stdout.encoding. Se supone que un carácter no Unicode está en sys.stdout.encoding y se acaba de enviar al terminal. En mi sistema:

>>> import unicodedata as ud 
>>> import sys 
>>> sys.stdout.encoding 
'cp437' 
>>> ud.name(u'\xe9') 
'LATIN SMALL LETTER E WITH ACUTE' 
>>> ud.name('\xe9'.decode('cp437')) 
'GREEK CAPITAL LETTER THETA' 
>>> import unicodedata as ud 
>>> ud.name(u'\xe9') 
'LATIN SMALL LETTER E WITH ACUTE' 
>>> '\xe9'.decode('cp437') 
u'\u0398' 
>>> ud.name(u'\u0398') 
'GREEK CAPITAL LETTER THETA' 
>>> print u'\xe9' 
é 
>>> print '\xe9' 
Θ 

sys.getdefaultencoding() sólo se utiliza cuando Python no tiene otra opción.

-1

funciona para mí:

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

Cheap hack sucio que inevitablemente romperá algo más. ¡No es difícil hacerlo de la manera correcta! –

Cuestiones relacionadas