這哥們的這段話說的太對了,搞Python不把編碼完全搞明白,總有一天它會猝不及防坑你一把。
不過感受這哥們的答案並沒把編碼問題寫明白,因此只好親自動筆了。
折騰編碼問題,有不少次,我覺得自已明白了,最終發現,那隻不過是自圓其說而已,這一次,終於100%肯定,動筆即再也不改!
看這篇文章前,你應該已經知道了爲何有編碼,以及編碼的種類狀況python
因爲每一個國家都有本身的字符,因此其對應關係也涵蓋了本身國家的字符,可是以上編碼都存在侷限性,即:僅涵蓋本國字符,無其餘國家字符的對應關係。應運而生出現了萬國碼,他涵蓋了全球全部的文字和二進制的對應關係。程序員
Unicode 起到了2個做用:編程
Unicode解決了字符和二進制的對應關係,可是使用unicode表示一個字符,太浪費空間。例如:利用unicode表示「Python」須要12個字節才能表示,比原來ASCII表示增長了1倍。
因爲計算機的內存比較大,而且字符串在內容中表示時也不會特別大,因此內容可使用unicode來處理,可是存儲和網絡傳輸時通常數據都會很是多,那麼增長1倍將是沒法容忍的!!!
爲了解決存儲和網絡傳輸的問題,出現了Unicode Transformation Format,學術名UTF,即:對unicode中的進行轉換,以便於在存儲和網絡傳輸時能夠節省空間!windows
總結:UTF 是爲unicode編碼 設計 的一種 在存儲 和傳輸時節省空間的編碼方案。網絡
不管以什麼編碼在內存裏顯示字符,存到硬盤上都是2進制。編程語言
ascii編碼(美國): l 0b1101100 o 0b1101111 v 0b1110110 e 0b1100101 GBK編碼(中國): 老 0b11000000 0b11001111 男 0b11000100 0b11010000 孩 0b10111010 0b10100010 Shift_JIS編碼(日本): 私 0b10001110 0b10000100 は 0b10000010 0b11001101 ks_c_5601-1987編碼(韓國): 나 0b10110011 0b10101010 는 0b10110100 0b11000010 TIS-620編碼(泰國): ฉัน 0b10101001 0b11010001 0b10111001 ...
要注意的是,存到硬盤上時是以何種編碼存的,再從硬盤上讀出來時,就必須以何種編碼讀,要否則就亂了。。
雖然國際語言是英語 ,但你們在本身的國家依然說自已的語言,不過出了國, 你就得會英語 編碼也同樣,雖然有了unicode and utf-8 ,可是因爲歷史問題,各個國家依然在大量使用本身的編碼,好比中國的windows,默認編碼依然是gbk,而不是utf-8。
基於此,若是中國的軟件出口到美國,在美國人的電腦上就會顯示亂碼,由於他們沒有gbk編碼。
若想讓中國的軟件能夠正常的在 美國人的電腦上顯示,只有如下2條路可走:學習
第1種方法幾乎不可能實現,第2種方法比較簡單。 可是也只能是針對新開發的軟件。 若是你以前開發的軟件就是以gbk編碼的,上百萬行代碼可能已經寫出去了,從新編碼成utf-8格式也會費很大力氣。編碼
so , 針對已經用gbk開發完畢的項目,以上2種方案都不能輕鬆的讓項目在美國人電腦上正常顯示,難道沒有別的辦法了麼?操作系統
有, 還記得咱們講unicode其中一個功能是其包含了跟全球全部國家編碼的映射關係,意思就是,你寫的是gbk的「路飛學城」,可是unicode能自動知道它在unicode中的「路飛學城」的編碼是什麼,若是這樣的話,那是否是意味着,不管你以什麼編碼存儲的數據,只要你的軟件在把數據從硬盤讀到內存裏,轉成unicode來顯示,就能夠了。因爲全部的系統、編程語言都默認支持unicode,那你的gbk軟件放到美國電腦上,加載到內存裏,變成了unicode,中文就能夠正常展現啦。
設計
這個表你本身也能夠下載下來
unicode與gbk的映射表 http://www.unicode.org/charts/
在看實際代碼的例子前,咱們來聊聊,python3 執行代碼的過程
實際代碼演示,在py3上 把你的代碼以utf-8編寫, 保存,而後在windows上執行。
s = '路飛學城' print(s)
so ,一切都很美好,到這裏,咱們關於編碼的學習按說就能夠結束了。
可是,如生活同樣,美好的表面下,老是隱藏着不盡如人意,上面的utf-8編碼之因此能在windows gbk的終端下顯示正常,是由於到了內存裏python解釋器把utf-8轉成了unicode , 可是這只是python3, 並非全部的編程語言在內存裏默認編碼都是unicode,好比 萬惡的python2 就不是, 它的默認編碼是ASCII,想寫中文,就必須聲明文件頭的coding爲gbk or utf-8, 聲明以後,python2解釋器僅以文件頭聲明的編碼去解釋你的代碼,加載到內存後,並不會主動幫你轉爲unicode,也就是說,你的文件編碼是utf-8,加載到內存裏,你的變量字符串就也是utf-8, 這意味着什麼你知道麼?。。。意味着,你以utf-8編碼的文件,在windows是亂碼。
亂是正常的,不亂纔不正常,由於只有2種狀況 ,你的windows上顯示纔不會亂。
既然Python2並不會自動的把文件編碼轉爲unicode存在內存裏, 那就只能使出最後一招了,你本身人肉轉。Py3 自動把文件編碼轉爲unicode一定是調用了什麼方法,這個方法就是,decode(解碼) 和encode(編碼)
UTF-8 --> decode 解碼 --> Unicode Unicode --> encode 編碼 --> GBK / UTF-8 ..
decode示例
encode 示例
記住下圖規則
unicode字符是有專門的unicode類型來判斷的,可是utf-8,gbk編碼的字符都是str,你若是分辨出來的當前的字符串數據是何種編碼的呢? 有人說能夠經過字節長度判斷,由於utf-8一箇中文佔3字節,gbk一個佔2字節。
靠上面字節個數,雖然也能大致判斷是什麼類型,但總以爲不是很專業。
怎麼才能精確的驗證一個字符的編碼呢,就是拿這些16進制的數跟編碼表裏去匹配。
「路飛學城」的unicode編碼的映射位置是 u'\u8def\u98de\u5b66\u57ce' ,‘\u8def’ 就是‘路’,到表裏搜一下。
「路飛學城」對應的GBK編碼是'\xc2\xb7\xb7\xc9\xd1\xa7\xb3\xc7' ,2個字節一箇中文,"路" 的二進制 "\xc2\xb7"是4個16進制,正好2字節,拿它到unicode映射表裏對一下, 發現是G0-4237,並非\xc2\xb7呀。。。擦。演砸了吧。。
再查下「飛」 \u98de ,對應的是G0-3749, 跟\xb7\xc9也對不上。
雖然對不上, 但好\xc2\xb7 和G0-4237中的第2位的2和第4位的7對上了,「飛」字也是同樣,莫非巧合?
把他們都轉成2進制顯示試試
路 C 2 8 4 2 1 8 4 2 1 <strong>1 1 0 0 0 0 1 0</strong> B 7 8 4 2 1 8 4 2 1 <strong>1 0 1 1 0 1 1 1</strong> 飛 B 7 8 4 2 1 8 4 2 1 1 0 1 1 0 1 1 1 C 9 8 4 2 1 8 4 2 1 1 1 0 0 1 0 0 1
這個「路」仍是跟G0-4237對不上呀,沒錯, 但若是你把路\xc2\xb7的每一個二進制字節的左邊第一個bit變成0試試呢, 我擦,加起來就真的是4237了呀。。難道又是巧合???
必然不是,是由於,GBK的編碼表示形式決定的。。由於GBK編碼在設計初期就考慮到了要兼容ASCII,即若是是英文,就用一個字節表示,2個字節就是中文,但如何區別連在一塊兒的2個字節是表明2個英文字母,仍是一箇中文漢字呢? 中國人如此聰明,決定,2個字節連在一塊兒,若是每一個字節的第1位(也就是至關於128的那個2進制位)若是是1,就表明這是個中文,這個首位是128的字節被稱爲高字節。 也就是2個高字節連在一塊兒,必然就是一箇中文。 你怎麼如此篤定?由於0-127已經表示了英文的絕大部分字符,128-255是ASCII的擴展表,表示的都是極特殊的字符,通常沒什麼用。因此中國人就直接拿來用了。
問:那爲何上面 "\xc2\xb7"的2進制要把128所在的位去掉才能與unicode編碼表裏的G0-4237匹配上呢?
這隻能說是unicode在映射表的表達上直接忽略了高字節,但真正映射的時候 ,確定仍是須要用高字節的哈。
在python 2 上寫字符串
>>> s = "路飛" >>> print s 路飛 >>> s '\xe8\xb7\xaf\xe9\xa3\x9e'
雖然說打印的是路飛,但直接調用變量s,看到的倒是一個個的16進製表示的二進制字節,咱們怎麼稱呼這樣的數據呢?直接叫二進制麼?也能夠, 但相比於010101,這個數據串在表示形式上又把2進制轉成了16進制來表示,這是爲何呢? 哈,爲的就是讓人們看起來更可讀。咱們稱之爲bytes類型,即字節類型, 它把8個二進制一組稱爲一個byte,用16進制來表示。
說這個有什麼意思呢?
想告訴你一個事實, 就是,python2的字符串其實更應該稱爲字節串。 經過存儲方式就能看出來, 但python2裏還有一個類型是bytes呀,難道又叫bytes又叫字符串? 嗯 ,是的,在python2裏,bytes == str , 其實就是一回事。
除此以外呢, python2裏還有個單獨的類型是unicode , 把字符串解碼後,就會變成unicode
>>> s '\xe8\xb7\xaf\xe9\xa3\x9e' #utf-8 >>> s.decode('utf-8') u'\u8def\u98de' #unicode 在unicode編碼表裏對應的位置 >>> print(s.decode('utf-8')) 路飛 #unicode 格式的字符
因爲Python創始人在開發初期認知的侷限性,其並未預料到python能發展成一個全球流行的語言,致使其開發初期並無把支持全球各國語言當作重要的事情來作,因此就輕佻的把ASCII當作了默認編碼。 當後來你們對支持漢字、日文、法語等語言的呼聲愈來愈高時,Python因而準備引入unicode,但若直接把默認編碼改爲unicode的話是不現實的, 由於不少軟件就是基於以前的默認編碼ASCII開發的,編碼一換,那些軟件的編碼就都亂了。因此Python 2 就直接 搞了一個新的字符類型,就叫unicode類型,好比你想讓你的中文在全球全部電腦上正常顯示,在內存裏就得把字符串存成unicode類型。
>>> s = "路飛" >>> s '\xe8\xb7\xaf\xe9\xa3\x9e' >>> s2 = s.decode("utf-8") >>> s2 u'\u8def\u98de' >>> type(s2) <type 'unicode'>
時間來到2008年,python發展已近20年,創始人龜叔愈來愈以爲python裏的好多東西已發展的不像他的初衷那樣,開始變得臃腫、不簡潔、且有些設計讓人摸不到頭腦,好比unicode 與str類型,str 與bytes類型的關係,這給不少python程序員形成了困擾。
龜叔再也忍不了,像以前同樣的修修補補已不能讓Python變的更好,因而來了個大變革,Python3橫空出世,不兼容python2,python3比python2作了很是多的改進,其中一個就是終於把字符串變成了unicode,文件默認編碼變成了utf-8,這意味着,只要用python3,不管你的程序是以哪一種編碼開發的,均可以在全球各國電腦上正常顯示,真是太棒啦!
PY3 除了把字符串的編碼改爲了unicode, 還把str 和bytes 作了明確區分, str 就是unicode格式的字符, bytes就是單純二進制啦。
最後一個問題,爲何在py3裏,把unicode編碼後,字符串就變成了bytes格式? 你直接給我直接打印成gbk的字符展現很差麼?我想其實py3的設計真是煞費苦心,就是想經過這樣的方式明確的告訴你,想在py3裏看字符,必須得是unicode編碼,其它編碼一概按bytes格式展現。
好吧,就說這麼多吧。
最後再提示一下,Python只要出現各類編碼問題,無非是哪裏的編碼設置出錯了
常見編碼錯誤的緣由有: