Unicode (UTF-8) lectura y escritura en archivos en Python


Estoy teniendo algún fallo cerebral en la comprensión de 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'

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

Lo que realmente estoy fallando a grok aquí, es lo que el punto de la representación UTF-8 es, si realmente no se puede conseguir Python para reconocerlo, cuando viene de fuera. Tal vez debería simplemente JSON volcar la cadena, y utilizar que en su lugar, ya que tiene una representación asciiable! Más al punto, ¿hay una representación ASCII de este objeto Unicode que Python reconocerá y decodificará, cuando llegue 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'
Author: Peter Mortensen, 2009-01-29

13 answers

En la notación

u'Capit\xe1n\n'

El "\xe1" representa solo un byte. "\x "le 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 por una barra invertida. Así que tienes cuatro bytes en tu cadena:"\", "x", "c"y " 3".

Editar:

Como otros señalaron en sus respuestas solo debe introducir 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 códec string_escape para decodificarla en una cadena normal:

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

El resultado es una cadena que está codificada en UTF-8 donde el carácter acentuado está representado por los dos bytes que se escribieron \\xc3\\xa1 en la cadena original. Si quieres tener una cadena unicode tienes que decodificar de nuevo con UTF-8.

A 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.out con el contenido del archivo que guardó con su editor.

 90
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-29 18:44:41

En lugar de meterme con los métodos de codificación y decodificación me resulta más fácil especificar la codificación al abrir el archivo. Las io module (añadido en Python 2.6) proporciona una función io.open, que tiene un parámetro de codificación.

Utilice el método abierto del módulo io.

>>>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 función io.open es un alias para la función incorporada open. La función abierta incorporada solo admite el argumento de codificación en Python 3, no Python 2.

Edit: Anteriormente esta respuesta recomendaba el módulo codecs . El módulo de codecs puede causar problemas al mezclar read() y readline(), así que esta respuesta ahora recomienda el módulo io en su lugar.

Utilice el método abierto del módulo codecs.

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

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

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

Si conoce la codificación de un archivo, usar el paquete codecs va a ser mucho menos confuso.

Véase http://docs.python.org/library/codecs.html#codecs.open

 612
Author: Tim Swast,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-03 22:16:04

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

[Editar en 2016-02-10 para la aclaración solicitada]

Python3 agregó el parámetro encoding a su función abierta. La siguiente información sobre la función open 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)

Encoding es el nombre de la codificación utilizada para decodificar o codificar file. Esto solo debe usarse en modo texto. La codificación predeterminada ser depende de la plataforma (sea cual sea la configuración regional .getpreferredencoding () devuelve), pero cualquier codificación de texto soportada por Python puede ser utilizada. Vea el módulo codecs para la lista de codificaciones soportadas.

Así que al agregar encoding='utf-8' como un parámetro a la función open, 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.)

 20
Author: Dakusan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-04 08:20:30

Por lo tanto, 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 en particular permite 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.

 16
Author: Gregg Lind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-04 18:37:29

En realidad, esto me funcionó 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)
 13
Author: Sina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-04 18:43:01
# -*- 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()
 12
Author: Ricardo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-08 20:24:46

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.

 5
Author: praj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-18 14:38:14

A excepción de codecs.open(), se puede usar io.open() para trabajar con Python2 o Python3 para leer / escribir un archivo 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
 5
Author: Ryan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-18 01:32:24

Bueno, su editor de texto favorito no se da cuenta de que \xc3\xa1 se 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 now ahora es una barra invertida real + xc3, etc. en tu expediente.

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

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 pega esta cadena en tu editor y asegúrate 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.

 4
Author: Torsten Marek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-04 18:11:07

Se ha tropezado con el problema general con las codificaciones: ¿Cómo puedo saber en qué codificación está un archivo?

Respuesta: No puedes a menos que el formato de archivo proporcione esto. XML, por ejemplo, comienza con:

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

Este encabezado fue cuidadosamente elegido para que pueda leerse sin importar la codificación. En su caso, no hay tal pista, por lo tanto, ni su editor ni Python tienen idea de lo que está pasando. Por lo tanto, debe utilizar el módulo codecs y utilizar codecs.open(path,mode,encoding) que proporciona el bit que falta en Python.

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

El punto 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 en el mundo pueden manejar). Pero dado que la mayoría de los OSs 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 tema es el representación en Python. Esto se explica perfectamente en el comentario de heikogerlach. Debe entender 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 el diéresis y guardar el archivo).

Dicho esto, puedes usar la función de Python eval () para convertir una cadena string:

>>> 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. Ahora es 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 todos 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. Así que necesitas 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 usando cuatro caracteres para codificar Unicode hasta 0xFFFF (65535).

Así que no puedes escribir Unicode directamente en ASCII (porque ASCII simplemente no contiene los mismos caracteres). Puede escribirlo como escape de cadena (como en f2); en este caso, el archivo puede ser representado 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 ser consciente de cuánta memoria usa: Tres veces la cantidad de uso de codecs.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 que "65 significa "A". Dado que \xc3\xa1 debe convertirse en "à", pero el equipo no tiene medios para saber, debe decirle especificando la codificación que se utilizó al escribir el archivo.

 4
Author: Aaron Digulla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:25

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

La forma en que realmente ingrese en UTF-8 no-ASCII codificado depende de su sistema operativo y/o su editor. Así es como se hace en Windows. Para que OS X ingrese un con un acento agudo, simplemente puede presionar la opción + E , luego A, y casi todos los editores de texto en OS X admiten UTF-8.

 3
Author: ʞɔıu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-04 18:09:13

También puede mejorar la función original open() para trabajar con archivos Unicode reemplazándola en su lugar, utilizando la función partial. 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')
 2
Author: hipertracker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-04 18:47:44

Estaba tratando de analizar {[3] }al usando Python 2.7.9:

Desde el Calendario de importación de icalendar

Pero yo estaba recibiendo:

 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 arregló con solo:

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

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

 0
Author: Alexx Roche,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-04 18:45:31