Tulis ke file UTF-8 dengan Python

204

Saya sangat bingung dengan codecs.open function. Ketika saya melakukannya:

file = codecs.open("temp", "w", "utf-8")
file.write(codecs.BOM_UTF8)
file.close()

Ini memberi saya kesalahan

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

Jika aku melakukan:

file = open("temp", "w")
file.write(codecs.BOM_UTF8)
file.close()

Ini bekerja dengan baik.

Pertanyaannya adalah mengapa metode pertama gagal? Dan bagaimana cara memasukkan bom?

Jika metode kedua adalah cara yang benar untuk melakukannya, apa gunanya menggunakan codecs.open(filename, "w", "utf-8")?

John Jiang
sumber
54
Jangan gunakan BOM di UTF-8. Silahkan.
tchrist
7
@tchrist Huh? Kenapa tidak?
Salman von Abbas
8
@SalmanPK BOM tidak diperlukan di UTF-8 dan hanya menambah kompleksitas (mis. Anda tidak bisa hanya menggabungkan file BOM dan hasil dengan teks yang valid). Lihat T&J ini ; jangan lewatkan komentar besar di bawah Q
Alois Mahdal

Jawaban:

271

Saya percaya masalahnya adalah bahwa itu codecs.BOM_UTF8adalah string byte, bukan string Unicode. Saya menduga file handler sedang mencoba menebak apa yang sebenarnya Anda maksudkan berdasarkan "Saya seharusnya menulis Unicode sebagai teks yang dikodekan UTF-8, tetapi Anda telah memberi saya string byte!"

Cobalah menulis string Unicode untuk tanda urutan byte (yaitu Unicode U + FEFF) secara langsung, sehingga file tersebut hanya mengkodekannya sebagai UTF-8:

import codecs

file = codecs.open("lol", "w", "utf-8")
file.write(u'\ufeff')
file.close()

(Tampaknya memberi jawaban yang benar - file dengan byte EF BB BF.)

EDIT: Saran S. Lott untuk menggunakan "utf-8-sig" sebagai pengkodean adalah yang lebih baik daripada secara eksplisit menulis BOM sendiri, tetapi saya akan meninggalkan jawaban ini di sini karena menjelaskan apa yang salah sebelumnya.

Jon Skeet
sumber
Peringatan: buka dan buka tidak sama. Jika Anda "dari codec import open", itu TIDAK akan sama dengan Anda cukup mengetik "open".
Apache
2
Anda juga dapat menggunakan codecs.open ('test.txt', 'w', 'utf-8-sig') sebagai gantinya
beta-closed
1
Saya mendapatkan "TypeError: diperlukan integer (mendapat tipe str)". Saya tidak mengerti apa yang kami lakukan di sini. Dapatkah seseorang tolong bantu? Saya perlu menambahkan string (paragraf) ke file teks. Apakah saya perlu mengubahnya menjadi integer terlebih dahulu sebelum menulis?
Mugen
@Mugen: Kode persis yang saya tulis berfungsi dengan baik sejauh yang saya bisa lihat. Saya sarankan Anda mengajukan pertanyaan baru menunjukkan persis kode apa yang Anda miliki, dan di mana kesalahan terjadi.
Jon Skeet
@ Mugen Anda perlu menelepon codecs.openbukan hanyaopen
northben
179

Baca yang berikut: http://docs.python.org/library/codecs.html#module-encodings.utf_8_sig

Melakukan hal ini

with codecs.open("test_output", "w", "utf-8-sig") as temp:
    temp.write("hi mom\n")
    temp.write(u"This has ♭")

File yang dihasilkan adalah UTF-8 dengan BOM yang diharapkan.

S.Lott
sumber
2
Terima kasih. Itu berhasil (Windows 7 x64, Python 2.7.5 x64). Solusi ini berfungsi dengan baik ketika Anda membuka file dalam mode "a" (tambahkan).
Mohamad Fakih
Ini tidak berhasil untuk saya, Python 3 di Windows. Saya harus melakukan ini sebagai gantinya dengan membuka (nama_file, 'wb') sebagai bomfile: bomfile.write (codecs.BOM_UTF8) kemudian membuka kembali file untuk ditambahkan.
Dustin Andrews
Mungkin menambahkan temp.close()?
user2905353
2
@ user2905353: tidak diperlukan; ini ditangani oleh manajemen konteks dari open.
matheburg
11

@ S-Lott memberikan prosedur yang tepat, tetapi memperluas masalah Unicode , juru bahasa Python dapat memberikan lebih banyak wawasan.

Jon Skeet benar (tidak biasa) tentang codecsmodul - ini berisi string byte:

>>> import codecs
>>> codecs.BOM
'\xff\xfe'
>>> codecs.BOM_UTF8
'\xef\xbb\xbf'
>>> 

Memilih nit lain, yang BOMmemiliki nama Unicode standar , dan itu dapat dimasukkan sebagai:

>>> bom= u"\N{ZERO WIDTH NO-BREAK SPACE}"
>>> bom
u'\ufeff'

Ini juga dapat diakses melalui unicodedata:

>>> import unicodedata
>>> unicodedata.lookup('ZERO WIDTH NO-BREAK SPACE')
u'\ufeff'
>>> 
Gimel
sumber
8

Saya menggunakan perintah file * nix untuk mengonversi file charset yang tidak dikenal dalam file utf-8

# -*- 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
1
Gunakan # coding: utf8alih-alih # -*- coding: utf-8 -*-yang jauh lebih mudah diingat.
show0k