Unicode (UTF-8) lectura y escritura en archivos en Python
Tengo cierta insuficiencia cerebral para comprender la lectura y escritura de texto en un archivo (Python 2.4).
# The string, which has an a-acute in it.
ss = u'Capit\xe1n'
ss8 = ss.encode('utf8')
repr(ss), repr(ss8)
("u'Capit\xe1n'", "'Capit\xc3\xa1n'")
print ss, ss8
print >> open('f1','w'), ss8
>>> file('f1').read()
'Capit\xc3\xa1n\n'
Entonces escribo Capit\xc3\xa1n
en mi editor favorito, en el archivo f2.
Entonces:
>>> open('f1').read()
'Capit\xc3\xa1n\n'
>>> open('f2').read()
'Capit\\xc3\\xa1n\n'
>>> open('f1').read().decode('utf8')
u'Capit\xe1n\n'
>>> open('f2').read().decode('utf8')
u'Capit\\xc3\\xa1n\n'
¿Qué no estoy entendiendo aquí? Es evidente que me falta algo vital de magia (o buen sentido). ¿Qué se escribe en archivos de texto para obtener conversiones adecuadas?
Lo que realmente no entiendo aquí es cuál es el objetivo de la representación UTF-8, si realmente no puedes lograr que Python la reconozca, cuando viene desde afuera. ¡Quizás debería simplemente volcar JSON la cadena y usarla en su lugar, ya que tiene una representación asciiable! Más concretamente, ¿existe una representación ASCII de este objeto Unicode que Python reconocerá y decodificará cuando provenga de un archivo? Si es así, ¿cómo lo consigo?
>>> print simplejson.dumps(ss)
'"Capit\u00e1n"'
>>> print >> file('f3','w'), simplejson.dumps(ss)
>>> simplejson.load(open('f3'))
u'Capit\xe1n'
En lugar de meterse con .encode
y .decode
, especifique la codificación al abrir el archivo. El io
módulo , agregado en Python 2.6, proporciona una io.open
función que permite especificar el archivo encoding
.
Suponiendo que el archivo esté codificado en UTF-8, podemos usar:
>>> import io
>>> f = io.open("test", mode="r", encoding="utf-8")
Luego f.read
devuelve un objeto Unicode decodificado:
>>> f.read()
u'Capit\xe1l\n\n'
En 3.x, la io.open
función es un alias para la función incorporada open
, que admite el encoding
argumento (no es así en 2.x).
También podemos usar open
desde el codecs
módulo de biblioteca estándar :
>>> import codecs
>>> f = codecs.open("test", "r", "utf-8")
>>> f.read()
u'Capit\xe1l\n\n'
Sin embargo, tenga en cuenta que esto puede causar problemas al mezclar read()
yreadline()
.
En la notación u'Capit\xe1n\n'
(debe estar solo 'Capit\xe1n\n'
en 3.x y debe estar en 3.0 y 3.1), \xe1
representa solo un carácter. \x
es una secuencia de escape, lo que indica que e1
está en hexadecimal.
Escribir Capit\xc3\xa1n
en el archivo en un editor de texto significa que en realidad contiene \xc3\xa1
. Son 8 bytes y el código los lee todos. Podemos ver esto mostrando el resultado:
# Python 3.x - reading the file as bytes rather than text,
# to ensure we see the raw data
>>> open('f2', 'rb').read()
b'Capit\\xc3\\xa1n\n'
# Python 2.x
>>> open('f2').read()
'Capit\\xc3\\xa1n\n'
En su lugar, simplemente ingrese caracteres como á
en el editor, que luego debería manejar la conversión a UTF-8 y guardarla.
En 2.x, una cadena que realmente contiene estas secuencias de escape de barra invertida se puede decodificar usando el string_escape
códec:
# Python 2.x
>>> print 'Capit\\xc3\\xa1n\n'.decode('string_escape')
Capitán
El resultado es un str
código que está codificado en UTF-8 donde el carácter acentuado está representado por los dos bytes que se escribieron \\xc3\\xa1
en la cadena original. Para obtener un unicode
resultado, decodifique nuevamente con UTF-8.
En 3.x, el string_escape
códec se reemplaza por unicode_escape
y se aplica estrictamente que solo podemos encode
ir de a str
a bytes
y decode
de bytes
a str
. unicode_escape
necesita comenzar con a bytes
para procesar las secuencias de escape (al revés, las agrega ); y luego tratará el resultado \xc3
y \xa1
como escapes de caracteres en lugar de escapes de bytes . Como resultado, tenemos que trabajar un poco más:
# Python 3.x
>>> 'Capit\\xc3\\xa1n\n'.encode('ascii').decode('unicode_escape').encode('latin-1').decode('utf-8')
'Capitán\n'
Ahora todo lo que necesitas en Python3 esopen(Filename, 'r', encoding='utf-8')
[Editar el 10 de febrero de 2016 para solicitar aclaración]
Python3 agregó el parámetro de codificación a su función abierta. La siguiente información sobre la función abierta se recopila desde aquí: https://docs.python.org/3/library/functions.html#open
open(file, mode='r', buffering=-1,
encoding=None, errors=None, newline=None,
closefd=True, opener=None)
Codificación es el nombre de la codificación utilizada para decodificar o codificar el archivo. Esto sólo debe usarse en modo texto. La codificación predeterminada depende de la plataforma (lo que devuelva locale.getpreferredencoding() ), pero se puede utilizar cualquier codificación de texto compatible con Python. Consulte el módulo de códecs para obtener la lista de codificaciones admitidas.
Entonces, al agregar encoding='utf-8'
como parámetro a la función de apertura, la lectura y escritura del archivo se realiza como utf8 (que ahora también es la codificación predeterminada de todo lo que se hace en Python).
Esto funciona para leer un archivo con codificación UTF-8 en Python 3.2:
import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
print(line)
Entonces, encontré una solución para lo que estoy buscando, que es:
print open('f2').read().decode('string-escape').decode("utf-8")
Hay algunos códecs inusuales que resultan útiles aquí. Esta lectura particular permite tomar representaciones UTF-8 desde Python, copiarlas en un archivo ASCII y leerlas en Unicode. Bajo la decodificación "string-escape", las barras no se duplicarán.
Esto permite el tipo de viaje de ida y vuelta que estaba imaginando.