1. 程式人生 > >Python中GBK, UTF-8和Unicode的編碼問題

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

https://www.cnblogs.com/jxzheng/p/5186490.html

編碼問題,一直是使用python2時的一塊心病。幾乎所有的控制檯輸入輸出、IO操作和HTTP操作都會涉及如下的編碼問題:

UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xc4 in position 10: ordinal not in range(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-8
s = "你好" # 整個檔案是UTF-8編碼,所以這裡的字串也是UTF-8
u = s.decode("utf-8") # 將utf-8的str轉換為unicode
g = u.encode('GBK') # 將unicode轉換為str,編碼為GBK
print type(s), "len=", len(s) 
# 輸出: len= 6,utf-8每個漢字佔3位元組
print type(u), "len=", len(u) 
# 輸出: len= 6,unicode統計的是字數
print type(g), "len=", len(g) 
# 輸出:g = u.encode('GBK'),GBK每個漢字佔2位元組
print s 
# 在GBK/ANSI環境下(如Windows),輸出亂碼,
#因為此時螢幕輸出會被強制理解為GBK;Linux下顯示正常
print g 
# 在Windows下輸出“你好”,
#Linux(UTF-8環境)下報錯,原因同上。

在Windows7(中文)下執行結果如下:

 len= 6
 len= 2
 len= 4
浣犲ソ
你好
Traceback (most recent call last):
  File "C:/Users/Sunicy/Desktop/encode.py", line 15, in 
    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) or isinstance(s, unicode)

不過這個方法有效,但是有點傻。既然str和unicode都派生自basestring,那麼實際上以basestring作為型別是最穩妥的:

isinstance(s, basestring)

下面是一組例子:

isinstance("aaa", str) # -> True
isinstance({}, dict) # -> True
isinstance([1,], list) # -> True
isinstance("aaa", list) # -> False
isinstance("你", str) # -> False
isinstance("你好", basestring) # -> True
isinstance("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是否為字串。

原文地址:Python中GBK, UTF-8和Unicode的編碼問題, 感謝原作者分享。