想用Python创建UTF-8文件,却得到了ANSI文件。为什么?

3 投票
3 回答
5751 浏览
提问于 2025-04-17 05:54

我有一个这样的函数:

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 个回答

0

如果你在查看一个文件时看到一些“乱码”的字符,那你需要确保你用来查看这个文件的工具能够识别这个文件是用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编码的可能性。

1

试着用带有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",那么当然需要进行相应的更改。)

3

当你用 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 你想要的。

撰写回答