Menulis teks Unicode ke file teks?

225

Saya menarik data dari Google doc, memprosesnya, dan menulisnya ke file (yang akhirnya akan saya tempelkan ke halaman Wordpress).

Ini memiliki beberapa simbol non-ASCII. Bagaimana saya bisa mengonversi ini dengan aman ke simbol yang dapat digunakan dalam sumber HTML?

Saat ini saya mengonversi semuanya ke Unicode di jalan, menggabungkan semuanya bersama dalam string Python, lalu melakukan:

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

Ada kesalahan penyandian pada baris terakhir:

UnicodeDecodeError: 'ascii' codec tidak dapat mendekode byte 0xa0 di posisi 12286: ordinal tidak dalam jangkauan (128)

Solusi parsial:

Python ini berjalan tanpa kesalahan:

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"))

Tetapi jika saya membuka file teks yang sebenarnya, saya melihat banyak simbol seperti:

Qur’an 

Mungkin saya perlu menulis sesuatu selain file teks?

simon
sumber
1
Program yang Anda gunakan untuk membukanya tidak menafsirkan teks UTF-8 dengan benar. Seharusnya ada opsi untuk membuka file sebagai UTF-8.
Thomas K

Jawaban:

322

Menangani secara eksklusif dengan objek unicode sebanyak mungkin dengan mendekodekan hal-hal ke objek unicode ketika Anda pertama kali mendapatkannya dan menyandikannya saat diperlukan di jalan keluar.

Jika string Anda benar-benar objek unicode, Anda harus mengonversinya menjadi objek string unicode-encode sebelum menulisnya ke file:

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

Saat Anda membaca file itu lagi, Anda akan mendapatkan string yang disandikan unicode yang bisa Anda dekode ke objek unicode:

f = file('test', 'r')
print f.read().decode('utf8')
quististoic
sumber
Terima kasih. Ini berjalan tanpa kesalahan, tetapi kemudian jika saya membuka file teks, saya melihat banyak simbol aneh :) Saya perlu menyalin dan menempelkan teks ke halaman Wordpress (jangan tanya). Apakah ada cara saya benar-benar dapat mencetak simbol yang ada di sana? Saya kira tidak ke file txt, kan, tapi mungkin untuk yang lain?
simon
1
Apa yang Anda gunakan untuk membuka file teks? Saya kira Anda menggunakan Windows, dan Anda membukanya di Notepad, yang tidak terlalu cerdas dengan penyandian. Apa yang terjadi ketika Anda membukanya di Wordpad?
quasistoic
@quasistoic, di mana metode file terbentuk?
Omar Cusma Fait
Saya perlu mengaktifkan mode biner, yaitu f = buka ('test', 'wb'), seperti yang dijelaskan dalam stackoverflow.com/a/5513856/6580199 - jika tidak saya akan mendapatkan argumen "TypeError: write () harus str, bukan byte "
Benji
72

Dalam Python 2.6+, Anda bisa menggunakanio.open() yang default ( builtinopen() ) di Python 3:

import io

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

Mungkin lebih nyaman jika Anda perlu menulis teks secara bertahap (Anda tidak perlu menelepon unicode_text.encode(character_encoding)berkali-kali). Tidak seperti codecsmodul, iomodul memiliki dukungan baris baru universal yang tepat.

jfs
sumber
1
Sobat, saya menghabiskan banyak waktu untuk menemukan ini! Terima kasih!
Georgy Gobozov
2
Ini juga berfungsi untuk Python 3 (jelas, tetapi masih layak untuk ditunjukkan).
Hippo
37

Penanganan string Unicode sudah terstandarisasi dalam Python 3.

  1. char sudah tersimpan di Unicode (32-bit) dalam memori
  2. Anda hanya perlu membuka file dalam utf-8
    (Konversi 32-bit Unicode ke variabel-byte-panjang utf-8 secara otomatis dilakukan dari memori ke file.)

    out1 = "(嘉南大圳 ㄐㄧㄚ ㄋㄢˊ ㄉㄚˋ ㄗㄨㄣˋ )"
    fobj = open("t1.txt", "w", encoding="utf-8")
    fobj.write(out1)
    fobj.close()
david m lee
sumber
Tapi ini tidak berfungsi pada Python 2, kan? (Saya harus mengatakan, pada kode Python 3 ini, terlihat sangat ringkas dan masuk akal)
Liwen Zhao
seharusnya tidak bekerja pada Python 2. Kami tetap menggunakan Python 3. 3 jauh lebih baik.
david m lee
18

File dibuka oleh codecs.openadalah file yang mengambil unicodedata, mengkodekannya iso-8859-1dan menulisnya ke file. Namun, apa yang Anda coba tulis bukanlah unicode; Anda mengambil unicodedan menyandikannya dalam iso-8859-1 diri Anda . Itulah yang dilakukan oleh unicode.encodemetode ini, dan hasil dari pengkodean string unicode adalah bytestring ( strtipe.)

Anda harus menggunakan normal open()dan menyandikan unicode sendiri, atau (biasanya ide yang lebih baik) menggunakan codecs.open()dan tidak menyandikan data sendiri.

Thomas Wouters
sumber
17

Pendahuluan: apakah pemirsa Anda akan berfungsi?

Pastikan pemirsa / editor / terminal Anda (namun Anda berinteraksi dengan file yang dikodekan utf-8 Anda) dapat membaca file tersebut. Ini sering menjadi masalah pada Windows , misalnya, Notepad.

Menulis teks Unicode ke file teks?

Dalam Python 2, gunakan opendari iomodul (ini sama dengan builtin opendi Python 3):

import io

Praktik terbaik, secara umum, digunakan UTF-8untuk menulis ke file (kita bahkan tidak perlu khawatir tentang byte-order dengan utf-8).

encoding = 'utf-8'

utf-8 adalah pengkodean yang paling modern dan dapat digunakan secara universal - ini bekerja di semua browser web, sebagian besar editor teks (lihat pengaturan Anda jika Anda memiliki masalah) dan sebagian besar terminal / shell.

Pada Windows, Anda dapat mencoba utf-16lejika Anda terbatas untuk melihat output di Notepad (atau penampil terbatas lainnya).

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

Dan buka saja dengan manajer konteks dan tulis karakter unicode Anda:

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

Contoh menggunakan banyak karakter Unicode

Berikut adalah contoh yang mencoba untuk memetakan setiap karakter yang mungkin hingga tiga bit lebar (4 adalah maks, tapi itu akan menjadi agak jauh) dari representasi digital (dalam bilangan bulat) ke output cetak yang disandikan, bersama dengan namanya, jika mungkin (taruh ini di file bernama 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))

Ini akan berjalan dalam urutan sekitar satu menit, dan Anda dapat melihat file data, dan jika penampil file Anda dapat menampilkan unicode, Anda akan melihatnya. Informasi tentang kategori dapat ditemukan di sini . Berdasarkan jumlah, kami mungkin dapat meningkatkan hasil dengan mengecualikan kategori Cn dan Co, yang tidak memiliki simbol yang terkait dengannya.

$ python uni.py

Ini akan menampilkan pemetaan heksadesimal, kategori , simbol (kecuali jika tidak bisa mendapatkan nama, jadi mungkin karakter kontrol), dan nama simbol. misalnya

Saya merekomendasikan lessUnix atau Cygwin (jangan cetak / cat seluruh file ke output Anda):

$ less unidata

misalnya akan menampilkan mirip dengan baris berikut yang saya sampel darinya menggunakan 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

Python 3.5 saya dari Anaconda memiliki unicode 8.0, saya kira kebanyakan 3 akan.

Aaron Hall
sumber
3

Cara mencetak karakter unicode ke file:

Simpan ini ke file: 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: é')

Jalankan dan pipa output ke file:

python foo.py > tmp.txt

Buka tmp.txt dan lihat ke dalam, Anda melihat ini:

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

Dengan demikian Anda telah menyimpan unicode e dengan tanda kebingungan pada file.

Eric Leschinski
sumber
2
Saya sangat senang dengan jawaban ini, tetapi memberikan kesalahan pada mesin saya. Ketika saya menyalin / menempelkan kode Anda, saya mendapatkan kesalahan: "TypeError: harus str, bukan byte"
Richard Rast
1

Kesalahan itu muncul ketika Anda mencoba untuk menyandikan string non-unicode: ia mencoba untuk men-decode, dengan asumsi itu dalam ASCII biasa. Ada dua kemungkinan:

  1. Anda menyandikannya menjadi bytestring, tetapi karena Anda telah menggunakan codecs.open, metode tulis mengharapkan objek unicode. Jadi Anda menyandikannya, dan mencoba memecahkannya lagi. Coba: f.write(all_html)sebagai gantinya.
  2. all_html sebenarnya bukan objek unicode. Ketika Anda melakukannya .encode(...), pertama kali mencoba untuk memecahkan kode itu.
Thomas K
sumber
0

Dalam hal penulisan di 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à'

Dalam hal penulisan di 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)

Untuk menghindari kesalahan ini, Anda harus menyandikannya ke byte menggunakan codec "utf-8" seperti ini:

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

dan mendekode data saat membaca menggunakan codec "utf-8":

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

Dan juga jika Anda mencoba menjalankan print pada string ini maka secara otomatis akan mendekode menggunakan codec "utf-8" seperti ini

>>> print a
batsà
ashish14
sumber