Ghi vào tệp UTF-8 bằng Python


204

Tôi thực sự bối rối với codecs.open function. Khi tôi làm:

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

Nó cho tôi lỗi

UnicodeDecodeError: 'ascii' codec không thể giải mã byte 0xef ở vị trí 0: thứ tự không nằm trong phạm vi (128)

Nếu tôi làm:

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

Nó hoạt động tốt.

Câu hỏi là tại sao phương pháp đầu tiên thất bại? Và làm thế nào để tôi chèn bom?

Nếu phương pháp thứ hai là cách làm đúng, thì điểm sử dụng là codecs.open(filename, "w", "utf-8")gì?


54
Không sử dụng BOM trong UTF-8. Xin vui lòng.
tchrist

7
@tchrist Hả? Tại sao không?
Salman von Abbas

8
@SalmanPK BOM không cần thiết trong UTF-8 và chỉ thêm độ phức tạp (ví dụ: bạn không thể ghép các tệp BOM'd và kết quả bằng văn bản hợp lệ). Xem phần hỏi đáp này ; đừng bỏ lỡ bình luận lớn dưới Q
Alois Mahdal

Câu trả lời:


271

Tôi tin rằng vấn đề là codecs.BOM_UTF8chuỗi byte chứ không phải chuỗi Unicode. Tôi nghi ngờ trình xử lý tệp đang cố gắng đoán ý nghĩa thực sự của bạn dựa trên "Tôi có nghĩa là viết Unicode dưới dạng văn bản được mã hóa UTF-8, nhưng bạn đã cho tôi một chuỗi byte!"

Hãy thử viết chuỗi Unicode cho dấu thứ tự byte (tức là Unicode U + FEFF) trực tiếp, để tệp chỉ mã hóa dưới dạng UTF-8:

import codecs

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

(Điều đó dường như đưa ra câu trả lời đúng - một tệp có byte EF BB BF.)

EDIT: Đề xuất của S. Lott về việc sử dụng "utf-8-sig" vì mã hóa là một cách tốt hơn so với việc tự viết BOM một cách rõ ràng, nhưng tôi sẽ để lại câu trả lời này ở đây vì nó giải thích những gì đã sai trước đây.


Cảnh báo: mở và mở không giống nhau. Nếu bạn thực hiện "từ codec nhập mở", nó sẽ KHÔNG giống như bạn chỉ cần gõ "mở".
Apache

2
thay vào đó, bạn cũng có thể sử dụng codec.open ('test.txt', 'w', 'utf-8-sig')
đã đóng cửa beta vào

1
Tôi đang nhận được "TypeError: một số nguyên là bắt buộc (có loại str)". Tôi không hiểu những gì chúng ta đang làm ở đây. Ai đó có thể vui lòng giúp đỡ? Tôi cần nối thêm một chuỗi (đoạn) vào một tệp văn bản. Tôi có cần phải chuyển đổi nó thành một số nguyên trước khi viết không?
Mugen

@Mugen: Mã chính xác tôi đã viết hoạt động tốt như tôi có thể thấy. Tôi đề nghị bạn hỏi một câu hỏi mới hiển thị chính xác mã bạn đã nhận và lỗi xảy ra ở đâu.
Jon Skeet

@Mugen bạn cần gọi codecs.openthay vì chỉopen
phía bắc

179

Đọc phần sau: http://docs.python.org/l Library / cadecs.html#module-encodings.utf_8_sig

Làm cái này

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

Tệp kết quả là UTF-8 với BOM dự kiến.


2
Cảm ơn. Điều đó đã làm việc (Windows 7 x64, Python 2.7.5 x64). Giải pháp này hoạt động tốt khi bạn mở tệp ở chế độ "a" (chắp thêm).
Mohamad Fakih

Điều này không làm việc cho tôi, Python 3 trên Windows. Tôi đã phải làm điều này thay vì mở (file_name, 'wb') là bomfile: bomfile.write (codecs.BOM_UTF8) sau đó mở lại tệp để chắp thêm.
Dustin Andrew

Có thể thêm temp.close()?
dùng2905353

2
@ user2905353: không bắt buộc; điều này được xử lý bởi quản lý bối cảnh của open.
matheburg

11

@ S-Lott đưa ra quy trình đúng, nhưng mở rộng về các vấn đề Unicode , trình thông dịch Python có thể cung cấp nhiều thông tin chi tiết hơn.

Jon Skeet nói đúng (không bình thường) về codecsmô-đun - nó chứa các chuỗi byte:

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

Chọn một nit khác, BOMcó tên Unicode tiêu chuẩn và có thể được nhập dưới dạng:

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

Nó cũng có thể truy cập thông qua unicodedata:

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

8

Tôi sử dụng lệnh file * nix để chuyển đổi tệp ký tự không xác định trong tệp 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()

1
Sử dụng # coding: utf8thay vì # -*- coding: utf-8 -*-đó là dễ nhớ hơn nhiều.
show0k
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.