如何在Python中读取可以保存为ANSI或Unicode的文件?
我需要写一个脚本,能够读取一个文件,这个文件可以保存为Unicode格式或Ansi格式(用微软的记事本)。
我不知道这个文件的编码格式是什么,我该怎么做才能同时支持这两种格式呢?(也就是说,我想要一种通用的方法来读取文件,而不需要提前知道它的格式)。
2 个回答
记事本在保存Unicode文件时,会加上一个字节顺序标记。这意味着文件的开头会有一些特定的字节:
- EF BB BF -- 这是UTF-8格式的标记
- FF FE -- 这是“Unicode”的标记(实际上是UTF-16小端格式,看起来是这样的)
- FE FF -- 这是“Unicode大端”的标记(看起来是UTF-16大端格式)
其他文本编辑器可能会有不同的行为,但如果你确定使用的是记事本,这些标记可以帮助你自动选择文件的编码方式。不过,这些字节序列在ANSI编码中也是有效的,所以这个方法有时可能会出错。不能保证一定会使用正确的编码。
MS记事本让用户选择四种编码方式,但这些术语听起来有点复杂,让人困惑:
“Unicode”其实是UTF-16格式,采用小端字节序。所谓“小端”,就是数据的低位字节在前,高位字节在后。“Unicode big endian”则是UTF-16格式,采用大端字节序,正好相反。在这两种UTF-16的情况下,文件会写入适当的字节顺序标记(BOM)。如果要读取这样的文件,可以用utf-16
。
“UTF-8”就是UTF-8格式;记事本会明确写入一个“UTF-8 BOM”。读取这样的文件时,可以用utf-8-sig
。
至于“ANSI”,这个词可能让人惊讶。它是微软用来表示“这个电脑上默认的旧版编码是什么”的术语。
下面是我知道的一些Windows编码,以及它们适用于哪些语言或脚本:
cp874 Thai
cp932 Japanese
cp936 Unified Chinese (P.R. China, Singapore)
cp949 Korean
cp950 Traditional Chinese (Taiwan, Hong Kong, Macao(?))
cp1250 Central and Eastern Europe
cp1251 Cyrillic ( Belarusian, Bulgarian, Macedonian, Russian, Serbian, Ukrainian)
cp1252 Western European languages
cp1253 Greek
cp1254 Turkish
cp1255 Hebrew
cp1256 Arabic script
cp1257 Baltic languages
cp1258 Vietnamese
cp???? languages/scripts of India
如果文件是在读取它的电脑上创建的,可以通过locale.getpreferredencoding()
获取“ANSI”编码。如果你知道文件的来源,可以指定使用的编码,如果不是UTF-16的话。如果都不知道,那就猜吧。
在Windows上使用codecs.open()
读取文件时要小心。文档中提到:“注意,文件总是以二进制模式打开,即使没有指定二进制模式。这是为了避免由于编码使用8位值而导致的数据丢失。这意味着在读取和写入时不会自动转换换行符‘\n’。”这就意味着你的行会以\r\n
结尾,你需要去掉这些。
把这些信息整合在一起:
一个示例文本文件,使用所有四种编码方式保存,在记事本中看起来是这样的:
The quick brown fox jumped over the lazy dogs.
àáâãäå
这里有一些演示代码:
import locale
def guess_notepad_encoding(filepath, default_ansi_encoding=None):
with open(filepath, 'rb') as f:
data = f.read(3)
if data[:2] in ('\xff\xfe', '\xfe\xff'):
return 'utf-16'
if data == u''.encode('utf-8-sig'):
return 'utf-8-sig'
# presumably "ANSI"
return default_ansi_encoding or locale.getpreferredencoding()
if __name__ == "__main__":
import sys, glob, codecs
defenc = sys.argv[1]
for fpath in glob.glob(sys.argv[2]):
print
print (fpath, defenc)
with open(fpath, 'rb') as f:
print "raw:", repr(f.read())
enc = guess_notepad_encoding(fpath, defenc)
print "guessed encoding:", enc
with codecs.open(fpath, 'r', enc) as f:
for lino, line in enumerate(f, 1):
print lino, repr(line)
print lino, repr(line.rstrip('\r\n'))
在Windows的“命令提示符”窗口中运行命令\python27\python read_notepad.py "" t1-*.txt
时,输出结果如下:
('t1-ansi.txt', '')
raw: 'The quick brown fox jumped over the lazy dogs.\r\n\xe0\xe1\xe2\xe3\xe4\xe5
\r\n'
guessed encoding: cp1252
1 u'The quick brown fox jumped over the lazy dogs.\r\n'
1 u'The quick brown fox jumped over the lazy dogs.'
2 u'\xe0\xe1\xe2\xe3\xe4\xe5\r\n'
2 u'\xe0\xe1\xe2\xe3\xe4\xe5'
('t1-u8.txt', '')
raw: '\xef\xbb\xbfThe quick brown fox jumped over the lazy dogs.\r\n\xc3\xa0\xc3
\xa1\xc3\xa2\xc3\xa3\xc3\xa4\xc3\xa5\r\n'
guessed encoding: utf-8-sig
1 u'The quick brown fox jumped over the lazy dogs.\r\n'
1 u'The quick brown fox jumped over the lazy dogs.'
2 u'\xe0\xe1\xe2\xe3\xe4\xe5\r\n'
2 u'\xe0\xe1\xe2\xe3\xe4\xe5'
('t1-uc.txt', '')
raw: '\xff\xfeT\x00h\x00e\x00 \x00q\x00u\x00i\x00c\x00k\x00 \x00b\x00r\x00o\x00w
\x00n\x00 \x00f\x00o\x00x\x00 \x00j\x00u\x00m\x00p\x00e\x00d\x00 \x00o\x00v\x00e
\x00r\x00 \x00t\x00h\x00e\x00 \x00l\x00a\x00z\x00y\x00 \x00d\x00o\x00g\x00s\x00.
\x00\r\x00\n\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\r\x00\n\x00'
guessed encoding: utf-16
1 u'The quick brown fox jumped over the lazy dogs.\r\n'
1 u'The quick brown fox jumped over the lazy dogs.'
2 u'\xe0\xe1\xe2\xe3\xe4\xe5\r\n'
2 u'\xe0\xe1\xe2\xe3\xe4\xe5'
('t1-ucb.txt', '')
raw: '\xfe\xff\x00T\x00h\x00e\x00 \x00q\x00u\x00i\x00c\x00k\x00 \x00b\x00r\x00o\
x00w\x00n\x00 \x00f\x00o\x00x\x00 \x00j\x00u\x00m\x00p\x00e\x00d\x00 \x00o\x00v\
x00e\x00r\x00 \x00t\x00h\x00e\x00 \x00l\x00a\x00z\x00y\x00 \x00d\x00o\x00g\x00s\
x00.\x00\r\x00\n\x00\xe0\x00\xe1\x00\xe2\x00\xe3\x00\xe4\x00\xe5\x00\r\x00\n'
guessed encoding: utf-16
1 u'The quick brown fox jumped over the lazy dogs.\r\n'
1 u'The quick brown fox jumped over the lazy dogs.'
2 u'\xe0\xe1\xe2\xe3\xe4\xe5\r\n'
2 u'\xe0\xe1\xe2\xe3\xe4\xe5'
需要注意的事项:
(1) “mbcs”是一种文件系统的伪编码,和解码文件的内容完全无关。在默认编码为cp1252
的系统上,它的表现就像latin1
(真让人抓狂!);见下文
>>> all_bytes = "".join(map(chr, range(256)))
>>> u1 = all_bytes.decode('cp1252', 'replace')
>>> u2 = all_bytes.decode('mbcs', 'replace')
>>> u1 == u2
False
>>> [(i, u1[i], u2[i]) for i in xrange(256) if u1[i] != u2[i]]
[(129, u'\ufffd', u'\x81'), (141, u'\ufffd', u'\x8d'), (143, u'\ufffd', u'\x8f')
, (144, u'\ufffd', u'\x90'), (157, u'\ufffd', u'\x9d')]
>>>
(2) chardet
在检测非拉丁脚本(如中文、日文、韩文、斯拉夫文、希伯来文、希腊文)的编码方面非常出色,但在拉丁编码(西欧、中欧、东欧、土耳其、越南)方面就不太行,阿拉伯文更是完全不懂。