python unicode encoding ascii python-2.x

¿Por qué Python imprime caracteres Unicode cuando la codificación predeterminada es ASCII?



encoding python-2.x (6)

Desde el shell de Python 2.6:

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

Esperaba tener algún galimatías o un error después de la declaración de impresión, ya que el carácter "é" 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

Moví la edición a la sección Respuestas y la acepté como se sugirió.


Cuando los caracteres Unicode se imprimen en stdout, se usa sys.stdout.encoding . Se supone que un carácter que no es Unicode está en sys.stdout.encoding y simplemente se envía al terminal. En mi sistema (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() solo se usa cuando Python no tiene otra opción.

Tenga en cuenta que Python 3.6 o posterior ignora las codificaciones en Windows y utiliza las API Unicode para escribir Unicode en la terminal. No se muestran advertencias UnicodeEncodeError y el carácter correcto si la fuente lo admite. Incluso si la fuente no lo admite, los caracteres pueden cortarse y pegarse desde la terminal a una aplicación con una fuente de apoyo y será correcta. ¡Mejorar!


Esto funciona para mi:

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


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

Al tratar de imprimir una cadena unicode, u ''/ xe9'', Python trata implícitamente de 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 valor predeterminado , ASCII.

Por ejemplo, utilizo un shell bash cuya codificación predeterminada es UTF-8. Si comienzo Python desde allí, retoma y usa esa configuración:

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

Vamos a salir por un momento del shell de Python y establecer el entorno de bash con alguna codificación falsa:

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

A continuación, inicie el shell de python nuevamente y verifique que efectivamente revierte a su codificación ascii predeterminada.

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

¡Bingo!

Si ahora intentas generar algún carácter Unicode fuera de ASCI, deberías recibir 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)

Salgamos de Python y descartemos el shell bash.

Ahora observaremos qué ocurre después de que Python produzca 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 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 propio terminal 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. 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 cadena binaria como está, 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 caracter "é" y eso es lo que muestra la terminal.

(2) python intenta codificar implícitamente la cadena Unicode con cualquier esquema establecido 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.

Cambiemos ahora la configuración de codificación de la terminal a UTF-8 desde 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. Intentemos imprimir de nuevo:

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

(4) python genera una cadena binaria como está. 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 codificar implícitamente la cadena Unicode con lo que se encuentre 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 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 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 apunta 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 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 en cuanto a requisitos de espacio, 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 completos también terminan 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 ha 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 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.
  • tras la codificación, 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 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 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" interiores se utilizan para señalar el comienzo de un byte interno.
  • de nuevo, 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

El código 0xe9 puntos 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 ©.


Ha especificado una codificación al ingresar 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 , Python asume tu codificación predeterminada (Ascii), imprimiendo así ... algo en blanco.


Python REPL intenta recoger qué codificación 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


Según las codificaciones y conversiones de cadenas predeterminadas / implícitas de Python :

  • Al print unicode , se encode d con <file>.encoding .
    • cuando la encoding no está establecida, el unicode se convierte implícitamente a str (ya que el códec para eso es sys.getdefaultencoding() , es decir, ascii , cualquier carácter nacional causaría UnicodeEncodeError )
    • para las transmisiones estándar, la encoding se deduce del entorno. Normalmente se configura para tty streams (desde la configuración regional del terminal), pero es probable que no se configure para los pipes
      • por lo que una print u''/xe9'' es probable que tenga éxito cuando la salida es a un terminal, y falla si se redirige. Una solución es encode() la cadena con la codificación deseada antes de print .
  • Al print str , los bytes se envían a la secuencia como está. Qué glifos muestra la terminal dependerá de su configuración regional.