標籤:
編碼問題,一直是使用python2時的一塊心病。幾乎所有的控制台輸入輸出、IO操作和HTTP操作都會涉及如下的編碼問題:
UnicodeDecodeError:‘ascii’codec can’t decodebyte0xc4inposition10:ordinalnotinrange(128)
這究竟是是個什麼東西?!有時稀裡糊塗地用一坨encode(),decode()之類的函數讓程式能跑對了,可是下次遇到非ASCII編碼時又悲劇了。
那麼Python 2.x中的字串究竟是個什麼呢?
基本編碼知識
在瞭解Python中字串(String)的本質前,我們需要知道ASCII、GBK、UTF-8和Unicode的關係究竟幾何。
我們知道,任何字串都是一串二進位位元組的序列,而ASCII碼是最經典的編碼方式,它將序列中的每個位元組理解為一個字元,可表示阿拉伯數字、字母在內的128個不同字元。很明顯,漢字在ascii中是無法表示的。
為了讓電腦能夠顯示、處理漢字,勤勞樸實的中國人民制定了GBK(GB2312的擴充)編碼,這是一種相容ASCII的不定長(長度為1-2)編碼,對於基本的128個字元仍舊用一個位元組表示,但“翔”這樣的中文就用兩個位元組表示:
UTF-8與GBK類似,也是一種相容ASCII碼的不定長編碼形式,它的長度變化更大,因此可以表示幾乎所有世界文字。具體細節可參考維基:http://zh.wikipedia.org/wiki/UTF-8
Unicode是一種定長的編碼方式(同ASCII),不過它是每2位元組認為是一個字元,如ASCII中0x61表示‘a‘,在Unicode中用0x0061表示‘a‘,它可映射所有文字,而且對於多種寫法的字,如強/強,它都可以唯一地區分它們。
由於Unicode編碼的字串體積很大,因此一般來說Unicode編碼只是文字在記憶體中的內在形式,具體的儲存(如檔案、網頁等)都需要靠外在的編碼(UTF-8、GBK等)詮釋。
Python2.x中字串的本質
Python中實際上有兩種字串,分別是str類型和unicode類型,這兩者都是basestring的衍生類別。它們的區別如下:
字串類型 |
常量子串表示 |
記憶體中表示 |
len() |
len含義 |
str |
S=“呵呵” |
與源碼檔案完全一致,一坨二進位編碼 |
若源碼檔案為UTF-8編碼, len(S)=6 |
位元組數 |
unicode |
S=u“呵呵” |
Unicode編碼 |
len(S)=2 |
字數 |
str類型的本質就是一坨二進位串,源檔案(或擷取的網頁)的編碼是怎樣,它就跟著是怎樣。實際上Python並不清楚某個str字串到底是什麼編碼。這也就解釋了為什麼我們需要在python檔案的開頭標定該檔案的編碼是什麼,如:
# encoding: utf-8
也解釋了為什麼len()一個str類型的字串,只會返回它在記憶體中佔用的位元組數,而非文字數。
相比於str,unicode是真正的字串。Python明確地知道它的編碼,所以可以很自信地獲得一個字串的實際字數。
字串編碼轉換:encode()和decode()
Python最常用的編碼轉換函式是encode()和decode(),他們的本質是:unicode和str的互相轉換。
具體而言:
encode(encoding): 將unicode轉換為str,並使用encoding編碼;
decode(encoding):將str轉換為unicode,其中str以encoding編碼。
我們來看一個例子:
#encoding: utf-8s="你好"# 整個檔案是UTF-8編碼,所以這裡的字串也是UTF-8u=s.decode("utf-8")# 將utf-8的str轉換為unicodeg=u.encode(‘GBK‘)# 將unicode轉換為str,編碼為GBKprinttype(s),"len=",len(s)# 輸出:<type ‘str‘> len= 6,utf-8每個漢字佔3位元組printtype(u),"len=",len(u)# 輸出:<type ‘str‘> len= 6,unicode統計的是字數printtype(g),"len=",len(g)# 輸出:g = u.encode(‘GBK‘),GBK每個漢字佔2位元組prints# 在GBK/ANSI環境下(如Windows),輸出亂碼,#因為此時螢幕輸出會被強制理解為GBK;Linux下顯示正常printg# 在Windows下輸出“你好”,#Linux(UTF-8環境)下報錯,原因同上。
在Windows7(中文)下運行結果如下:
<type‘str‘>len= 6<type‘unicode‘>len= 2<type‘str‘>len= 4浣犲ソ你好Traceback (most recent call last): File "C:/Users/Sunicy/Desktop/encode.py", line 15, in<module>g.decode(‘utf-8‘) File "C:\Python27\lib\encodings\utf_8.py", line 16, in decode return codecs.utf_8_decode(input, errors, True)UnicodeDecodeError: ‘utf8‘ codec can‘t decode byte 0xc4 in position 0: invalid continuation byte
判斷變數是否為字串
我們知道Python中判斷一個變數是否為某個類型使用isinstance(變數, 類型)函數,如
isinstance(1.2,float)
傳回值為True
那麼判斷變數是不是字串能不能用
isinstance(s,str)
呢?
答案是否定的。
現在我們知道除了str之外,unicode類型也是字串,因此上述代碼如果遇到unicode字串,就返回False。
直觀地改進是既判斷str又判斷unicode:
isinstance(s,str)orisinstance(s,unicode)
不過這個方法有效,但是有點傻。既然str和unicode都派生自basestring,那麼實際上以basestring作為類型是最穩妥的:
isinstance(s,basestring)
下面是一組例子:
isinstance("aaa",str)# -> Trueisinstance({},dict)# -> Trueisinstance([1,],list)# -> Trueisinstance("aaa",list)# -> Falseisinstance("你",str)# -> Falseisinstance("你好",basestring)# -> Trueisinstance("aaa",basestring)# -> True
總結
- unicode是支援所有文字的統一編碼,但一般只用作文字的內部表示,檔案、網頁(也是檔案)、螢幕輸入輸出等處均需使用具體的外在編碼,如GBK、UTF-8等;
- encode和decode都是針對unicode進行“編碼”和“解碼”,所以encode是unicode->str的過程,decode是str->unicode的過程;
- unicode和str是一對孿生兄弟,來自basestring,所以用isinstance(s, basestring)來判斷s是否為字串。
Python中GBK, UTF-8和Unicode的編碼問題