Ejecuto este fragmento dos veces, en la terminal de Ubuntu (codificación establecida en utf-8), una vez con ./test.py
y luego con ./test.py >out.txt
:
uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni
Sin redirección imprime basura. Con la redirección obtengo un UnicodeDecodeError. ¿Alguien puede explicar por qué obtengo el error solo en el segundo caso, o mejor aún, dar una explicación detallada de lo que sucede detrás de la cortina en ambos casos?
Respuestas:
La clave de estos problemas de codificación es comprender que, en principio, existen dos conceptos distintos de "cadena" : (1) cadena de caracteres y (2) cadena / matriz de bytes. Esta distinción ha sido mayoritariamente ignorada durante mucho tiempo debido a la ubicuidad histórica de codificaciones con no más de 256 caracteres (ASCII, Latin-1, Windows-1252, Mac OS Roman,…): estas codificaciones mapean un conjunto de caracteres comunes a números entre 0 y 255 (es decir, bytes); el intercambio relativamente limitado de archivos antes de la llegada de la web hizo tolerable esta situación de codificaciones incompatibles, ya que la mayoría de los programas podían ignorar el hecho de que había múltiples codificaciones siempre que produjeran texto que permaneciera en el mismo sistema operativo: tales programas simplemente tratar el texto como bytes (a través de la codificación utilizada por el sistema operativo). La visión moderna y correcta separa adecuadamente estos dos conceptos de cuerdas, basándose en los dos puntos siguientes:
La mayoría de los personajes no están relacionados con las computadoras : uno puede dibujarlos en una pizarra, etc., como por ejemplo بايثون, 中 蟒 y 🐍. Los "caracteres" para máquinas también incluyen "instrucciones de dibujo" como, por ejemplo, espacios, retorno de carro, instrucciones para establecer la dirección de escritura (para árabe, etc.), acentos, etc. El estándar Unicode incluye una lista de caracteres muy grande ; cubre la mayoría de los personajes conocidos.
Por otro lado, las computadoras necesitan representar caracteres abstractos de alguna manera: para esto, usan matrices de bytes (números entre 0 y 255 incluidos), porque su memoria viene en bloques de bytes. El proceso necesario que convierte caracteres en bytes se llama codificación . Por lo tanto, una computadora requiere una codificación para representar caracteres. Cualquier texto presente en su computadora está codificado (hasta que se muestra), ya sea que se envíe a una terminal (que espera caracteres codificados de una manera específica) o se guarde en un archivo. Para que se muestren o "comprendan" correctamente (por ejemplo, el intérprete de Python), los flujos de bytes se decodifican en caracteres. Algunas codificaciones(UTF-8, UTF-16,…) están definidos por Unicode para su lista de caracteres (Unicode define tanto una lista de caracteres como codificaciones para estos caracteres; todavía hay lugares donde uno ve la expresión "codificación Unicode" como un forma de referirse al ubicuo UTF-8, pero esta es una terminología incorrecta, ya que Unicode proporciona múltiples codificaciones).
En resumen, las computadoras necesitan representar internamente caracteres con bytes , y lo hacen a través de dos operaciones:
Algunas codificaciones no pueden codificar todos los caracteres (por ejemplo, ASCII), mientras que (algunas) codificaciones Unicode le permiten codificar todos los caracteres Unicode. La codificación tampoco es necesariamente única , porque algunos caracteres se pueden representar directamente o como una combinación (por ejemplo, de un carácter base y de acentos).
Tenga en cuenta que el concepto de nueva línea agrega una capa de complicación , ya que puede estar representado por diferentes caracteres (de control) que dependen del sistema operativo (esta es la razón del modo de lectura de archivos de nueva línea universal de Python ).
Ahora, lo que he llamado "carácter" anteriormente es lo que Unicode llama un " carácter percibido por el usuario ". Un solo carácter percibido por el usuario a veces se puede representar en Unicode combinando partes de caracteres (carácter base, acentos, ...) que se encuentran en diferentes índices de la lista Unicode, que se denominan " puntos de código "; estos puntos de código se pueden combinar para formar un "grupo de grafemas". Unicode conduce así a un tercer concepto de cadena, hecho de una secuencia de puntos de código Unicode, que se encuentra entre cadenas de bytes y caracteres, y que está más cerca de esta última. Los llamaré " cadenas Unicode " (como en Python 2).
Si bien Python puede imprimir cadenas de caracteres (percibidos por el usuario), las cadenas sin bytes de Python son esencialmente secuencias de puntos de código Unicode , no de caracteres percibidos por el usuario. Los valores de los puntos de código son los que se utilizan en la sintaxis de cadenas de Python
\u
y\U
Unicode. No deben confundirse con la codificación de un carácter (y no tienen que tener ninguna relación con él: los puntos de código Unicode se pueden codificar de varias formas).Esto tiene una consecuencia importante: la longitud de una cadena de Python (Unicode) es su número de puntos de código, que no siempre es su número de caracteres percibidos por el usuario : así
s = "\u1100\u1161\u11a8"; print(s, "len", len(s))
(Python 3) da a각 len 3
pesar des
tener un solo usuario percibido (coreano) carácter (porque está representado con 3 puntos de código, incluso si no es necesario, como seprint("\uac01")
muestra). Sin embargo, en muchas circunstancias prácticas, la longitud de una cadena es el número de caracteres percibidos por el usuario, porque Python normalmente almacena muchos caracteres como un único punto de código Unicode.En Python 2 , las cadenas Unicode se denominan ... "cadenas Unicode" (
unicode
tipo, forma literalu"…"
), mientras que las matrices de bytes son "cadenas" (str
tipo, donde la matriz de bytes se puede construir, por ejemplo, con cadenas literales"…"
). En Python 3 , las cadenas Unicode se denominan simplemente "cadenas" (str
tipo, forma literal"…"
), mientras que las matrices de bytes son "bytes" (bytes
tipo, forma literalb"…"
). Como consecuencia, algo como"🐍"[0]
da un resultado diferente en Python 2 ('\xf0'
, un byte) y Python 3 ("🐍"
, el primer y único carácter).Con estos pocos puntos clave, ¡debería poder comprender la mayoría de las preguntas relacionadas con la codificación!
Normalmente, cuando imprime
u"…"
en una terminal , no debería recibir basura: Python conoce la codificación de su terminal. De hecho, puede verificar qué codificación espera el terminal:Si sus caracteres de entrada se pueden codificar con la codificación de la terminal, Python lo hará y enviará los bytes correspondientes a su terminal sin quejarse. El terminal hará todo lo posible para mostrar los caracteres después de decodificar los bytes de entrada (en el peor de los casos, la fuente del terminal no tiene algunos de los caracteres y en su lugar imprimirá algún tipo de espacio en blanco).
Si sus caracteres de entrada no se pueden codificar con la codificación del terminal, significa que el terminal no está configurado para mostrar estos caracteres. Python se quejará (en Python con un
UnicodeEncodeError
dado que la cadena de caracteres no se puede codificar de una manera que se adapte a su terminal). La única solución posible es usar una terminal que pueda mostrar los caracteres (ya sea configurando la terminal para que acepte una codificación que pueda representar sus caracteres, o usando un programa de terminal diferente). Esto es importante cuando distribuye programas que se pueden utilizar en diferentes entornos: los mensajes que imprima deben poder representarse en el terminal del usuario. A veces, por lo tanto, es mejor ceñirse a cadenas que solo contienen caracteres ASCII.Sin embargo, cuando redirige o canaliza la salida de su programa, generalmente no es posible saber cuál es la codificación de entrada del programa receptor, y el código anterior devuelve alguna codificación predeterminada: Ninguna (Python 2.7) o UTF-8 ( Python 3):
Sin embargo, la codificación de stdin, stdout y stderr se puede configurar a través de la
PYTHONIOENCODING
variable de entorno, si es necesario:Si la impresión en un terminal no produce lo que espera, puede verificar que la codificación UTF-8 que ingresó manualmente sea correcta; por ejemplo, su primer carácter (
\u001A
) no se puede imprimir, si no me equivoco .En http://wiki.python.org/moin/PrintFails , puede encontrar una solución como la siguiente, para Python 2.x:
Para Python 3, puede consultar una de las preguntas que se hicieron anteriormente en StackOverflow.
fuente
Python siempre codifica cadenas Unicode cuando escribe en una terminal, archivo, canalización, etc. Cuando escribe en una terminal, Python generalmente puede determinar la codificación de la terminal y usarla correctamente. Al escribir en un archivo o canalización, Python utiliza de forma predeterminada la codificación 'ascii' a menos que se indique explícitamente lo contrario. A Python se le puede decir qué hacer cuando canaliza la salida a través de la
PYTHONIOENCODING
variable de entorno. Un shell puede establecer esta variable antes de redirigir la salida de Python a un archivo o canalización para que se conozca la codificación correcta.En su caso, ha impreso 4 caracteres poco comunes que su terminal no admitía en su fuente. Aquí hay algunos ejemplos para ayudar a explicar el comportamiento, con caracteres que realmente son compatibles con mi terminal (que usa cp437, no UTF-8).
Ejemplo 1
Tenga en cuenta que el
#coding
comentario indica la codificación en la que se guarda el archivo de origen . Elegí utf8 para poder admitir caracteres en la fuente que mi terminal no podía. Codificación redirigida a stderr para que se pueda ver cuando se redirige a un archivo.Salida (ejecutar directamente desde el terminal)
Python determinó correctamente la codificación de la terminal.
Salida (redirigida a archivo)
Python no pudo determinar la codificación (Ninguna), por lo que usó 'ascii' por defecto. ASCII solo admite la conversión de los primeros 128 caracteres de Unicode.
Salida (redirigido a archivo, PYTHONIOENCODING = cp437)
y mi archivo de salida era correcto:
Ejemplo 2
Ahora incluiré un carácter en la fuente que no es compatible con mi terminal:
Salida (ejecutar directamente desde el terminal)
Mi terminal no entendió ese último carácter chino.
Salida (ejecutar directamente, PYTHONIOENCODING = 437: reemplazar)
Los controladores de errores se pueden especificar con la codificación. En este caso, los caracteres desconocidos fueron reemplazados por
?
.ignore
yxmlcharrefreplace
hay algunas otras opciones. Cuando se usa UTF8 (que admite la codificación de todos los caracteres Unicode), nunca se realizarán reemplazos, pero la fuente utilizada para mostrar los caracteres aún debe admitirlos.fuente
PYTHONIOENCODING
. Hacerprint string.encode("UTF-8")
lo sugerido por @Ismail funcionó para mí.chcp
página de códigos no los admite. Para evitarloUnicodeEncodeError: 'charmap'
, puede instalar elwin-unicode-console
paquete.PYTHONIOENCODING=utf-8
resuelve el problema.Codifíquelo mientras imprime
Esto se debe a que cuando ejecuta el script manualmente, Python lo codifica antes de enviarlo a la terminal, cuando lo canaliza, Python no lo codifica en sí mismo, por lo que debe codificarlo manualmente al hacer E / S.
fuente
win-unicode-console
(Windows), o acepte un parámetro de línea de comandos (si debe hacerlo).