想用Python创建UTF-8文件,却得到了ANSI文件。为什么?
我有一个这样的函数:
def storeTaggedCorpus(corpus, filename):
corpusFile = codecs.open(filename, mode = 'w', encoding = 'utf-8')
for token in corpus:
tagged_token = '/'.join(str for str in token)
tagged_token = tagged_token.decode('ISO-8859-1')
tagged_token = tagged_token.encode('utf-8')
corpusFile.write(tagged_token)
corpusFile.write(u"\n")
corpusFile.close()
当我执行它时,出现了以下错误:
(...) in storeTaggedCorpus
corpusFile.write(tagged_token)
File "c:\Python26\lib\codecs.py", line 691, in write
return self.writer.write(data)
File "c:\Python26\lib\codecs.py", line 351, in write
data, consumed = self.encode(object, self.errors)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
于是我开始调试,发现创建的文件是用ANSI编码的,而不是我在corpusFile = codecs.open(filename, mode = 'w', encoding = 'utf-8')
中声明的UTF-8。如果我去掉corpusFile.write(tagged_token)
,这个函数就会(显然)正常工作,文件会用ANSI编码。如果我去掉tagged_token = tagged_token.encode('utf-8')
,它也会正常工作,但是生成的文件会有“ANSI作为UTF-8”的编码(???),而且拉丁字符会变得乱七八糟。因为我在分析葡萄牙语(巴西)的文本,这样是完全不可接受的。
我相信如果corpusFile
以UTF-8打开,一切都会正常,但我就是搞不定。我在网上搜索过,但找到的关于Python/Unicode的内容都和这个无关……所以为什么这个文件总是以ANSI结尾呢?我在Windows 7 x64上使用Python 2.6,这些文件编码是从Notepad++得来的。
编辑 — 关于corpus
参数
我不知道corpus
字符串的编码。它是通过PlaintextCorpusReader.tag()
方法生成的,来自NLTK。根据Notepad++,原始语料库文件是用UTF-8编码的。tagged_token.decode('ISO-8859-1')
只是个猜测。我试着用cp1252解码,结果得到了和ISO-8859-1一样的乱码。
3 个回答
如果你在查看一个文件时看到一些“乱码”的字符,那你需要确保你用来查看这个文件的工具能够识别这个文件是用UTF-8编码的。
下面这段代码创建的文件:
import codecs
for enc in "utf-8 utf-8-sig".split():
with codecs.open(enc + ".txt", mode = 'w', encoding = enc) as corpusFile:
tagged_token = '\xdcml\xe4ut'
tagged_token = tagged_token.decode('cp1252') # not 'ISO-8859-1'
corpusFile.write(tagged_token) # write unicode objects
corpusFile.write(u'\n')
在不同的软件中是这样识别的:
Notepad++(版本5.7(UNICODE)):UTF-8无BOM,UTF-8
Firefox(7.0.1):西方(ISO-8859-1),Unicode(UTF-8)
记事本(Windows 7):UTF-8,UTF-8
在你的UTF-8文件中加入一个BOM(字节顺序标记),虽然在Unix系统上不推荐这样做,但在Windows上,这样做能大大提高其他软件识别你文件为UTF-8编码的可能性。
试着用带有UTF-8签名(也叫BOM)的方式来写文件:
def storeTaggedCorpus(corpus, filename):
corpusFile = codecs.open(filename, mode = 'w', encoding = 'utf-8-sig')
for token in corpus:
tagged_token = '/'.join(str for str in token)
# print(type(tagged_token)); break
# tagged_token = tagged_token.decode('cp1252')
corpusFile.write(tagged_token)
corpusFile.write(u"\n")
corpusFile.close()
注意,这样做只有在tagged_token
是一个unicode字符串时才会正常工作。要检查这一点,可以取消注释上面代码中的第一条注释 - 这样会打印出<type 'unicode'>
。
如果tagged_token
不是unicode字符串,那么你需要先用第二条注释中的代码进行解码。(注意:我假设使用的是"cp1252"编码,但如果你确定是"iso-8859-1",那么当然需要进行相应的更改。)
当你用 codec.open('w', encoding='utf8')
打开文件时,其实没有必要把字节数组(str
对象)写入文件。相反,你应该写 unicode
对象,像这样:
corpusFile = codecs.open(filename, mode = 'w', encoding = 'utf-8')
# ...
tagged_token = '\xdcml\xe4ut'
tagged_token = tagged_token.decode('ISO-8859-1')
corpusFile.write(tagged_token)
corpusFile.write(u'\n')
这样做会写入与平台相关的行结束符。
另外,你也可以打开一个二进制文件,直接写入已经编码好的字符串的字节数组:
corpusFile = open(filename, mode = 'wb')
# ...
tagged_token = '\xdcml\xe4ut'
tagged_token = tagged_token.decode('ISO-8859-1')
corpusFile.write(tagged_token.encode('utf-8'))
corpusFile.write('\n')
这样做会写入与平台无关的行结束符。如果你想要一个 与平台相关的行结束符,可以打印 os.sep
而不是 '\n'
。
需要注意的是,Notepad++ 中的编码名称可能会让人误解:ANSI as UTF-8
是你想要的。