2011-12-11 31 views
2

我必須編寫一個腳本,支持讀取可以保存爲Unicode或Ansi的文件(使用MS的記事本)。如何讀取可以保存爲Python或Unicode的文件?

我沒有任何關於文件中編碼格式的指示,我該如何支持兩種編碼格式? (一種通過閱讀文件而不知道高級格式的通用方式)。

+0

您正在使用哪個Python版本? 2.x和3.x以不同方式處理Unicode。 – FakeRainBrigand

+0

對於unicode,您可以使用UTF-16文件上的ByteOrderMark(BOM)來表明它實際上是unicode,以及字節的順序。常規「ansi」(ascii,我假設?)極不可能啓動用這樣的標記。 –

+0

我正在使用Python 2.7 – YSY

回答

11

MS記事本給出4個編碼選擇,在笨拙令人混淆的術語所表示的用戶:

「的Unicode」 是UTF-16,寫入小端。 「Unicode大端」是UTF-16,寫成big-endian。在兩種UTF-16情況下,這意味着將編寫適當的BOM。使用utf-16解碼這樣的文件。

「UTF-8」是UTF-8;記事本明確寫入「UTF-8 BOM」。使用utf-8-sig來解碼這樣的文件。

「ANSI」是令人震驚的。這是MS的術語,「任何默認的遺留編碼在這臺計算機上」。

這裏是他們使用的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。如果沒有,猜測。

請小心使用codecs.open()來讀取Windows上的文件。該文檔說:「」「注意 即使沒有指定二進制模式,文件也始終以二進制模式打開,這樣做是爲了避免由於使用8位值的編碼導致數據丟失,這意味着不會自動轉換'\ n'在閱讀和寫作上完成。「」「這意味着您的線路將以\r\n結束,您將需要/希望將這些線路剝離。

全部放在一起:

示例文本文件中,保存了所有4點編碼的選擇,看起來像這樣在記事本中:

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(aarrgghh !!);見下文

>>> 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是基於非拉丁文字檢測編碼很好(中國/日本/韓國,西里爾文,希伯來文,希臘文),但不能在基於拉丁語的編碼多好(西方/中歐/東歐,土耳其,越南),並且根本不會用阿拉伯語。

3

記事本使用字節順序標記保存Unicode文件。這意味着該文件的第一個字節將是:

  • EF BB BF - UTF-8
  • FF FE - 「統一」(實際上是UTF-16小端,貌似)
  • FE FF - 「的Unicode大端」(看起來像UTF-16大尾數)
可能會或可能不會有相同的行爲,但如果你肯定知道記事本正在使用

其他文本編輯器,這會給你一個體面的啓發式自動選擇編碼。但是,所有這些序列在ANSI編碼中都是有效的,因此這種啓發式算法可能會犯錯誤。無法保證使用正確的編碼。

+0

+1指出試圖通過查找BOM來檢測編碼僅僅是一種啓發式。請注意,Unicode編碼委員會不建議您以這種方式猜測編碼。材料清單僅用於指示已知編碼中的字節排序。即使只是區分UTF-32,UTF-16和UTF-8,也不推薦使用它們。 – bames53

+1

@ bames53。您的評論似乎與Unicode [Byte Order Mark FAQ](http://www.unicode.org/faq/utf_bom.html#BOM)中給出的建議相反 - 特別是「Q:BOM在哪裏有用?」部分。 。當然_any_編碼簽名總是可以關於文件的真實編碼。但對於大多數實際用途而言,BOM是一個合理可靠的指標。 – ekhumoro

+0

@ bames53:哪些物料清單是不相關的。問題是,鑑於記事本的已知行爲,以及文件實際上由記事本創建的知識,讀取此類文件的最佳策略是什麼。這種策略涉及啓發式的事實應該不言而喻。 –

相關問題