標籤:
win、py、notepad++的編碼方式及問題先說結論:
因為win的cmd預設使用的編碼方式是gbk(ANSI) 所以遇到bat或者python中的中文需要在cmd中print顯示時,如果出現亂碼,首先需要檢查是否因為不是 1. gbk編碼的中文或2. 在代碼中被轉換為gbk編碼或3. 在python代碼中是unicode對象。
別在win下用內建的文字文件編輯器開啟utf-8編碼的檔案並儲存,因為win預設會在utf-8編碼的內容中加BOM,只是查看還沒問題因為不儲存不會自動加BOM,但是如果儲存的話就win就會自動的加BOM
注意notepad++的設定,即 設定->喜好設定->建立
中建立文檔的預設編碼方式
,推薦還是按照win預設的ANSI儲存,否則建立的utf-8文檔若被內建的文檔編輯器開啟,就會自動加BOM,導致一些未可知的運行錯誤
對於在win下用notepad++寫.py 雖然不會有被開啟編輯導致加BOM的問題,但是因為有時會需要在cmd中顯示,所以最好還是不要用utf-8編碼,用ANSI即gbk編碼吧
python代碼前面添加的 #!coding=utf-8
或者 #!coding=gbk
等表示編碼方式的含義是給python的解譯器看的,結論就是最好與文檔本身的編碼方式相吻合,對於win下面的python代碼,也就是最好是#!coding=gbk
ok,說完了上面的結論,開始說一下具體的細節。
寫這個文檔的原因是因為在notepad++寫python代碼時遇到的一個問題:
1 前情提要:
- 在notepad++中安裝了python Script外掛程式使得可以使用notepad++作為一個IDE編輯器。
- 因為之前寫的一些python代碼涉及到中文路徑,以及一些中文注釋,所以在notepad++中直接用了
#!coding=utf-8
,但是.py檔案本身的編碼方式還是預設的gbk。
- 之前在notepad++的設定中設定了
使用utf-8應用於開啟ANSI檔案
,這是個很奇怪的功能,就是說檔案本身編碼的方式還是gbk,但是notepad++開啟後先轉碼成了utf-8並顯示,然後在儲存的時候還是按原來的方式,即gbk的編碼儲存,引起了後面分析一系列的誤解,之後再詳細說明。
2 問題出現的情形:
- 本身這種“編碼關係錯亂”的python代碼直接在notepad++中編輯並使用Python Script運行或直接使用python的解譯器運行時是沒問題的,也就是說—-中文路徑可以識別,且能夠正確讀出路徑中的檔案等
- 將這種代碼使用pycharm開啟,在IDE中顯示代碼中的中文就變成了亂碼,同時,也無法運行了。
3 問題的分析:3.1 字元的編碼方式
* ASCII碼*
八個二進位位(bit)可以組合出256種狀態,這被稱為一個位元組(byte)。也就是說,一個位元組一共可以用來表示256種不同的狀態,每一個狀態對應一個符號,就是256個符號,從0000000到11111111。
ASCII碼一共規定了128個字元的編碼,比如空格”SPACE”是32(二進位00100000),大寫的字母A是65(二進位01000001)。這128個符號(包括32個不能列印出來的控制符號),只佔用了一個位元組的後面7位,最前面的1位統一規定為0。
非ASCII編碼
英語用128個符號編碼就夠了,但是用來表示其他語言,128個符號是不夠的。比如,在法語中,字母上方有注音符號,它就無法用ASCII碼錶示。於是,一些歐洲國家就決定,利用位元組中閑置的最高位編入新的符號。比如,法語中的é的編碼為130(二進位10000010)。這樣一來,這些歐洲國家使用的編碼體系,可以表示最多256個符號。 但是,這裡又出現了新的問題。不同的國家有不同的字母,因此,哪怕它們都使用256個符號的編碼方式,代表的字母卻不一樣。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (?),在俄語編碼中又會代表另一個符號。但是不管怎樣,所有這些編碼方式中,0–127表示的符號是一樣的,不一樣的只是128–255的這一段。
至於亞洲國家的文字,使用的符號就更多了,漢字就多達10萬左右。一個位元組只能表示256種符號,肯定是不夠的,就必須使用多個位元組表達一個符號。比如,簡體中文常見的編碼方式是GB2312,使用兩個位元組表示一個漢字,所以理論上最多可以表示256x256=65536個符號。 中文編碼的問題需要專文討論,這篇筆記不涉及。這裡只指出,雖然都是用多個位元組表示一個符號,但是GB類的漢字編碼與後文的Unicode和UTF-8是毫無關係的。
Unicode
正如上一節所說,世界上存在著多種編碼方式,同一個位元字可以被解釋成不同的符號。因此,要想開啟一個文字檔,就必須知道它的編碼方式,否則用錯誤的編碼方式解讀,就會出現亂碼。為什麼電子郵件常常出現亂碼?就是因為發信人和收信人使用的編碼方式不一樣。
可以想象,如果有一種編碼,將世界上所有的符號都納入其中。每一個符號都給予一個獨一無二的編碼,那麼亂碼問題就會消失。這就是Unicode,就像它的名字都表示的,這是一種所有符號的編碼。 Unicode當然是一個很大的集合,現在的規模可以容納100多萬個符號。每個符號的編碼都不一樣,比如,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,U+4E25表示漢字”嚴”。具體的符號對應表,可以查詢unicode.org,或者專門的漢字對應表。 需要注意的是,Unicode只是一個符號集,它只規定了符號的二進位代碼,卻沒有規定這個二進位代碼應該如何儲存。
比如,漢字”嚴”的unicode是十六進位數4E25,轉換成位元足足有15位(100111000100101),也就是說這個符號的表示至少需要2個位元組。表示其他更大的符號,可能需要3個位元組或者4個位元組,甚至更多。
這裡就有兩個嚴重的問題,第一個問題是,如何才能區別Unicode和ASCII?電腦怎麼知道三個位元組表示一個符號,而不是分別表示三個符號呢?第二個問題是,我們已經知道,英文字母只用一個位元組表示就夠了,如果Unicode統一規定,每個符號用三個或四個位元組表示,那麼每個英文字母前都必然有二到三個位元組是0,這對於儲存來說是極大的浪費,文字檔的大小會因此大出二三倍,這是無法接受的。 它們造成的結果是:1)出現了Unicode的多種儲存方式,也就是說有許多種不同的二進位格式,可以用來表示Unicode。2)Unicode在很長一段時間內無法推廣,直到互連網的出現。
UTF-8
互連網的普及,強烈要求出現一種統一的編碼方式。UTF-8就是在互連網上使用最廣的一種Unicode的實現方式。其他實現方式還包括UTF-16(字元用兩個位元組或四個位元組表示)和UTF-32(字元用四個位元組表示),不過在互連網上基本不用。重複一遍,這裡的關係是,UTF-8是Unicode的實現方式之一。 UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個位元組表示一個符號,根據不同的符號而變化位元組長度。 UTF-8的編碼規則很簡單,只有二條:
1. 對於單位元組的符號,位元組的第一位設為0,後面7位為這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。
2. 對於n位元組的符號(n>1),第一個位元組的前n位都設為1,第n+1位設為0,後面位元組的前兩位一律設為10。剩下的沒有提及的二進位位,全部為這個符號的unicode碼。
下表總結了編碼規則,字母x表示可用編碼的位。
5. Unicode符號範圍 | UTF-8編碼方式6. (十六進位) | (二進位)7. --------------------+---------------------------------------------8. 0000 0000-0000 007F | 0xxxxxxx9. 0000 0080-0000 07FF | 110xxxxx 10xxxxxx10. 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx11. 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
跟據上表,解讀UTF-8編碼非常簡單。如果一個位元組的第一位是0,則這個位元組單獨就是一個字元;如果第一位是1,則連續有多少個1,就表示當前字元佔用多少個位元組。
下面,還是以漢字”嚴”為例,示範如何?UTF-8編碼。 已知”嚴”的unicode是4E25(100111000100101),根據上表,可以發現4E25處在第三行的範圍內(0000 0800-0000 FFFF),因此”嚴”的UTF-8編碼需要三個位元組,即格式是”1110xxxx 10xxxxxx 10xxxxxx”。然後,從”嚴”的最後一個二進位位開始,依次從後向前填入格式中的x,多出的位補0。這樣就得到了,”嚴”的UTF-8編碼是”11100100 10111000 10100101”,轉換成十六進位就是E4B8A5。
win下的編碼與編碼格式轉換
通過上一節的例子,可以看到”嚴”的Unicode碼是4E25,UTF-8編碼是E4B8A5,兩者是不一樣的。它們之間的轉換可以通過win的記事本實現。
開啟檔案後,點擊”檔案”菜單中的”另存新檔”命令,會跳出一個對話方塊,在最底部有一個”編碼”的下拉條。
裡面有四個選項:ANSI,Unicode,Unicode big endian 和 UTF-8。
1. ANSI是預設的編碼方式。對於英文檔案是ASCII編碼,對於簡體中文檔案是GB2312編碼(只針對Windows簡體中文版,如果是繁體中文版會採用Big5碼)。
2. Unicode編碼指的是UCS-2編碼方式,即直接用兩個位元組存入字元的Unicode碼。這個選項用的little endian格式。(也就是說win下這樣轉換是little endian,但是從讀取順序來看這是反的,下面會有說明)
3. Unicode big endian編碼與上一個選項相對應。我在下一節會解釋little endian和big endian的涵義。
4. UTF-8編碼,也就是上一節談到的編碼方法。
5. 選擇完”編碼方式”後,點擊”儲存”按鈕,檔案的編碼方式就立刻轉換好了。
Little endian和Big endian 上一節已經提到,Unicode碼可以採用UCS-2格式直接儲存。以漢字”嚴”為例,Unicode碼是4E25,需要用兩個位元組儲存,一個位元組是4E,另一個位元組是25。儲存的時候,4E在前,25在後,就是Big endian方式;25在前,4E在後,就是Little endian方式。 因此,第一個位元組在前,就是”大頭方式”(Big endian),第二個位元組在前就是”小頭方式”(Little endian)。
那麼很自然的,就會出現一個問題:電腦怎麼知道某一個檔案到底採用哪一種方式編碼? Unicode規範中定義,每一個檔案的最前面分別加入一個表示編碼順序的字元,這個字元的名字叫做”零寬度非換行空格”(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。這正好是兩個位元組,而且FF比FE大1。
1. 如果一個文字檔的頭兩個位元組是FE FF,就表示該檔案採用大頭方式;
2. 如果頭兩個位元組是FF FE,就表示該檔案採用小頭方式。
試用”我”舉例:
**big edidon FE FF**我:\u 62 11o: \u 00 6F**small edidon FF FE**我:\u 11 62o: \u 6F 00**utf-8**我: E6 88 91o: 6FPS:對於win系統,是會出現bom標識符的,也就是EF BB BF**ANSI(對於win系統,就是預設的gbk或gb2312方式)**我:CE D2o:6F
ps:網上有一些這種文字編碼轉換的網站,但是網上的轉換因為瀏覽器的編碼問題 一般顯示時候很容易出錯,可以以這些測試
3.2 notepad++中字元的編碼
既然瞭解了這些編碼方式,如何而我的儲存為gbk的#!coding=utf-8
的文檔究竟是什麼格式呢,在這裡嘗試使用查看檔案二進位編碼的程式進行查看,然後就發現一個很奇怪的現象—-將檔案本身拖到二進位查看器,比如beyond compare中時,顯示的是gbk編碼(因為中文的”我”是CE D2 ,但在notepad++中用hex外掛程式卻發現顯示的是utf-8編碼。一開始以為是notepad++會自動根據檔案頭上#!coding=utf-8
調整字元的編碼方式,結果真相卻是在這個文章最前面所說的建立文檔的預設編碼方式
中選擇了使用utf-8開啟gbk編碼檔案
。
因此,在notepad++中所有編碼為gbk的檔案開啟後在顯示是都被轉換到utf-8的編碼,所以在notepad++中的hex外掛程式顯示檔案是utf-8的編碼以及在notepad++中點擊以ANSI顯示
會出現亂碼 但是退出的時候還是還原到gbk
解決方案:取消上面所說的這個選項,具體參見本文開頭
3.3 python中字元的編碼
在本節,以’哈’來解釋作樣本解釋所有的問題,”哈”的各種編碼如下:
UNICODE (UTF8-16): C854;UTF-8: E59388;GBK: B9FE。
3.3.1 python中的str和unicode
python中的str和unicode到底是一個什麼東西呢? 在python中提到unicode,一般指的是unicode對象,例如’哈哈’的unicode對象為u‘\u54c8\u54c8‘
而str,是一個位元組數組,這個位元組數組表示的是對unicode對象編碼(可以是utf-8、gbk、cp936、GB2312)後的儲存的格式。這裡它僅僅是一個位元組流,沒有其它的含義,如果你想使這個位元組流顯示的內容有意義,就必須用正確的編碼格式,解碼顯示。 例如:
對於unicode對象哈哈進行編碼,編碼成一個utf-8編碼的str—-s_utf8,s_utf8就是是一個位元組數組,存放的就是’\xe5\x93\x88\xe5\x93\x88’,但是這僅僅是一個位元組數組,但是如果將utf-8編碼的位元組數組直接print,就會顯示亂碼,為什麼?
因為print語句它的實現是將要輸出的內容傳送了作業系統,作業系統會根據系統的編碼對輸入的位元組流進行編碼,這就解釋了為什麼utf-8格式的字串”哈哈”,輸出的是”鍝堝搱”,因為 ‘\xe5\x93\x88\xe5\x93\x88’用GB2312去解釋,其顯示的出來就是”鍝堝搱”。
同時,因為str記錄的是位元組數組,只是某種編碼的儲存格式,至於輸出到檔案或是列印出來是什麼格式,完全取決於其解碼的編碼將它解碼成什麼樣子。這裡再對print進行一點補充說明:當將一個unicode對象傳給print時,在內部會將該unicode對象進行一次轉換,轉換成本地的預設編碼(也就是中直接輸出su編碼正常的原因)。
3.3.2 str和unicode對象的轉換
str和unicode對象的轉換,通過encode和decode實現,具體使用如下:
將GBK’哈哈’轉換成unicode,然後再轉換成UTF8
3.3.3 操作不同檔案的編碼格式的檔案
建立一個檔案test.txt,檔案格式用ANSI,內容為: “abc中文”
用python來讀取
# coding=gbkprint open("Test.txt").read()結果:abc中文把檔案格式改成UTF-8:結果:abc涓枃
顯然,如果檔案格式不是gbk,需要解碼:
# coding=gbkimport codecsprint open("Test.txt").read().decode("utf-8")結果:abc中文
上面的test.txt我是用Editplus來編輯的,但當我用Windows內建的記事本編輯並存成UTF-8格式時, 運行時報錯:
Traceback (most recent call last): File "ChineseTest.py", line 3, in print open("Test.txt").read().decode("utf-8") UnicodeEncodeError: ‘gbk‘ codec can‘t encode character u‘\ufeff‘ in position 0: illegal multibyte sequence
原來,某些軟體,如記事本,在儲存一個以UTF-8編碼的檔案時,會在檔案開始的地方插入三個不可見的字元(0xEF 0xBB 0xBF,即BOM)。 因此我們在讀取時需要自己去掉這些字元,python中的codecs module定義了這個常量:
# coding=gbkimport codecsdata = open("Test.txt").read()if data[:3] == codecs.BOM_UTF8: data = data[3:]print data.decode("utf-8")結果:abc中文
3.3.4檔案的編碼格式和編碼聲明的作用
源檔案的編碼格式和對字串的聲明有什麼作用呢?
先說檔案編碼格式:檔案的編碼格式決定了在該源檔案中字串的編碼格式
而編碼聲明的作用:即每個檔案在最上面的地方的# coding=gbk
,作用有三:
1. 聲明源檔案中將出現非ascii編碼,通常也就是中文;
2. 在進階的IDE中,IDE會將你的檔案格式儲存成你指定編碼格式。(如pycharm)
3. 決定源碼中類似於u’哈’這類聲明的將’哈’解碼成unicode所用的編碼格式,也是一個比較容易讓人迷惑的地方。
看樣本:
#coding:gbk ss = u‘哈哈‘ print repr(ss) print ‘ss:%s‘ % ss
將這個些代碼儲存成一個utf-8文本,運行,你認為會輸出什麼呢?大家第一感覺肯定輸出的肯定是:
u‘\u54c8\u54c8‘ ss:哈哈
但是實際上輸出是:
u‘\u935d\u581d\u6431‘ ss:鍝堝搱 ```為什麼會這樣,這時候,就是編碼聲明在作怪了,在運行ss = u‘哈哈‘的時候,整個過程可以分為以下幾步: 1. **擷取‘哈哈‘的編碼:由檔案編碼格式確定**,為‘\xe5\x93\x88\xe5\x93\x88‘(哈哈的utf-8編碼形式) 2. 轉成 unicode編碼的時候,在這個轉換的過程中,**對於‘\xe5\x93\x88\xe5\x93\x88‘的解碼,不是用utf-8解碼,而是用聲明編碼處指定的編碼GBK**,將‘\xe5\x93\x88\xe5\x93\x88‘按GBK解碼,得到就是‘‘鍝堝搱‘‘,這三個字的unicode編碼就是u‘\u935d\u581d\u6431‘,至止可以解釋為什麼print repr(ss)輸出的是u‘\u935d\u581d\u6431‘ 了。 好了,這裡有點繞,我們來分析下一個樣本:```python #!coding=utf-8 ss = u‘哈哈‘ print repr(ss) print ‘ss:%s‘ % ss<div class="se-preview-section-delimiter"></div>
將這個樣本這次儲存成GBK編碼形式,運行結果,竟然是:
UnicodeDecodeError: ‘utf8‘ codec can‘t decode byte 0xb9 in
position 0: unexpected code byte
這裡為什麼會有utf8解碼錯誤呢?想想上個樣本也明白了,
1. 轉換第一步,因為檔案編碼是GBK,得到的是’哈哈’編碼是GBK的編碼’\xb9\xfe\xb9\xfe’
- 轉換成 unicode的時候,會用UTF8對’\xb9\xfe\xb9\xfe’進行解碼,而大家查utf-8的編碼錶會發現,它在utf8編碼錶中根本不存在,所以會報上述錯誤。
因此,一切都清楚了—-
總結
- notepad++不會根據檔案頭上的
#!coding
來自動判斷或者改變檔案的編碼方式,這個#!coding
是給python的解譯器看的
2.
對於在notepad++中寫python輸入中文的問題:
- 如果是先在win中建立一個txt,然後直接改尾碼為py,用notepad++開啟,作為python的代碼進行編寫時, 因為此時note是ANSI編碼的,預設不識別中文,必須要加
#!coding=gbk
或者#!coding=utf-8
什麼的都可以,此時在cmd顯示端顯示的都不是亂碼
- 如果note已經改成了utf-8編碼的,python似乎可以直接識別,在前面不用加
#!coding
也可以,不過因為cmd顯示端是gbk的編碼,如果想要顯示不是亂碼,可以先.decode(‘utf-8’)轉化為unicode或者直接在中文前u’中文’
- pycharm開啟亂碼的問題就是因為之前在notepad++中聲明和編碼不一致,而pycharm會根據聲明來對文檔進行編碼,至此,還是推薦在win下面統一用gbk編碼吧
win、py、notepad++的編碼方式及問題