¿Escribir texto Unicode en un archivo de texto?

225

Extraigo datos de un documento de Google, los proceso y los escribo en un archivo (que eventualmente pegaré en una página de Wordpress).

Tiene algunos símbolos no ASCII. ¿Cómo puedo convertirlos de manera segura en símbolos que se pueden usar en código fuente HTML?

Actualmente estoy convirtiendo todo a Unicode en el camino, uniéndolo todo en una cadena de Python, y luego haciendo:

import codecs
f = codecs.open('out.txt', mode="w", encoding="iso-8859-1")
f.write(all_html.encode("iso-8859-1", "replace"))

Hay un error de codificación en la última línea:

UnicodeDecodeError: el códec 'ascii' no puede decodificar el byte 0xa0 en la posición 12286: el ordinal no está en el rango (128)

Solución parcial:

Este Python se ejecuta sin error:

row = [unicode(x.strip()) if x is not None else u'' for x in row]
all_html = row[0] + "<br/>" + row[1]
f = open('out.txt', 'w')
f.write(all_html.encode("utf-8"))

Pero luego, si abro el archivo de texto real, veo muchos símbolos como:

Qur’an 

¿Quizás necesito escribir en algo que no sea un archivo de texto?

Simón
fuente
1
El programa que está utilizando para abrirlo no está interpretando el texto UTF-8 correctamente. Debería tener una opción para abrir el archivo como UTF-8.
Thomas K

Respuestas:

322

Trate exclusivamente con objetos unicode tanto como sea posible decodificando cosas en objetos unicode cuando los obtenga por primera vez y codificándolos según sea necesario al salir.

Si su cadena es en realidad un objeto Unicode, deberá convertirla en un objeto de cadena codificada Unicode antes de escribirla en un archivo:

foo = u'Δ, Й, ק, ‎ م, ๗, あ, 叶, 葉, and 말.'
f = open('test', 'w')
f.write(foo.encode('utf8'))
f.close()

Cuando vuelva a leer ese archivo, obtendrá una cadena codificada en Unicode que puede decodificar en un objeto Unicode:

f = file('test', 'r')
print f.read().decode('utf8')
cuasistoico
fuente
Gracias. Esto se ejecuta sin un error, pero si abro el archivo de texto, veo un montón de símbolos extraños :) Necesito copiar y pegar el texto en una página de Wordpress (no preguntar). ¿Hay alguna manera de que realmente pueda imprimir los símbolos que están allí? Supongo que no a un archivo txt, ¿verdad, pero tal vez a otra cosa?
Simon
1
¿Qué estás usando para abrir el archivo de texto? Supongo que estás en Windows y lo estás abriendo en el Bloc de notas, que no es demasiado inteligente con las codificaciones. ¿Qué sucede cuando lo abres en Wordpad?
Cuasistoica
@quasistoic ¿de dónde viene el método de archivo ?
Omar Cusma Fait
Necesitaba activar el modo binario, es decir, f = abierto ('prueba', 'wb'), como se describe en stackoverflow.com/a/5513856/6580199 ; de lo contrario, obtendría el argumento "TypeError: write () debe ser str, no bytes "
Benji
72

En Python 2.6+, puede usario.open() el valor predeterminado ( integradoopen() ) en Python 3:

import io

with io.open(filename, 'w', encoding=character_encoding) as file:
    file.write(unicode_text)

Puede ser más conveniente si necesita escribir el texto de forma incremental (no necesita llamar unicode_text.encode(character_encoding)varias veces). A diferencia del codecsmódulo, el iomódulo tiene un soporte de líneas nuevas universal adecuado.

jfs
fuente
1
Hombre, pasé mucho tiempo para encontrar esto! ¡Gracias!
Georgy Gobozov
2
Esto también funciona para Python 3 (obvio, pero aún vale la pena señalarlo).
Hippo
37

El manejo de cadenas Unicode ya está estandarizado en Python 3.

  1. los caracteres ya están almacenados en Unicode (32 bits) en la memoria
  2. Solo necesita abrir el archivo en utf-8
    (la conversión de Unicode de 32 bits a longitud de byte variable utf-8 se realiza automáticamente de la memoria al archivo).

    out1 = "(嘉南大圳 ㄐㄧㄚ ㄋㄢˊ ㄉㄚˋ ㄗㄨㄣˋ )"
    fobj = open("t1.txt", "w", encoding="utf-8")
    fobj.write(out1)
    fobj.close()
david m lee
fuente
Pero esto no funciona en Python 2, ¿verdad? (Debo decir, en este código de Python 3, se ve tan conciso y razonable)
Liwen Zhao
no debería funcionar en Python 2. Nos quedamos en Python 3. 3 es mucho mejor.
david m lee
18

El archivo abierto por codecs.openes un archivo que toma unicodedatos, los codifica iso-8859-1y los escribe en el archivo. Sin embargo, lo que intentas escribir no lo es unicode; lo tomas unicodey lo codificas en iso-8859-1 ti mismo . Eso es lo que hace el unicode.encodemétodo, y el resultado de codificar una cadena Unicode es una cadena de bytes (un strtipo).

Debe usar normal open()y codificar el Unicode usted mismo, o (generalmente una mejor idea) usar codecs.open()y no codificar los datos usted mismo.

Thomas Wouters
fuente
17

Prefacio: ¿funcionará su visor?

Asegúrese de que su visor / editor / terminal (sin embargo, está interactuando con su archivo codificado utf-8) pueda leer el archivo. Esto es frecuentemente un problema en Windows , por ejemplo, el Bloc de notas.

¿Escribir texto Unicode en un archivo de texto?

En Python 2, use opendesde el iomódulo (esto es lo mismo que el incorporado openen Python 3):

import io

Las mejores prácticas, en general, se utilizan UTF-8para escribir en archivos (ni siquiera tenemos que preocuparnos por el orden de bytes con utf-8).

encoding = 'utf-8'

utf-8 es la codificación más moderna y universalmente utilizable: funciona en todos los navegadores web, en la mayoría de los editores de texto (consulte su configuración si tiene problemas) y en la mayoría de los terminales / shells.

En Windows, puede intentarlo utf-16lesi está limitado a ver la salida en el Bloc de notas (u otro visor limitado).

encoding = 'utf-16le' # sorry, Windows users... :(

Y solo ábralo con el administrador de contexto y escriba sus caracteres Unicode:

with io.open(filename, 'w', encoding=encoding) as f:
    f.write(unicode_object)

Ejemplo usando muchos caracteres Unicode

Aquí hay un ejemplo que intenta asignar todos los caracteres posibles de hasta tres bits de ancho (4 es el máximo, pero eso iría un poco lejos) de la representación digital (en enteros) a una salida imprimible codificada, junto con su nombre, si posible (poner esto en un archivo llamado uni.py):

from __future__ import print_function
import io
from unicodedata import name, category
from curses.ascii import controlnames
from collections import Counter

try: # use these if Python 2
    unicode_chr, range = unichr, xrange
except NameError: # Python 3
    unicode_chr = chr

exclude_categories = set(('Co', 'Cn'))
counts = Counter()
control_names = dict(enumerate(controlnames))
with io.open('unidata', 'w', encoding='utf-8') as f:
    for x in range((2**8)**3): 
        try:
            char = unicode_chr(x)
        except ValueError:
            continue # can't map to unicode, try next x
        cat = category(char)
        counts.update((cat,))
        if cat in exclude_categories:
            continue # get rid of noise & greatly shorten result file
        try:
            uname = name(char)
        except ValueError: # probably control character, don't use actual
            uname = control_names.get(x, '')
            f.write(u'{0:>6x} {1}    {2}\n'.format(x, cat, uname))
        else:
            f.write(u'{0:>6x} {1}  {2}  {3}\n'.format(x, cat, char, uname))
# may as well describe the types we logged.
for cat, count in counts.items():
    print('{0} chars of category, {1}'.format(count, cat))

Esto debería ejecutarse en el orden de aproximadamente un minuto, y puede ver el archivo de datos, y si su visor de archivos puede mostrar unicode, lo verá. La información sobre las categorías se puede encontrar aquí . Según los recuentos, probablemente podamos mejorar nuestros resultados al excluir las categorías Cn y Co, que no tienen símbolos asociados.

$ python uni.py

Mostrará el mapeo hexadecimal, la categoría , el símbolo (a menos que no pueda obtener el nombre, por lo que probablemente sea un carácter de control) y el nombre del símbolo. p.ej

Recomiendo lessen Unix o Cygwin (no imprimir / cat el archivo completo a su salida):

$ less unidata

por ejemplo, se mostrará de manera similar a las siguientes líneas que probé usando Python 2 (Unicode 5.2):

     0 Cc NUL
    20 Zs     SPACE
    21 Po  !  EXCLAMATION MARK
    b6 So    PILCROW SIGN
    d0 Lu  Ð  LATIN CAPITAL LETTER ETH
   e59 Nd    THAI DIGIT NINE
  2887 So    BRAILLE PATTERN DOTS-1238
  bc13 Lo    HANGUL SYLLABLE MIH
  ffeb Sm    HALFWIDTH RIGHTWARDS ARROW

Mi Python 3.5 de Anaconda tiene unicode 8.0, supongo que la mayoría de los 3 lo harían.

Aaron Hall
fuente
3

Cómo imprimir caracteres Unicode en un archivo:

Guarde esto en el archivo: foo.py:

#!/usr/bin/python -tt
# -*- coding: utf-8 -*-
import codecs
import sys 
UTF8Writer = codecs.getwriter('utf8')
sys.stdout = UTF8Writer(sys.stdout)
print(u'e with obfuscation: é')

Ejecútelo y canalice la salida al archivo:

python foo.py > tmp.txt

Abra tmp.txt y mire adentro, verá esto:

el@apollo:~$ cat tmp.txt 
e with obfuscation: é

Por lo tanto, ha guardado unicode e con una marca de ofuscación en un archivo.

Eric Leschinski
fuente
2
Estaba bastante entusiasmado con esta respuesta, pero da un error en mi máquina. Cuando copio / pego su código, aparece un error: "TypeError: debe ser str, no bytes"
Richard Rast
1

Ese error surge cuando intenta codificar una cadena no unicode: intenta decodificarla, suponiendo que esté en ASCII simple. Hay dos posibilidades:

  1. Lo está codificando en una cadena de bytes, pero debido a que ha utilizado codecs.open, el método de escritura espera un objeto unicode. Entonces lo codifica e intenta decodificarlo nuevamente. Prueba: en su f.write(all_html)lugar.
  2. all_html no es, de hecho, un objeto unicode. Cuando lo haces .encode(...), primero intenta decodificarlo.
Thomas K
fuente
0

En caso de escribir en python3

>>> a = u'bats\u00E0'
>>> print a
batsà
>>> f = open("/tmp/test", "w")
>>> f.write(a)
>>> f.close()
>>> data = open("/tmp/test").read()
>>> data
'batsà'

En caso de escribir en python2:

>>> a = u'bats\u00E0'
>>> f = open("/tmp/test", "w")
>>> f.write(a)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe0' in position 4: ordinal not in range(128)

Para evitar este error, tendría que codificarlo en bytes usando códecs "utf-8" como este:

>>> f.write(a.encode("utf-8"))
>>> f.close()

y decodifique los datos mientras lee utilizando los códecs "utf-8":

>>> data = open("/tmp/test").read()
>>> data.decode("utf-8")
u'bats\xe0'

Y también si intenta ejecutar la impresión en esta cadena, se decodificará automáticamente utilizando los códecs "utf-8" como este

>>> print a
batsà
ashish14
fuente