Unicode (UTF-8) membaca dan menulis ke file dengan Python

330

Saya mengalami beberapa kegagalan otak dalam memahami membaca dan menulis teks ke file (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'

Jadi saya mengetikkan Capit\xc3\xa1nke editor favorit saya, di file f2.

Kemudian:

>>> 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'

Apa yang tidak saya mengerti di sini? Jelas ada beberapa sihir penting (atau akal sehat) yang saya lewatkan. Apa yang diketik satu dalam file teks untuk mendapatkan konversi yang tepat?

Apa yang saya benar-benar gagal grok di sini, adalah apa tujuan dari representasi UTF-8 adalah, jika Anda tidak bisa benar-benar mendapatkan Python untuk mengenalinya, ketika itu datang dari luar. Mungkin saya seharusnya hanya membuang string JSON, dan menggunakannya sebagai gantinya, karena itu memiliki representasi asciiable! Lebih penting lagi, apakah ada representasi ASCII dari objek Unicode ini yang akan dikenali dan didekode oleh Python, ketika masuk dari file? Jika demikian, bagaimana cara mendapatkannya?

>>> print simplejson.dumps(ss)
'"Capit\u00e1n"'
>>> print >> file('f3','w'), simplejson.dumps(ss)
>>> simplejson.load(open('f3'))
u'Capit\xe1n'
Gregg Lind
sumber

Jawaban:

110

Dalam notasi

u'Capit\xe1n\n'

"\ xe1" hanya mewakili satu byte. "\ x" memberi tahu Anda bahwa "e1" dalam heksadesimal. Ketika Anda menulis

Capit\xc3\xa1n

ke dalam file Anda, Anda memiliki "\ xc3" di dalamnya. Itu adalah 4 byte dan dalam kode Anda, Anda membaca semuanya. Anda dapat melihat ini saat menampilkannya:

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

Anda dapat melihat bahwa backslash lolos dari backslash. Jadi Anda memiliki empat byte dalam string Anda: "\", "x", "c" dan "3".

Edit:

Seperti yang ditunjukkan orang lain dalam jawaban mereka, Anda hanya harus memasukkan karakter dalam editor dan editor Anda kemudian harus menangani konversi ke UTF-8 dan menyimpannya.

Jika Anda benar-benar memiliki string dalam format ini, Anda dapat menggunakan string_escapecodec untuk mendekodenya menjadi string normal:

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

Hasilnya adalah string yang dikodekan dalam UTF-8 di mana karakter beraksen diwakili oleh dua byte yang ditulis \\xc3\\xa1dalam string asli. Jika Anda ingin memiliki string unicode, Anda harus mendekode lagi dengan UTF-8.

Untuk edit Anda: Anda tidak memiliki UTF-8 di file Anda. Untuk benar-benar melihat tampilannya:

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

Bandingkan konten file utf-8.outdengan konten file yang Anda simpan dengan editor Anda.


sumber
Jadi, apa gunanya format yang dikodekan utf-8 jika python dapat membaca file yang menggunakannya? Dengan kata lain, apakah ada representasi ascii yang python akan baca dalam \ xc3 sebagai 1 byte?
Gregg Lind
4
Jawaban untuk pertanyaan "Jadi, apa gunanya ..." adalah "Mu." (karena Python dapat membaca file yang disandikan dalam UTF-8). Untuk pertanyaan kedua Anda: \ xc3 bukan bagian dari set ASCII. Mungkin maksud Anda "pengodean 8-bit" sebagai gantinya. Anda bingung tentang Unicode dan penyandian; tidak apa-apa, banyak.
tzot
8
Coba baca ini sebagai primer: joelonsoftware.com/articles/Unicode.html
tzot
catatan: u'\xe1'adalah satu titik kode Unicode U+00e1yang dapat direpresentasikan menggunakan 1 atau lebih byte tergantung pada pengkodean karakter (ini adalah 2 byte dalam utf-8). b'\xe1'adalah satu byte (angka 225), huruf apa yang dapat diwakilinya tergantung pada pengkodean karakter yang digunakan untuk mendekodekannya, misalnya б( U+0431) di cp1251, с( U+0441) di cp866, dll.
jfs
11
Sungguh menakjubkan betapa banyak coders Inggris mengatakan "hanya menggunakan ascii" dan kemudian gagal menyadari bahwa tanda £ bukan. Sebagian besar tidak menyadari bahwa ascii! = Halaman kode lokal (yaitu latin1).
Danny Staple
712

Daripada mengacaukan metode encode dan decode saya merasa lebih mudah untuk menentukan encoding saat membuka file. The ioModul (ditambahkan dalam Python 2.6) menyediakanio.open fungsi, yang memiliki parameter encoding.

Gunakan metode terbuka dari iomodul.

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

Kemudian setelah memanggil fungsi read (), objek Unicode yang dikodekan dikembalikan.

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

Perhatikan bahwa dalam Python 3, io.openfungsinya adalah alias untuk openfungsi bawaan. Fungsi terbuka bawaan hanya mendukung argumen penyandian dalam Python 3, bukan Python 2.

Sunting: Sebelumnya jawaban ini merekomendasikan modul codec . The codec modul dapat menyebabkan masalah ketika pencampuran read()danreadline() , jadi jawaban ini sekarang merekomendasikan io modul sebagai gantinya.

Gunakan metode terbuka dari modul codec.

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

Kemudian setelah memanggil fungsi read (), objek Unicode yang dikodekan dikembalikan.

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

Jika Anda mengetahui penyandian file, menggunakan paket codec akan jauh lebih membingungkan.

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

Tim Swast
sumber
74
Bekerja sempurna untuk menulis file juga, bukan open(file,'w')yang codecs.open(file,'w','utf-8')dipecahkan
Matt Connolly
1
Ini jawaban yang saya cari :)
Justin
6
Apakah codecs.open(...)metode ini juga sepenuhnya sesuai dengan with open(...):gaya, di mana withpeduli tentang penutupan file setelah semua dilakukan Sepertinya tetap berhasil.
coba-tangkap-akhirnya
2
@ coba-tangkap-akhirnya Ya. Saya menggunakan with codecs.open(...) as f:semua waktu.
Tim Swast
6
Saya berharap saya dapat memperbaiki ini seratus kali. Setelah menderita selama beberapa hari karena masalah pengkodean yang disebabkan oleh banyak data campuran dan membaca silang tentang pengodean, jawaban ini seperti air di gurun. Seandainya aku melihatnya lebih cepat.
Mike Girard
45

Sekarang yang Anda butuhkan di Python3 adalah open(Filename, 'r', encoding='utf-8')

[Edit pada 2016-02-10 untuk klarifikasi yang diminta]

Python3 menambahkan parameter encoding ke fungsi terbuka. Informasi berikut tentang fungsi terbuka dikumpulkan dari sini: 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)

Pengkodean adalah nama pengodean yang digunakan untuk mendekode atau menyandikan file. Ini seharusnya hanya digunakan dalam mode teks. Pengkodean default bergantung pada platform (apa pun locale.getpreferredencoding () kembali), tetapi pengodean teks apa pun yang didukung oleh Python dapat digunakan. Lihat modul codec untuk daftar penyandian yang didukung.

Jadi dengan menambahkan encoding='utf-8'sebagai parameter ke fungsi terbuka, membaca dan menulis file semuanya dilakukan sebagai utf8 (yang juga sekarang merupakan pengkodean default dari semua yang dilakukan dengan Python.)

Dakusan
sumber
Bisakah Anda menjelaskan lebih lanjut jawaban Anda dengan menambahkan sedikit deskripsi tentang solusi yang Anda berikan?
abarisone
2
Tampaknya ini tersedia dalam python 2 menggunakan modul codec - codecs.open('somefile', encoding='utf-8') stackoverflow.com/a/147756/149428
Taylor Edmiston
18

Jadi, saya telah menemukan solusi untuk apa yang saya cari, yaitu:

print open('f2').read().decode('string-escape').decode("utf-8")

Ada beberapa codec yang tidak biasa yang berguna di sini. Bacaan khusus ini memungkinkan seseorang untuk mengambil representasi UTF-8 dari dalam Python, menyalinnya ke file ASCII, dan meminta mereka untuk membacanya di Unicode. Di bawah decode "string-escape", garis miring tidak akan digandakan.

Ini memungkinkan untuk semacam perjalanan pulang pergi yang saya bayangkan.

Gregg Lind
sumber
1
Respons yang baik, saya telah menguji kedua solusi (codecs.open(file,"r","utf-8")dan secara sederhana open(file,"r").read().decode("utf-8")dan keduanya bekerja dengan sempurna.
Elang
Saya mendapatkan "TypeError: diharapkan str, byte atau os.PathLike objek, bukan _io.TextIOWrapper" tahu mengapa?
JinSnow
Saya pikir, mengingat jumlah upvote, itu akan menjadi ide bagus untuk menerima jawaban kedua :)
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
sumber
14

Sebenarnya, ini berhasil bagi saya untuk membaca file dengan pengkodean UTF-8 dengan Python 3.2:

import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
    print(line)
Sina
sumber
6

Untuk membaca dalam string Unicode dan kemudian mengirim ke HTML, saya melakukan ini:

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

Berguna untuk server http bertenaga python.

praj
sumber
6

Anda menemukan masalah penyandian secara umum: Bagaimana saya bisa tahu di mana penyandian file?

Jawab: Anda tidak bisa kecuali format file yang disediakan untuk ini. XML, misalnya, dimulai dengan:

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

Header ini dipilih dengan hati-hati sehingga dapat dibaca terlepas dari pengodeannya. Dalam kasus Anda, tidak ada petunjuk seperti itu, maka editor atau Python Anda tidak tahu apa yang sedang terjadi. Oleh karena itu, Anda harus menggunakan codecsmodul dan menggunakan codecs.open(path,mode,encoding)yang menyediakan bit yang hilang dengan Python.

Adapun editor Anda, Anda harus memeriksa apakah itu menawarkan beberapa cara untuk mengatur penyandian file.

Inti dari UTF-8 adalah untuk dapat menyandikan karakter 21-bit (Unicode) sebagai aliran data 8-bit (karena itulah satu-satunya hal yang dapat ditangani oleh semua komputer di dunia). Tetapi karena sebagian besar OS mendahului era Unicode, mereka tidak memiliki alat yang sesuai untuk melampirkan informasi pengkodean ke file pada hard disk.

Masalah selanjutnya adalah representasi dalam Python. Ini dijelaskan dengan sempurna dalam komentar oleh heikogerlach . Anda harus memahami bahwa konsol Anda hanya dapat menampilkan ASCII. Untuk menampilkan Unicode atau apa pun> = charcode 128, ia harus menggunakan beberapa cara untuk melarikan diri. Di editor Anda, Anda tidak boleh mengetikkan string tampilan lolos tetapi apa artinya string (dalam hal ini, Anda harus memasukkan umlaut dan menyimpan file).

Yang mengatakan, Anda bisa menggunakan fungsi Python eval () untuk mengubah string yang lolos menjadi string:

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

Seperti yang Anda lihat, string "\ xc3" telah berubah menjadi satu karakter. Ini sekarang merupakan string 8-bit, disandikan UTF-8. Untuk mendapatkan Unicode:

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

Gregg Lind bertanya: Saya pikir ada beberapa bagian yang hilang di sini: file f2 berisi: hex:

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

codecs.open('f2','rb', 'utf-8'), misalnya, membaca semuanya dalam karakter yang terpisah (diharapkan) Apakah ada cara untuk menulis ke file di ASCII yang akan berfungsi?

Jawaban: Itu tergantung pada apa yang Anda maksud. ASCII tidak dapat mewakili karakter> 127. Jadi, Anda perlu cara untuk mengatakan "beberapa karakter berikutnya berarti sesuatu yang istimewa" yang dilakukan oleh urutan "\ x". Dikatakan: Dua karakter berikutnya adalah kode dari satu karakter. "\ u" melakukan hal yang sama menggunakan empat karakter untuk menyandikan Unicode hingga 0xFFFF (65535).

Jadi, Anda tidak dapat langsung menulis Unicode ke ASCII (karena ASCII tidak mengandung karakter yang sama). Anda dapat menulisnya saat string keluar (seperti pada f2); dalam hal ini, file dapat direpresentasikan sebagai ASCII. Atau Anda dapat menulisnya sebagai UTF-8, dalam hal ini, Anda memerlukan aliran aman 8-bit.

Solusi Anda menggunakan decode('string-escape')tidak berfungsi, tetapi Anda harus menyadari berapa banyak memori yang Anda gunakan: Tiga kali jumlah penggunaan codecs.open().

Ingat bahwa file hanya urutan byte dengan 8 bit. Baik bit maupun byte tidak memiliki arti. Kaulah yang mengatakan "65 berarti 'A'". Karena \xc3\xa1harus menjadi "à" tetapi komputer tidak memiliki sarana untuk mengetahuinya, Anda harus mengetahuinya dengan menentukan pengkodean yang digunakan saat menulis file.

Aaron Digulla
sumber
Saya pikir ada beberapa bagian yang hilang di sini: file f2 berisi: hex: 0000000: 4361 7069 745c 7863 335c 7861 316e 0a Capit \ xc3 \ xa1n. codecs.open ('f2', 'rb', 'utf-8'), misalnya, membaca semuanya dalam karakter yang terpisah (diharapkan) Apakah ada cara untuk menulis ke file di ascii yang akan berfungsi?
Gregg Lind
6

kecuali codecs.open(), seseorang dapat menggunakan io.open()untuk bekerja dengan Python2 atau Python3 untuk membaca / menulis file unicode

contoh

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
sumber
Ya, menggunakan io lebih baik; Tapi saya menulis dengan pernyataan seperti ini with io.open('data.txt', 'w', 'utf-8') as file:dan mendapat kesalahan: TypeError: an integer is required. Setelah saya ganti with io.open('data.txt', 'w', encoding='utf-8') as file:dan berhasil.
Evan Hu
5

Nah, editor teks favorit Anda tidak menyadari bahwa \xc3\xa1seharusnya karakter literal, tetapi menafsirkannya sebagai teks. Itu sebabnya Anda mendapatkan garis miring terbalik ganda di baris terakhir - sekarang garis miring terbalik + xc3, dll. Di file Anda.

Jika Anda ingin membaca dan menulis file yang disandikan dengan Python, gunakan codec terbaik modul .

Menempelkan teks antara terminal dan aplikasi sulit, karena Anda tidak tahu program mana yang akan menafsirkan teks Anda menggunakan pengkodean mana. Anda dapat mencoba yang berikut ini:

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

Kemudian tempelkan string ini ke editor Anda dan pastikan itu menyimpannya menggunakan Latin-1. Di bawah asumsi bahwa clipboard tidak memutarbalikkan tali, perjalanan pulang pergi akan berhasil.

Torsten Marek
sumber
4

Urutan \ x .. adalah sesuatu yang khusus untuk Python. Ini bukan urutan escape byte universal.

Bagaimana Anda memasukkan non-ASCII yang dikodekan UTF-8 tergantung pada OS dan / atau editor Anda. Inilah cara Anda melakukannya di Windows . Untuk OS X untuk memasuki sebuah dengan aksen akut Anda hanya dapat menekan option+ E, kemudian A, dan hampir semua teks editor di OS X dukungan UTF-8.

ʞɔıu
sumber
3

Anda juga dapat meningkatkan open()fungsi asli agar berfungsi dengan file Unicode dengan menggantinya di tempat, menggunakan partialfungsi tersebut. Keindahan dari solusi ini adalah Anda tidak perlu mengubah kode lama apa pun. Itu transparan.

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

Saya mencoba mengurai iCal menggunakan Python 2.7.9:

dari Kalender impor icalendar

Tapi saya mendapatkan:

 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)

dan diperbaiki hanya dengan:

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

(Sekarang dapat mencetak liké á böss.)

Alexx Roche
sumber
0

Saya menemukan pendekatan paling sederhana dengan mengubah pengkodean default seluruh skrip menjadi 'UTF-8':

import sys
reload(sys)
sys.setdefaultencoding('utf8')

apa saja open, printatau pernyataan lain hanya akan digunakanutf8 .

Paling tidak berfungsi untuk Python 2.7.9 .

Thx pergi ke https://markhneedham.com/blog/2015/05/21/python-unicodeencodeerror-ascii-codec-cant-encode-character-uxfc-in-position-11-ordinal-not-in-range128/ ( lihat bagian akhir).

dr0i
sumber