Gracias a fragmentos de varias respuestas, creo que podemos unir una explicación.
Al intentar imprimir una cadena Unicode, u '\ xe9', Python intenta implícitamente codificar esa cadena usando el esquema de codificación actualmente almacenado en sys.stdout.encoding. Python en realidad recoge esta configuración del entorno desde el que se inició. Si no puede encontrar una codificación adecuada del entorno, solo entonces vuelve a su valor predeterminado , ASCII.
Por ejemplo, yo uso un shell bash cuya codificación por defecto es UTF-8. Si inicio Python desde allí, recoge y usa esa configuración:
$ python
>>> import sys
>>> print sys.stdout.encoding
UTF-8
Salgamos por un momento del shell de Python y configuremos el entorno de bash con alguna codificación falsa:
$ export LC_CTYPE=klingon
# we should get some error message here, just ignore it.
Luego, vuelva a iniciar el shell de Python y verifique que efectivamente vuelva a su codificación ASCII predeterminada.
$ python
>>> import sys
>>> print sys.stdout.encoding
ANSI_X3.4-1968
¡Bingo!
Si ahora intenta generar algún carácter unicode fuera de ASCII, debería recibir un buen mensaje de error
>>> print u'\xe9'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9'
in position 0: ordinal not in range(128)
Salgamos de Python y descartemos el bash shell.
Ahora observaremos lo que sucede después de que Python genera 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 aka latin-1 (los terminales gráficos generalmente tienen una opción para establecer el carácter Codificación en uno de sus menús desplegables). Tenga en cuenta que esto no cambia la codificación del entorno de shell real , solo cambia la forma en que el terminal descodificará la salida que se le da, un poco como lo hace un navegador web. Por lo tanto, puede cambiar la codificación del terminal, independientemente del entorno del shell. Entonces, comencemos Python desde el shell y verifiquemos que sys.stdout.encoding esté configurado para la codificación del entorno del 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) python genera una cadena binaria tal cual, la terminal la recibe e intenta hacer coincidir su valor con el mapa de caracteres latin-1. En latin-1, 0xe9 o 233 produce el carácter "é" y eso es lo que muestra el terminal.
(2) python intenta codificar implícitamente la cadena Unicode con cualquier esquema actualmente configurado 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 y, por lo tanto, solo decodifica flujos de 1 byte a la vez. 0xc3a9 tiene 2 bytes de longitud, por lo tanto, 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 puntos del código latin-1 es 0-255 y apunta exactamente al mismo carácter 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 codifican en latin-1. Entonces u '\ xe9' (233) codificado en latin-1 también producirá la cadena binaria '\ xe9'. Terminal recibe ese valor e intenta hacer coincidirlo en el mapa de caracteres latin-1. Al igual que el caso (1), produce "é" y eso es lo que se muestra.
Cambiemos ahora la configuración de codificación del terminal a UTF-8 desde el menú desplegable (como cambiaría 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. Intentemos imprimir de nuevo:
>>> print '\xe9' # (4)
>>> print u'\xe9' # (5)
é
>>> print u'\xe9'.encode('latin-1') # (6)
>>>
(4) python genera una cadena binaria tal cual. El terminal intenta decodificar ese flujo con UTF-8. Pero UTF-8 no entiende el valor 0xe9 (ver explicación posterior) y, por lo tanto, no puede convertirlo en un punto de código Unicode. No se encontró ningún punto de código, no se imprimió ningún carácter.
(5) python intenta codificar implícitamente la cadena Unicode con lo que esté en sys.stdout.encoding. Todavía "UTF-8". La cadena binaria resultante es '\ xc3 \ xa9'. El terminal recibe el flujo e intenta decodificar 0xc3a9 también usando UTF-8. Devuelve el valor del código 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'. Nuevamente, para el terminal esto es más o menos lo mismo que el 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 utilizando 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 latin-1:
Unicode es básicamente una tabla de caracteres donde algunas teclas (puntos de código) se han asignado convencionalmente para señalar algunos símbolos. por ejemplo, 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 puntos a 'Ü' en latin-1 y Unicode, 0xe9 apunta a 'é' en latin-1 y Unicode.
Cuando se trabaja con dispositivos electrónicos, los puntos de código Unicode necesitan una forma eficiente de representarse 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 1 a 1 sería poco práctica, ya que requeriría que todos los puntos de código se almacenen 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 requisito de espacio, los más económicos no cubren todos los puntos de código Unicode, por ejemplo, ascii solo cubre los primeros 128, mientras que latin-1 cubre los primeros 256. Otros que intentan ser más completos también terminan siendo 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 ASCII ('B', que es 65, todavía 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 resulta haber resuelto inteligentemente 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 ata puntos de código con bits de bandera que indican (presumiblemente a los decodificadores) sus requisitos de espacio y sus límites.
Codificación UTF-8 de puntos de código Unicode en el rango ASCII (0-127):
0xxx xxxx (in binary)
- las x muestran 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.
Por ejemplo, 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 codificar 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 puntos de código Unicode por 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' indican 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 '10' internos se utilizan para señalar el comienzo de un byte interno.
- nuevamente, las x marcan el espacio donde se almacena el valor del punto de código Unicode después de la codificación.
por ejemplo, el punto de código 'é' Unicode es 0xe9 (233).
1110 1001 <-- 0xe9
Cuando UTF-8 codifica este valor, determina que el valor es mayor que 127 y menor que 2048, por lo tanto, debe codificarse 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 convierten en 0xc3a9. Que es exactamente como lo recibe el terminal. Si su terminal está configurado para decodificar cadenas usando latin-1 (una de las codificaciones heredadas no unicode), verá à ©, porque sucede que 0xc3 en latin-1 apunta a à y 0xa9 a ©.