Python中GBK, UTF-8和Unicode的編碼問題


編碼問題,一直是使用python2時的一塊心病。幾乎所有的控制台輸入輸出、IO操作和HTTP操作都會涉及如下的編碼問題:
UnicodeDecodeError:asciicodec cant 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個字符仍舊用一個字節表示,但“翔”這樣的中文就用兩個字節表示:
翔的GBK表示

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中字符串的本質

blog-python-encoding-string

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

總結

  1. unicode是支持所有文字的統一編碼,但一般只用作文字的內部表示,文件、網頁(也是文件)、屏幕輸入輸出等處均需使用具體的外在編碼,如GBK、UTF-8等;
  2. encode和decode都是針對unicode進行“編碼”和“解碼”,所以encode是unicode->str的過程,decode是str->unicode的過程;
  3. unicode和str是一對孿生兄弟,來自basestring,所以用isinstance(s, basestring)來判斷s是否為字符串。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM