Đọc và ghi Unicode (UTF-8) vào các tệp bằng Python


328

Tôi đang bị suy não khi đọc và viết văn bản vào một tệp (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'

Vì vậy, tôi gõ Capit\xc3\xa1nvào trình soạn thảo yêu thích của tôi, trong tệp f2.

Sau đó:

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

Tôi không hiểu gì ở đây? Rõ ràng có một số phép thuật quan trọng (hoặc ý nghĩa tốt) mà tôi đang thiếu. Điều gì làm một loại vào các tập tin văn bản để có được chuyển đổi thích hợp?

Điều tôi thực sự thất bại ở đây, đó là điểm của đại diện UTF-8 là gì, nếu bạn thực sự không thể khiến Python nhận ra nó, khi nó đến từ bên ngoài. Có lẽ tôi chỉ nên kết xuất chuỗi JSON và sử dụng chuỗi đó thay vào đó, vì nó có đại diện đáng tin cậy! Hơn nữa, có một đại diện ASCII của đối tượng Unicode này mà Python sẽ nhận ra và giải mã, khi đến từ một tệp không? Nếu vậy, làm thế nào để tôi có được nó?

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

Câu trả lời:


110

Trong ký hiệu

u'Capit\xe1n\n'

"\ xe1" chỉ đại diện cho một byte. "\ x" cho bạn biết rằng "e1" ở dạng thập lục phân. Khi bạn viết

Capit\xc3\xa1n

vào tập tin của bạn, bạn có "\ xc3" trong đó. Đó là 4 byte và trong mã của bạn, bạn đọc tất cả. Bạn có thể thấy điều này khi bạn hiển thị chúng:

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

Bạn có thể thấy dấu gạch chéo ngược được thoát bằng dấu gạch chéo ngược. Vì vậy, bạn có bốn byte trong chuỗi của mình: "\", "x", "c" và "3".

Biên tập:

Như những người khác đã chỉ ra trong câu trả lời của họ, bạn chỉ cần nhập các ký tự trong trình chỉnh sửa và sau đó trình soạn thảo của bạn sẽ xử lý việc chuyển đổi sang UTF-8 và lưu nó.

Nếu bạn thực sự có một chuỗi ở định dạng này, bạn có thể sử dụng string_escapecodec để giải mã nó thành một chuỗi bình thường:

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

Kết quả là một chuỗi được mã hóa bằng UTF-8 trong đó ký tự có dấu được biểu thị bằng hai byte được ghi \\xc3\\xa1trong chuỗi gốc. Nếu bạn muốn có một chuỗi unicode, bạn phải giải mã lại bằng UTF-8.

Để chỉnh sửa của bạn: bạn không có UTF-8 trong tệp của mình. Để thực sự thấy nó trông như thế nào:

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

So sánh nội dung của tệp utf-8.outvới nội dung của tệp bạn đã lưu với trình chỉnh sửa của mình.


Vì vậy, điểm của định dạng được mã hóa utf-8 là gì nếu python có thể đọc trong các tệp bằng cách sử dụng nó? Nói cách khác, có đại diện ascii nào mà python sẽ đọc trong \ xc3 dưới dạng 1 byte không?
Gregg Lind

4
Câu trả lời cho câu hỏi "Vậy, điểm quan trọng" của bạn là "Mu." (vì Python có thể đọc các tệp được mã hóa trong UTF-8). Đối với câu hỏi thứ hai của bạn: \ xc3 không phải là một phần của bộ ASCII. Có lẽ bạn có nghĩa là "mã hóa 8 bit" thay vào đó. Bạn đang bối rối về Unicode và mã hóa; không sao đâu
tzot

8
Hãy thử đọc phần này dưới dạng mồi: joelonsoftware.com/articles/Unicode.html
tzot

lưu ý: u'\xe1'là một loại tiền mã Unicode U+00e1có thể được biểu diễn bằng 1 hoặc nhiều byte tùy theo mã hóa ký tự (nó là 2 byte trong utf-8). b'\xe1'là một byte (một số 225), những gì thư nếu có nó có thể đại diện phụ thuộc vào mã hóa ký tự dùng để giải mã nó ví dụ, đó là б( U+0431) trong CP1251, с( U+0441) trong cp866 vv
JFS

11
Thật đáng ngạc nhiên khi nhiều lập trình viên người Anh nói "chỉ sử dụng ascii" và sau đó không nhận ra rằng dấu £ không phải là nó. Hầu hết đều không biết rằng ascii! = Trang mã địa phương (tức là latin1).
Daniel Staple

710

Thay vì lộn xộn với các phương thức mã hóa và giải mã, tôi thấy việc chỉ định mã hóa dễ dàng hơn khi mở tệp. Các iomô-đun (được thêm vào trong Python 2.6) cung cấp một io.openchức năng, trong đó có một tham số mã hóa.

Sử dụng phương pháp mở từ iomô-đun.

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

Sau khi gọi hàm read () của f, một đối tượng Unicode được mã hóa được trả về.

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

Lưu ý rằng trong Python 3, io.openhàm là bí danh cho openhàm tích hợp. Hàm mở tích hợp chỉ hỗ trợ đối số mã hóa trong Python 3, không phải Python 2.

Chỉnh sửa: Trước đây câu trả lời này đã đề xuất mô-đun codec . Các mô-đun codec có thể gây ra vấn đề khi trộn read()readline() , vì vậy câu trả lời này ngay bây giờ đề nghị các io mô-đun để thay thế.

Sử dụng phương pháp mở từ mô-đun codec.

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

Sau khi gọi hàm read () của f, một đối tượng Unicode được mã hóa được trả về.

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

Nếu bạn biết mã hóa một tập tin, sử dụng gói codec sẽ ít gây nhầm lẫn hơn.

Xem http://docs.python.org/l Library / codecs.html # codecs.open


74
Hoạt động hoàn hảo cho các văn bản file quá, thay vì open(file,'w')làm codecs.open(file,'w','utf-8')giải quyết
Matt Connolly

1
Đây là câu trả lời tôi đang tìm kiếm :)
Justin

6
Liệu codecs.open(...)phương pháp này cũng hoàn toàn phù hợp với with open(...):phong cách, nơi withquan tâm về việc đóng tệp sau khi tất cả được thực hiện? Dường như nó vẫn hoạt động.
thử-bắt-cuối-

2
@ thử-bắt-cuối cùng Có. Tôi sử dụng with codecs.open(...) as f:tất cả thời gian.
Tim Swast

6
Tôi ước tôi có thể nâng cao điều này hàng trăm lần. Sau khi đau đớn trong vài ngày về các vấn đề mã hóa gây ra bởi nhiều dữ liệu hỗn hợp và đọc lướt qua về mã hóa, câu trả lời này giống như nước trong sa mạc. Ước gì tôi nhìn thấy nó sớm hơn.
Mike Girard

45

Bây giờ tất cả những gì bạn cần trong Python3 là open(Filename, 'r', encoding='utf-8')

[Chỉnh sửa vào ngày 2016 / 02-10 để làm rõ yêu cầu]

Python3 đã thêm tham số mã hóa vào hàm mở của nó. Thông tin sau đây về chức năng mở được thu thập từ đây: https://docs.python.org/3/l Library / fiances.html # open

open(file, mode='r', buffering=-1, 
      encoding=None, errors=None, newline=None, 
      closefd=True, opener=None)

Mã hóa là tên của mã hóa được sử dụng để giải mã hoặc mã hóa tệp. Điều này chỉ nên được sử dụng trong chế độ văn bản. Mã hóa mặc định phụ thuộc vào nền tảng (bất cứ thứ gì trả về locale.getpreferredencoding () ), nhưng bất kỳ mã hóa văn bản nào được Python hỗ trợ đều có thể được sử dụng. Xem mô-đun codec để biết danh sách các mã hóa được hỗ trợ.

Vì vậy, bằng cách thêm encoding='utf-8'làm tham số cho hàm mở, việc đọc và ghi tệp hoàn toàn được thực hiện dưới dạng utf8 (hiện cũng là mã hóa mặc định của mọi thứ được thực hiện trong Python.)


Bạn có thể vui lòng giải thích thêm câu trả lời của bạn thêm một chút mô tả về giải pháp bạn cung cấp không?
abarisone

2
Có vẻ như điều này có sẵn trong python 2 bằng cách sử dụng mô-đun codec - codecs.open('somefile', encoding='utf-8') stackoverflow.com/a/147756/149428
Taylor Edmiston

18

Vì vậy, tôi đã tìm thấy một giải pháp cho những gì tôi đang tìm kiếm, đó là:

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

Có một số codec bất thường hữu ích ở đây. Cách đọc đặc biệt này cho phép một người lấy các biểu diễn UTF-8 từ bên trong Python, sao chép chúng vào tệp ASCII và để chúng được đọc thành Unicode. Theo giải mã "thoát chuỗi", các dấu gạch chéo sẽ không được nhân đôi.

Điều này cho phép loại chuyến đi khứ hồi mà tôi đang tưởng tượng.


1
Phản hồi tốt, tôi đã được thử nghiệm cả hai giải pháp (codecs.open(file,"r","utf-8")và đơn giản open(file,"r").read().decode("utf-8")và cả hai đều hoạt động hoàn hảo.
bàng

Tôi đang nhận được một "TypeError: đối tượng str, byte hoặc os.PathLike dự kiến, không phải là _io.TextIOWrapper" tại sao?
JinSnow

Tôi nghĩ rằng, xem xét số lượng upvote, sẽ là một ý tưởng tuyệt vời để chấp nhận câu trả lời thứ hai :)
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()

14

Trên thực tế, điều này làm việc cho tôi khi đọc một tệp có mã hóa UTF-8 trong Python 3.2:

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

6

Để đọc một chuỗi Unicode và sau đó gửi tới HTML, tôi đã làm điều này:

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

Hữu ích cho các máy chủ http hỗ trợ python.


6

Bạn đã vấp phải vấn đề chung với mã hóa: Làm thế nào tôi có thể biết mã hóa tệp là gì?

Trả lời: Bạn không thể trừ khi định dạng tệp cung cấp cho việc này. XML, ví dụ, bắt đầu bằng:

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

Tiêu đề này đã được chọn cẩn thận để nó có thể được đọc bất kể mã hóa. Trong trường hợp của bạn, không có gợi ý nào như vậy, do đó, cả trình soạn thảo và Python của bạn đều không biết chuyện gì đang xảy ra. Do đó, bạn phải sử dụng codecsmô-đun và sử dụng codecs.open(path,mode,encoding)cung cấp bit bị thiếu trong Python.

Đối với trình chỉnh sửa của bạn, bạn phải kiểm tra xem nó có cung cấp một số cách để đặt mã hóa của tệp không.

Quan điểm của UTF-8 là có thể mã hóa các ký tự 21 bit (Unicode) dưới dạng luồng dữ liệu 8 bit (vì đó là điều duy nhất mà tất cả các máy tính trên thế giới có thể xử lý). Nhưng vì hầu hết các hệ điều hành trước thời đại Unicode, chúng không có các công cụ phù hợp để đính kèm thông tin mã hóa vào các tệp trên đĩa cứng.

Vấn đề tiếp theo là sự đại diện trong Python. Điều này được giải thích hoàn hảo trong bình luận của heikogerlach . Bạn phải hiểu rằng bảng điều khiển của bạn chỉ có thể hiển thị ASCII. Để hiển thị Unicode hoặc bất cứ thứ gì> = charcode 128, nó phải sử dụng một số phương tiện để thoát. Trong trình chỉnh sửa của bạn, bạn không được nhập chuỗi hiển thị đã thoát nhưng chuỗi đó có nghĩa là gì (trong trường hợp này, bạn phải nhập ô và lưu tệp).

Điều đó nói rằng, bạn có thể sử dụng hàm Python eval () để biến một chuỗi thoát thành chuỗi:

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

Như bạn có thể thấy, chuỗi "\ xc3" đã được biến thành một ký tự. Đây là một chuỗi 8 bit, được mã hóa UTF-8. Để có được Unicode:

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

Gregg Lind hỏi: Tôi nghĩ có một số phần bị thiếu ở đây: tệp f2 chứa: hex:

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

codecs.open('f2','rb', 'utf-8'), ví dụ, đọc tất cả chúng trong một ký tự riêng (dự kiến) Có cách nào để ghi vào một tệp trong ASCII sẽ hoạt động không?

Trả lời: Điều đó phụ thuộc vào ý của bạn. ASCII không thể biểu thị các ký tự> 127. Vì vậy, bạn cần một số cách để nói "một vài ký tự tiếp theo có nghĩa là một cái gì đó đặc biệt", đó là những gì chuỗi "\ x" làm. Nó nói: Hai ký tự tiếp theo là mã của một ký tự. "\ u" thực hiện tương tự bằng cách sử dụng bốn ký tự để mã hóa Unicode lên đến 0xFFFF (65535).

Vì vậy, bạn không thể trực tiếp viết Unicode sang ASCII (vì ASCII đơn giản là không chứa cùng các ký tự). Bạn có thể viết nó dưới dạng chuỗi thoát (như trong f2); trong trường hợp này, tệp có thể được biểu diễn dưới dạng ASCII. Hoặc bạn có thể viết nó dưới dạng UTF-8, trong trường hợp đó, bạn cần một luồng an toàn 8 bit.

Giải pháp sử dụng của bạn decode('string-escape')không hoạt động, nhưng bạn phải nhận thức được bạn sử dụng bao nhiêu bộ nhớ: Ba lần lượng sử dụng codecs.open().

Hãy nhớ rằng một tệp chỉ là một chuỗi byte có 8 bit. Cả bit và byte đều không có nghĩa. Chính bạn là người nói "65 có nghĩa là" A ". Vì \xc3\xa1sẽ trở thành "à" nhưng máy tính không có phương tiện để biết, bạn phải nói với nó bằng cách chỉ định mã hóa được sử dụng khi ghi tệp.


Tôi nghĩ rằng có một số phần bị thiếu ở đây: tệp f2 chứa: hex: 0000000: 4361 7069 745c 7863 335c 7861 316e 0a Capit \ xc3 \ xa1n. codecs.open ('f2', 'rb', 'utf-8'), ví dụ, đọc tất cả chúng trong một ký tự riêng (dự kiến) Có cách nào để ghi vào tệp trong ascii sẽ hoạt động không?
Gregg Lind

6

ngoại trừ codecs.open(), người ta có thể sử dụng io.open()để làm việc với Python2 hoặc Python3 để đọc / ghi tệp unicode

thí dụ

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


Có, sử dụng io là tốt hơn; Nhưng tôi đã viết câu lệnh như thế này with io.open('data.txt', 'w', 'utf-8') as file:và gặp lỗi : TypeError: an integer is required. Sau khi tôi đổi thành with io.open('data.txt', 'w', encoding='utf-8') as file:và nó hoạt động.
Evan Hu

5

Chà, trình soạn thảo văn bản yêu thích của bạn không nhận ra rằng nó \xc3\xa1được coi là chữ nhân vật, nhưng nó diễn giải chúng thành văn bản. Đó là lý do tại sao bạn nhận được dấu gạch chéo kép ở dòng cuối cùng - giờ đây là dấu gạch chéo ngược thực sự + xc3, v.v. trong tệp của bạn.

Nếu bạn muốn đọc và ghi các tệp được mã hóa bằng Python, tốt nhất nên sử dụng mô-đun codec .

Việc dán văn bản giữa thiết bị đầu cuối và các ứng dụng rất khó khăn, vì bạn không biết chương trình nào sẽ diễn giải văn bản của bạn bằng cách sử dụng mã hóa nào. Bạn có thể thử như sau:

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

Sau đó dán chuỗi này vào trình soạn thảo của bạn và đảm bảo rằng nó lưu chuỗi đó bằng Latin-1. Theo giả định rằng bảng tạm không cắt xén chuỗi, chuyến đi khứ hồi sẽ hoạt động.


4

Chuỗi \ x .. là một cái gì đó đặc trưng cho Python. Đây không phải là một chuỗi thoát byte phổ quát.

Cách bạn thực sự nhập vào UTF-8 được mã hóa không phải ASCII tùy thuộc vào HĐH và / hoặc trình soạn thảo của bạn. Đây là cách bạn làm điều đó trong Windows . Đối với OS X để nhập một với dấu sắc bạn chỉ có thể nhấn option+ E, sau đó A, và hầu như tất cả soạn thảo văn bản trong OS X hỗ trợ UTF-8.


3

Bạn cũng có thể cải thiện open()chức năng ban đầu để làm việc với các tệp Unicode bằng cách thay thế nó tại chỗ, sử dụng partialchức năng. Cái hay của giải pháp này là bạn không cần thay đổi bất kỳ mã cũ nào. Nó trong suốt.

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

1

Tôi đã cố phân tích iCal bằng Python 2.7.9:

từ Lịch nhập icalWiki

Nhưng tôi đã nhận được:

 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)

và nó đã được sửa chỉ với:

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

(Bây giờ nó có thể in likeé á böss.)


0

Tôi đã tìm thấy cách tiếp cận đơn giản nhất bằng cách thay đổi mã hóa mặc định của toàn bộ tập lệnh thành 'UTF-8':

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

bất kỳ open, printhoặc tuyên bố khác sẽ chỉ sử dụng utf8.

Hoạt động ít nhất cho Python 2.7.9.

Thx truy cập https://markhneedham.com/blog/2015/05/21/python-unicodeencodeerror-ascii-codec-cant-encode-character-uxfc-in-poseition-11-ordinal-not-in-range128/ ( nhìn vào cuối).

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.