在正式說明之前,先給大家一個參考資料:戳這裡
文章的內容參考了這篇資料,並加以總結,為了避免我總結的不夠完善,或者說出現什麼錯誤的地方,有疑問的地方大家可以看看上面那篇文章。
下面開始講python中的編碼問題,首先,我們看看編碼有哪些。
1. ASCII
ASCII是用一個字節表示字符,而一個字節由八位二進制組成,所以能產生2**8=256種變化,在計算機剛誕生的年代,用來表示大小寫的26個英文字母,外加一些符號之類的還是綽綽有余的。這也是python2.x中默認使用的編碼,所以在python2.x中默認不能使用中文,除非使用編碼聲明。
2. MBCS
隨著時代的發展,ASCII就太夠用了,計算機不能只顯示英文吧,那樣實在太low。此時,大家看到ASCII碼中還有沒用完的,所以都想占用剩下的部分,但是剩下的部分還是不夠,例如我們中文那麼多肯定是不夠用的,所以此時又擴展了一下,一個字節不行,我就用兩個。而又為了兼容ASCII碼,有定義了這樣一套規則:如果第一個字節是\x80以下,則仍然表示ASCII字符;而如果是\x80以上,則跟下一個字節一起(共兩個字節)表示一個字符,然後跳過下一個字節,繼續往下判斷。例如GB...和BIG...之類的都遵循這樣的規則。
但是,這樣還是實在太亂了,此時IBM跳了出來,大喊一聲:這些東西都要統一進行管理!!所以弄出了代碼頁的概念,將這些字符集都收錄了起來,並進行了分頁,而這些分頁的總稱就叫MBCS,例如GBK在936頁,所以又叫cp936。而大家都是使用的雙字節,所以也稱為DBCS。
但很明顯,MBCS裡面收集和各樣的字符集,但是你不能說你要使用MBCS這個字符集編碼,裡面存了怎麼多種,到底是要用哪種,你不說清楚我總不能隨機給你一種吧。所以必須要進行指定,但是這個工作已經由操作系統自己完成了(linux不支持),而操作系統有時根據地區的不同而選擇的。例如簡體中文版的,就選GBK,其他國家的又會有不同,具體按版本而定。所以,一旦在python的編碼聲明中使用MBCS/BDCS,在進行過系統或跨地區運行的時候,報錯也是在所難免的。所以編碼聲明中一定要具體的指定,例如我們常用的utf-8,這樣就不會因為系統和地區的差異而造成各種編碼的錯誤。
在windows中,微軟又為它起了個別名,叫ANSI,其實就是MBSC,大家知道就好了。
3.Unicode
雖然MBSC一定程度上解決了編碼混亂的問題,但還是特點的編碼只能顯示特點的字符。這樣要開發一種適配多國語言的程序就變得非常困難,此時人們在想,有沒有一種編碼能搞到所以的字符。大家研究了一番之後,Unicode就此誕生。干脆大家都不要在ASCII上拓展來拓展去,搞得各種版本如此混亂。以後大家都用兩個字節保存算了,這樣就有了256*256=65536種字符可以表示了,總歸是夠用了吧。這就是UCS-2標准了。後來還有人說不夠用的,那麼干脆翻一倍,用四個字節表示,256**4=4294967296,就算將來表示外星文字也能撐一段時間了吧。當然現在常用的還是UCS-2標准的。
UCS(Unicode Character Set)還僅僅是字符對應碼位的一張表而已(也就是表示字節),比如"漢"這個字的碼位是6C49。字符具體如何傳輸和儲存則是由UTF(UCS Transformation Format)來負責(也就是保存字節)。(注意:表示字節≠保存字節,也就是雖然我用了2個字節表示字符,但是我保存的時候不一定就直接保存用來表示的那個字節)
剛開始都是直接使用UCS的碼位來保存,這就是UTF-16,比如,"漢"直接使用\x6C\x49保存(UTF-16-BE),或是倒過來使用\x49\x6C保存(UTF-16-LE)。但美國佬後來不願意了,我原來用ASCII只有1個字節就能搞到,現在卻要兩個字節,足足長了一倍呀。一倍是什麼概念,四捨五入那是將近一個億呀。真當我磁盤空間不用錢呀,為了滿足這個述求,就誕生了UTF-8。
UTF-8是一種很別扭的編碼,具體表現在他是變長的,並且兼容ASCII,ASCII字符使用1字節表示。但有得必有失,在UTF-8中,東亞的文字是用三個字節表示的,包括中文,一些不常用的字符更是用四個字節表示。於是別的國家保存的成本變高了,而美國佬卻變低了。又再次坑了別人,滿足了自己。但是沒辦法,誰叫人家是計算機界的老大呢?
什麼是BOM
當一個文本編輯要打開一個文件時,它表示懵逼了。世間編碼如此之多,我究竟要用什麼哪種編碼去解碼呀?你總得告訴我吧!
此時,UTF就進入了BOM來表示編碼。所謂的BOM就是文件使用編碼的標識符,就和python的編碼聲明一樣,告訴文本編輯器我用的是什麼編碼,下面的你都用那個編碼去解碼就行。
同樣的,只有文本編輯器在文件開頭的地方讀到了關於BOM的描述,就能夠進行正確的界面了。
下面是一些BOM的總結:
BOM_UTF8 '\xef\xbb\xbf'
BOM_UTF16_LE '\xff\xfe'
BOM_UTF16_BE '\xfe\xff'
同樣了,為了我們自己編輯的文件的編碼也能被正確識別,我們也要寫入BOM,一般由編輯器完成。但不寫也可以,只有在打開文件的時候自己手動選擇用什麼去解碼也是可以的。
但是,還有一種叫UTF-8無BOM模式的,這又是什麼鬼。
因為UTF-8實在太流行了,所以文本編輯器默認會首先用UTF-8進行解碼。即使是保存時默認使用ANSI(MBCS)的記事本,在讀取文件時也是先使用UTF-8測試編碼,如果可以成功解碼,則使用UTF-8解碼。記事本這個別扭的做法造成了一個BUG:如果你新建文本文件並輸入"姹塧"然後使用ANSI(MBCS)保存,再打開就會變成"漢a"。)
下用一幅圖來總結:
此時,有些人會在MBCS和UCS-2之間迷糊,大家都是兩個字節表示,又有什麼不同?
MBCS是各自拓展的,有就是說很可能相同的二進制表示MBCS會出現不同的結果,而Unicode是統一拓展,保證了每種二進制表示都對應唯一一個字符,保證了唯一性,也就提高了兼容能力。
ok,在講完字符編碼的問題之後,現在再來看一下:
# coding:gbk 和 # coding= utf-8 之類的編碼聲明對python而言到底意味著什麼。
這裡插播一個小技巧:
# coding : utf-8 或者這樣 # coding = utf-8 的聲明方式是會報錯的,這裡並不是說是特點的=或者:的問題,而是空格的問題,在coding和符號之間是不能有空格的,但在符號和utf-8之類的編碼名稱間是運行0個或多個空格的,#和coding間也是運行0個或多個空格的。我也不知道為什麼,但實際就是報錯了。
#! /usr/bin/env python #coding = utf-8 print '中文'
這裡coding和=號一個空格:
報錯了。
#! /usr/bin/env python #coding= utf-8 print '中文'
coding和=之間沒空格:
正常執行。
不清楚是我IDE的問題還是python本來的語法是這樣規定的,但其實很少有地方談及這個地方的語法,所以這裡提及一下,最後自己實驗一下。
# -*- coding: utf-8 -*- 的寫法也是一樣的。
好了,一下進入正題:
#! /usr/bin/env python # coding= utf-8 print '中文' print str('中文') print repr('中文') print repr(u'中文')
這裡順便解釋一下str()函數和repr()函數在創建或轉換字符串上的區別,str()得到是一個對人類可讀性比較好的字符串,而print也是默認調用這個函數的。而repr()是創建或轉換字符串時,得到是對機器可讀性更好的字符串,就是這裡得到是其編碼。
下面是另一種編碼的輸出:
#! /usr/bin/env python # coding= gbk print '中文' print str('中文') print repr('中文') print repr(u'中文')
前面兩個是亂碼,不過這裡是我IDE的問題,我的IDE默認是用UTF-8的。
這裡改成GBK再試試。
又可以了。
這裡再次引入一個問題,什麼是文件保存編碼,什麼是代碼運行編碼。
還是不能,那再改一下。
這樣又可以了。
其實保存編碼,也就是IDE Encoding設置的是在磁盤中保存和打開的時候的編碼。假設我使用的是windows的文本編輯器寫的代碼,也就是用ANSI保存的,如何我再用其他編碼打開就會出現亂碼,這無關運行的事。而運行編碼是則是影響python交互界面的編碼,我在前面的python的第一個程序中也說過這個問題,我用windows的cmd運行一個python文件,為什麼我在python中聲明了可以使用中文的utf-8,卻在輸出顯示的時候還是出現了亂碼?這是因為雖然python輸出的utf-8的編碼,但是cmd這個家伙默認是用GBK去解碼的,所以出現了顯示上的亂碼。但這並不是真正的亂碼,換用一個支持utf-8的顯示環境就可以了。
好,題外話多了些,但這些小的錯誤可能會讓人糾結很久都解決不了,所以這裡還是提及一下的好。
下面進入正題,看下面的現象:
utf-8:
gbk:
我們可以發現,隨著python編碼聲明的不同,其字符串的編碼也不同,所以我們得出一個結論:
python中的編碼聲明影響的是普通字符串的編碼,也是就用工程函數str()或者單純的引號創建的出來的字符串。是隨著編碼聲明的不同而不同的。
此時再看一下這個現象:
發現Unicode字符串無論在上面情況下都是一樣的,因為它永遠采用的是Unicode進行編碼,不管你聲明的到底是什麼。
Unicode字符串的創建,也就是Unicode對象的創建可以使用工廠函數unicode()或者在引號前面加個u。
所以,我們可以得出一個在python進行編碼轉換的規則:
再總體擴展一下:
只要python支持的字符集,都能夠用這樣的邏輯在python的內部進行編碼轉換。
只要知道了這些,那麼在文件處理中就能避免許多錯誤了,關於python的文件處理我們下篇再講。