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編碼, |
字節數 |
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是否為字符串。