Unicode (UTF-8) leyendo y escribiendo en archivos en Python

330

Tengo algunos problemas cerebrales para entender leer y escribir 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'

Así que escribo en Capit\xc3\xa1nmi editor favorito, en el archivo f2.

Luego:

>>> 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í? Claramente, me falta algo de magia vital (o buen sentido). ¿Qué escribe uno en archivos de texto para obtener conversiones adecuadas?

Lo que realmente no puedo entender aquí es cuál es el punto de la representación UTF-8, si no puedes hacer que Python lo reconozca, cuando viene del exterior. ¡Tal vez debería simplemente JSON volcar la cadena y usarla en su lugar, ya que tiene una representación sorprendente! Más concretamente, ¿hay una representación ASCII de este objeto Unicode que Python reconocerá y decodificará al ingresar desde 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'
Gregg Lind
fuente

Respuestas:

110

En la notación

u'Capit\xe1n\n'

el "\ xe1" representa solo un byte. "\ x" te dice que "e1" está en hexadecimal. Cuando escribes

Capit\xc3\xa1n

en su archivo tiene "\ xc3". Esos son 4 bytes y en tu código los lees todos. Puede ver esto cuando los muestra:

>>> open('f2').read()
'Capit\\xc3\\xa1n\n'

Puede ver que la barra invertida se escapa mediante una barra invertida. Entonces tiene cuatro bytes en su cadena: "\", "x", "c" y "3".

Editar:

Como otros señalaron en sus respuestas, solo debe ingresar los caracteres en el editor y su editor debe manejar la conversión a UTF-8 y guardarlo.

Si realmente tiene una cadena en este formato, puede usar el string_escapecódec para decodificarla en una cadena normal:

In [15]: print 'Capit\\xc3\\xa1n\n'.decode('string_escape')
Capitán

El resultado es una cadena codificada en UTF-8 donde el carácter acentuado está representado por los dos bytes que se escribieron \\xc3\\xa1en la cadena original. Si desea tener una cadena Unicode, debe decodificar nuevamente con UTF-8.

Para su edición: no tiene UTF-8 en su archivo. Para ver realmente cómo se vería:

s = u'Capit\xe1n\n'
sutf8 = s.encode('UTF-8')
open('utf-8.out', 'w').write(sutf8)

Compare el contenido del archivo utf-8.outcon el contenido del archivo que guardó con su editor.


fuente
Entonces, ¿cuál es el punto del formato codificado utf-8 si Python puede leer en archivos que lo usan? En otras palabras, ¿hay alguna representación ascii que Python leerá en \ xc3 como 1 byte?
Gregg Lind el
44
La respuesta a su pregunta "Entonces, ¿cuál es el punto ..." es "Mu". (ya que Python puede leer archivos codificados en UTF-8). Para su segunda pregunta: \ xc3 no es parte del conjunto ASCII. Quizás te refieres a "codificación de 8 bits". Estás confundido acerca de Unicode y las codificaciones; está bien, muchos lo están.
tzot
8
Intente leer esto como manual: joelonsoftware.com/articles/Unicode.html
tzot
nota: u'\xe1'es un punto de código Unicode U+00e1que se puede representar usando 1 o más bytes dependiendo de la codificación de caracteres (es 2 bytes en utf-8). b'\xe1'es un byte (un número 225), la letra que puede representar depende de la codificación de caracteres utilizada para decodificarla, por ejemplo, es б( U+0431) en cp1251, с( U+0441) en cp866, etc.
jfs
11
Es sorprendente la cantidad de codificadores británicos que dicen "solo usa ascii" y luego no se dan cuenta de que el signo £ no lo es. La mayoría no sabe que ascii! = Página de códigos local (es decir, latin1).
Danny Staple
712

En lugar de meterse con los métodos de codificación y decodificación, me resulta más fácil especificar la codificación al abrir el archivo. El iomódulo (agregado en Python 2.6) proporciona unio.open función, que tiene un parámetro de codificación.

Use el método abierto del iomódulo.

>>>import io
>>>f = io.open("test", mode="r", encoding="utf-8")

Luego, después de llamar a la función read () de f, se devuelve un objeto Unicode codificado.

>>>f.read()
u'Capit\xe1l\n\n'

Tenga en cuenta que en Python 3, la io.openfunción es un alias para la openfunción incorporada. La función abierta incorporada solo admite el argumento de codificación en Python 3, no Python 2.

Editar: Anteriormente, esta respuesta recomendaba el módulo de códecs . El módulo de códecs puede causar problemas al mezclar read()yreadline() , por lo tanto, esta respuesta ahora recomienda módulo io .

Use el método abierto del módulo de códecs.

>>>import codecs
>>>f = codecs.open("test", "r", "utf-8")

Luego, después de llamar a la función read () de f, se devuelve un objeto Unicode codificado.

>>>f.read()
u'Capit\xe1l\n\n'

Si conoce la codificación de un archivo, usar el paquete de códecs será mucho menos confuso.

Ver http://docs.python.org/library/codecs.html#codecs.open

Tim Swast
fuente
74
También funciona perfectamente para escribir archivos, en lugar de open(file,'w')hacerlo codecs.open(file,'w','utf-8')resuelto
Matt Connolly
1
Esta es la respuesta que estaba buscando :)
Justin
66
¿El codecs.open(...)método también se ajusta completamente al with open(...):estilo, donde se withpreocupa por cerrar el archivo después de todo? Parece funcionar de todos modos.
try-catch-finally
2
@ try-catch-finally Sí. Yo uso with codecs.open(...) as f:todo el tiempo.
Tim Swast
66
Desearía poder votar esto cien veces. Después de agonizar durante varios días por problemas de codificación causados ​​por una gran cantidad de datos mixtos y leer con los ojos cruzados sobre la codificación, esta respuesta es como el agua en un desierto. Ojalá lo hubiera visto antes.
Mike Girard
45

Ahora todo lo que necesitas en Python3 es open(Filename, 'r', encoding='utf-8')

[Editar el 10/02/2016 para la aclaración solicitada]

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 solo debe usarse en modo texto. La codificación predeterminada depende de la plataforma (lo que devuelve locale.getpreferredencoding () ), pero se puede usar cualquier codificación de texto compatible con Python. Consulte el módulo de códecs para ver la lista de codificaciones compatibles.

Entonces, al agregar encoding='utf-8'como parámetro a la función abierta, 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).

Dakusan
fuente
¿Podría por favor elaborar más su respuesta agregando un poco más de descripción sobre la solución que proporciona?
abarisone
2
Parece que está disponible en python 2 usando el módulo de códecs - codecs.open('somefile', encoding='utf-8') stackoverflow.com/a/147756/149428
Taylor Edmiston
18

Entonces, he encontrado 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 son útiles aquí. Esta lectura particular le permite a uno tomar representaciones UTF-8 desde Python, copiarlas en un archivo ASCII y hacer que se lean 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.

Gregg Lind
fuente
1
Buena respuesta, he probado ambas soluciones (codecs.open(file,"r","utf-8")y, de manera simple, open(file,"r").read().decode("utf-8")ambas funcionaron perfectamente.
Eagle
Recibo un "TypeError: str esperado, bytes u objeto os.PathLike, no _io.TextIOWrapper" ¿alguna idea de por qué?
JinSnow
Creo que, teniendo en cuenta el número de votos a favor, sería una buena idea aceptar la segunda respuesta :)
Jacquot
14
# -*- encoding: utf-8 -*-

# converting a unknown formatting file in utf-8

import codecs
import commands

file_location = "jumper.sub"
file_encoding = commands.getoutput('file -b --mime-encoding %s' % file_location)

file_stream = codecs.open(file_location, 'r', file_encoding)
file_output = codecs.open(file_location+"b", 'w', 'utf-8')

for l in file_stream:
    file_output.write(l)

file_stream.close()
file_output.close()
Ricardo
fuente
14

En realidad, esto funcionó para mí 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)
Sina
fuente
6

Para leer en una cadena Unicode y luego enviar a HTML, hice esto:

fileline.decode("utf-8").encode('ascii', 'xmlcharrefreplace')

Útil para servidores http con tecnología python.

praj
fuente
6

Te has topado con el problema general de las codificaciones: ¿cómo puedo saber en qué codificación está un archivo?

Respuesta: No puede, a menos que el formato de archivo lo permita . XML, por ejemplo, comienza con:

<?xml encoding="utf-8"?>

Este encabezado se eligió cuidadosamente para que pueda leerse sin importar la codificación. En su caso, no existe tal pista, por lo tanto, ni su editor ni Python tienen idea de lo que está sucediendo. Por lo tanto, debe usar el codecsmódulo y usarcodecs.open(path,mode,encoding) que proporciona el bit que falta en Python.

En cuanto a su editor, debe verificar si ofrece alguna forma de configurar la codificación de un archivo.

El objetivo de UTF-8 es poder codificar caracteres de 21 bits (Unicode) como un flujo de datos de 8 bits (porque eso es lo único que todas las computadoras del mundo pueden manejar). Pero como la mayoría de los sistemas operativos son anteriores a la era Unicode, no tienen herramientas adecuadas para adjuntar la información de codificación a los archivos en el disco duro.

El siguiente problema es la representación en Python. Esto se explica perfectamente en el comentario de heikogerlach . Debe comprender que su consola solo puede mostrar ASCII. Para mostrar Unicode o cualquier cosa> = charcode 128, debe usar algún medio de escape. En su editor, no debe escribir la cadena de visualización escapada sino lo que significa la cadena (en este caso, debe ingresar la diéresis y guardar el archivo).

Dicho esto, puede usar la función Python eval () para convertir una cadena escapada en una cadena:

>>> x = eval("'Capit\\xc3\\xa1n\\n'")
>>> x
'Capit\xc3\xa1n\n'
>>> x[5]
'\xc3'
>>> len(x[5])
1

Como puede ver, la cadena "\ xc3" se ha convertido en un solo carácter. Esta es ahora una cadena de 8 bits, codificada en UTF-8. Para obtener Unicode:

>>> x.decode('utf-8')
u'Capit\xe1n\n'

Gregg Lind preguntó: Creo que faltan algunas piezas aquí: el archivo f2 contiene: hex:

0000000: 4361 7069 745c 7863 335c 7861 316e  Capit\xc3\xa1n

codecs.open('f2','rb', 'utf-8'), por ejemplo, los lee en caracteres separados (esperado) ¿Hay alguna forma de escribir en un archivo en ASCII que funcione?

Respuesta: Eso depende de lo que quieras decir. ASCII no puede representar caracteres> 127. Por lo tanto, necesita alguna forma de decir "los siguientes caracteres significan algo especial", que es lo que hace la secuencia "\ x". Dice: Los siguientes dos caracteres son el código de un solo carácter. "\ u" hace lo mismo con cuatro caracteres para codificar Unicode hasta 0xFFFF (65535).

Por lo tanto, no puede escribir directamente Unicode en ASCII (porque ASCII simplemente no contiene los mismos caracteres). Puede escribirlo como escapes de cadena (como en f2); en este caso, el archivo se puede representar como ASCII. O puede escribirlo como UTF-8, en cuyo caso, necesita una transmisión segura de 8 bits.

Su solución usando decode('string-escape')funciona, pero debe saber cuánta memoria usa: tres veces la cantidad de usocodecs.open() .

Recuerde que un archivo es solo una secuencia de bytes con 8 bits. Ni los bits ni los bytes tienen un significado. Eres tú quien dice "65 significa 'A'". Como \xc3\xa1debería convertirse en "à" pero la computadora no tiene medios para saberlo, debe indicarlo especificando la codificación que se usó al escribir el archivo.

Aaron Digulla
fuente
Creo que faltan algunas piezas aquí: el archivo f2 contiene: hex: 0000000: 4361 7069 745c 7863 335c 7861 316e 0a Capit \ xc3 \ xa1n. codecs.open ('f2', 'rb', 'utf-8'), por ejemplo, los lee a todos en caracteres separados (esperado) ¿Hay alguna forma de escribir en un archivo en ASCII que funcione?
Gregg Lind el
6

a excepción de codecs.open(), uno puede usar io.open()para trabajar con Python2 o Python3 para leer / escribir archivos Unicode

ejemplo

import io

text = u'á'
encoding = 'utf8'

with io.open('data.txt', 'w', encoding=encoding, newline='\n') as fout:
    fout.write(text)

with io.open('data.txt', 'r', encoding=encoding, newline='\n') as fin:
    text2 = fin.read()

assert text == text2
Ryan
fuente
Sí, usar io es mejor; Pero escribí con la declaración como esta with io.open('data.txt', 'w', 'utf-8') as file:y da un error: TypeError: an integer is required. Después me cambié with io.open('data.txt', 'w', encoding='utf-8') as file:y funcionó.
Evan Hu
5

Bueno, su editor de texto favorito no se da cuenta de que \xc3\xa1se supone que son literales de caracteres, pero los interpreta como texto. Es por eso que obtienes las barras invertidas dobles en la última línea: ahora es una barra invertida + xc3, etc. real en tu archivo.

Si desea leer y escribir archivos codificados en Python, mejor utilice el módulo de códecs .

Pegar texto entre el terminal y las aplicaciones es difícil, porque no sabes qué programa interpretará tu texto usando qué codificación. Puedes probar lo siguiente:

>>> s = file("f1").read()
>>> print unicode(s, "Latin-1")
Capitán

Luego pegue esta cadena en su editor y asegúrese de que la almacena usando Latin-1. Bajo el supuesto de que el portapapeles no distorsiona la cuerda, el viaje de ida y vuelta debería funcionar.

Torsten Marek
fuente
4

La secuencia \ x .. es algo específico de Python. No es una secuencia de escape de bytes universal.

La forma en que ingrese realmente en un código no ASCII codificado en UTF-8 depende de su sistema operativo y / o su editor. Así es como lo haces en Windows . Para que OS X ingrese a con un acento agudo, puede presionar option+ Ey luego A, y casi todos los editores de texto en OS X admiten UTF-8.

ʞɔıu
fuente
3

También puede mejorar la open()función original para trabajar con archivos Unicode reemplazándola en su lugar, utilizando la partialfunción. La belleza de esta solución es que no necesita cambiar ningún código antiguo. Es transparente

import codecs
import functools
open = functools.partial(codecs.open, encoding='utf-8')
hipertracker
fuente
1

Estaba tratando de analizar iCal usando Python 2.7.9:

desde el calendario de importación icalendar

Pero estaba obteniendo:

 Traceback (most recent call last):
 File "ical.py", line 92, in parse
    print "{}".format(e[attr])
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1' in position 7: ordinal not in range(128)

y se solucionó con solo:

print "{}".format(e[attr].encode("utf-8"))

(Ahora puede imprimir liké á böss).

Alexx Roche
fuente