python - open - UnicodeDecodeError al redireccionar al archivo
python unicode utf-8 (3)
Ejecuto este fragmento dos veces, en el terminal de Ubuntu (conjunto de codificación a 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 recibo 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 ocurre detrás de la cortina en ambos casos?
Codifíquelo mientras imprime
uni = u"/u001A/u0BC3/u1451/U0001D10C"
print uni.encode("utf-8")
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 por sí mismo, por lo que debe codificar manualmente al hacer E / S.
La clave de tales problemas de codificación es comprender que, en principio, hay dos conceptos distintos de "cadena" : (1) cadena de caracteres y (2) cadena / matriz de bytes . Esta distinción ha sido ignorada durante mucho tiempo debido a la ubicuidad histórica de las codificaciones con no más de 256 caracteres (ASCII, Latin-1, Windows-1252, Mac OS Roman, ...): estas codificaciones mapean un conjunto de caracteres comunes para números entre 0 y 255 (es decir, bytes); el intercambio relativamente limitado de archivos antes del advenimiento 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 existían codificaciones múltiples 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 vista correcta y moderna separa correctamente estos dos conceptos de cadena, en función de los dos puntos siguientes:
Los personajes en general 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, devolución de carro, instrucciones para establecer la dirección de escritura (para árabe, etc.), acentos, etc. Se incluye una lista de caracteres muy grande en el estándar Unicode ; 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 fragmentos 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 personajes. Cualquier texto presente en su computadora está codificado (hasta que se muestra), ya sea que se envíe a un terminal (que espera caracteres codificados de una manera específica) o guardado en un archivo. Para poder mostrarse o "entenderse" adecuadamente (por ejemplo, el intérprete de Python), las secuencias de bytes se decodifican en caracteres. Unicode define algunas codificaciones (UTF-8, UTF-16, ...) para su lista de caracteres (Unicode define así una lista de caracteres y codificaciones para estos caracteres; todavía hay lugares donde se ve la expresión "codificación Unicode" "como una 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:
Codificación : caracteres → bytes
Decodificación : bytes → caracteres
Algunas codificaciones no pueden codificar todos los caracteres (p. Ej., ASCII), mientras que (algunas) codificaciones Unicode te permiten codificar todos los caracteres Unicode. La codificación tampoco es necesariamente única , porque algunos caracteres pueden representarse 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 representarse mediante diferentes caracteres (de control) que dependen del sistema operativo (esta es la razón del modo de lectura de archivo de nueva línea universal de Python).
Ahora, lo que he llamado "personaje" arriba es lo que Unicode llama un " personaje percibido por el usuario ". Un único carácter percibido por el usuario a veces se puede representar en Unicode combinando partes de caracteres (carácter base, acentos, ...) encontradas en diferentes índices en la lista Unicode, que se llaman " puntos de código ": estos puntos de códigos se pueden combinar para formar un "grupo de grafemas". Así, Unicode conduce a un tercer concepto de cadena, compuesto por una secuencia de puntos de código Unicode, que se ubica entre el byte y las cadenas de caracteres, y que está más cerca de este último. Los llamaré " cadenas Unicode " (como en Python 2).
Si bien Python puede imprimir cadenas de caracteres (percibidos por el usuario), las cadenas de no bytes de Python son esencialmente secuencias de puntos de código Unicode , no de caracteres percibidos por el usuario. Los valores del punto de código son los que se utilizan en la sintaxis de la cadena Unicode /u
/U
Python. No deben confundirse con la codificación de un personaje (y no deben tener ninguna relación con él: los puntos de código Unicode se pueden codificar de varias maneras).
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))
s = "/u1100/u1161/u11a8"; print(s, "len", len(s))
(Python 3) da 각 len 3
pesar de s
tener un solo carácter (coreano) percibido por el usuario (porque está representado con 3 puntos de código, incluso si no tiene que , como muestra print("/uac01")
). Sin embargo, en muchas circunstancias prácticas, la longitud de una cadena es la cantidad de caracteres percibidos por el usuario, ya que Python suele almacenar muchos caracteres como un único punto de código Unicode.
En Python 2 , las cadenas Unicode se llaman ... "cadenas Unicode" (tipo unicode
, forma literal u"…"
), mientras que las matrices Byte son "cadenas" (tipo str
, donde la matriz de bytes puede construirse, por ejemplo, con cadenas literales "…"
). En Python 3 , las cadenas Unicode simplemente se llaman "cadenas" (tipo str
, forma literal "…"
), mientras que las matrices byte son "bytes" (tipo de bytes
, forma literal b"…"
).
Con estos pocos puntos clave, ¡debería ser capaz de entender la mayoría de las preguntas relacionadas con la codificación!
Normalmente, cuando imprime u"…"
en un terminal , no debe obtener basura: Python conoce la codificación de su terminal. De hecho, puede verificar qué codificación espera el terminal:
% python
Python 2.7.6 (default, Nov 15 2013, 15:20:37)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.stdout.encoding
UTF-8
Si los caracteres de entrada se pueden codificar con la codificación del terminal, Python lo hará y enviará los bytes correspondientes a su terminal sin quejarse. El terminal hará su mejor esfuerzo para mostrar los caracteres después de decodificar los bytes de entrada (en el peor, la fuente del terminal no tiene algunos de los caracteres e imprimirá algún tipo de espacio en blanco en su lugar).
Si sus caracteres de entrada no pueden codificarse 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
ya que la cadena de caracteres no se puede codificar de manera que se adapte a su terminal). La única solución posible es usar un terminal que pueda mostrar los caracteres (ya sea configurando el terminal para que acepte una codificación que pueda representar a sus personajes, o mediante el uso de un programa de terminal diferente). Esto es importante cuando distribuye programas que se pueden usar en diferentes entornos: los mensajes que imprima deben ser representables en el terminal del usuario. En ocasiones, es mejor seguir con cadenas que solo contengan 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 una codificación predeterminada: Ninguno (Python 2.7) o UTF-8 ( Python 3):
% python2.7 -c "import sys; print sys.stdout.encoding" | cat
None
% python3.4 -c "import sys; print(sys.stdout.encoding)" | cat
UTF-8
Sin embargo, la codificación de stdin, stdout y stderr se puede set través de la variable de entorno PYTHONIOENCODING
, si es necesario:
% PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat
UTF-8
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 caracter ( /u001A
) no es imprimible, si no me equivoco .
En http://wiki.python.org/moin/PrintFails , puede encontrar una solución como la siguiente para Python 2.x:
import codecs
import locale
import sys
# Wrap sys.stdout into a StreamWriter to allow writing unicode.
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)
uni = u"/u001A/u0BC3/u1451/U0001D10C"
print uni
Para Python 3, puede verificar una de las preguntas formuladas anteriormente en .
Python siempre codifica cadenas Unicode cuando escribe en un terminal, archivo, tubería, etc. Al escribir en un terminal, Python generalmente puede determinar la codificación del terminal y usarlo correctamente. Al escribir en un archivo o conducto, Python adopta de manera predeterminada la codificación ''ascii'' a menos que se indique explícitamente lo contrario. A Python se le puede decir qué hacer cuando la salida de la tubería PYTHONIOENCODING
la variable de entorno PYTHONIOENCODING
. 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 admite 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 comentario #coding
indica la codificación en la que se guarda el archivo fuente . Elegí utf8 para poder admitir caracteres en la fuente que mi terminal no pudo. Codificación redirigida a stderr para que se pueda ver cuando se redirige a un archivo.
#coding: utf8
import sys
uni = u''αßΓπΣσµτΦΘΩδ∞φ''
print >>sys.stderr,sys.stdout.encoding
print uni
Salida (se ejecuta directamente desde la terminal)
cp437
αßΓπΣσµτΦΘΩδ∞φ
Python determinó correctamente la codificación de la terminal.
Salida (redirigido a un archivo)
None
Traceback (most recent call last):
File "C:/ex.py", line 5, in <module>
print uni
UnicodeEncodeError: ''ascii'' codec can''t encode characters in position 0-13: ordinal not in range(128)
Python no pudo determinar la codificación (Ninguno) por lo que se usa ''ascii'' por defecto. ASCII solo admite la conversión de los primeros 128 caracteres de Unicode.
Salida (redirigido a un archivo, PYTHONIOENCODING = cp437)
cp437
y mi archivo de salida fue correcto:
C:/>type out.txt
αßΓπΣσµτΦΘΩδ∞φ
Ejemplo 2
Ahora incluiré un carácter en la fuente que no es compatible con mi terminal:
#coding: utf8
import sys
uni = u''αßΓπΣσµτΦΘΩδ∞φ马'' # added Chinese character at end.
print >>sys.stderr,sys.stdout.encoding
print uni
Salida (se ejecuta directamente desde la terminal)
cp437
Traceback (most recent call last):
File "C:/ex.py", line 5, in <module>
print uni
File "C:/Python26/lib/encodings/cp437.py", line 12, in encode
return codecs.charmap_encode(input,errors,encoding_map)
UnicodeEncodeError: ''charmap'' codec can''t encode character u''/u9a6c'' in position 14: character maps to <undefined>
Mi terminal no entendió ese último carácter chino.
Salida (ejecutar directamente, PYTHONIOENCODING = 437: reemplazar)
cp437
αßΓπΣσµτΦΘΩδ∞φ?
Los manejadores de errores se pueden especificar con la codificación. En este caso, los caracteres desconocidos fueron reemplazados por ?
. ignore
y xmlcharrefreplace
son algunas otras opciones. Al usar 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 ser compatible.