python2x的str/unicode轉換以及python3x中的str/bytes轉換

寫在開頭

爲什麼哪裏都會出現編碼問題,而編碼問題總是那麼難搞懂?我想在讀這篇博客前大家都應該深刻地瞭解下爲什麼會出現所謂的編碼問題?

Python2x中的str/unicode轉換

字符的十六進制表達
首先,我們在ULtraEdit中做個試驗
圖一
圖二
圖三
圖一:我們先用記事本保存了“中文”二字,然後以ANSI編碼格式保存後用ULtraEdit打開,再轉成十六進制編輯環境後就可以看到“中文”二字用ANSI編碼存儲的十六進制的表達,可以看到是4個字節。
圖二:是用Unicode BigEndian保存的”中文“二字的十六進制表達,即從”4E“到”87“對應的4個字節,前面的兩個字節是頭,不管他,有興趣的讀者可以自行度娘。
圖三:是用UTF-8編碼存儲的”中文“二字的十六進制表達,即從“E4“到”87“這6個字節,前三個字節是頭,不管他。
可以看到,用不同的編碼保存同樣的字符,底層就會有不同的十六進制表達,ANSI底層是用兩個字節存儲一箇中文字符的,Unicode也是用兩個字節存儲一箇中文字符的,而UTF-8編碼是用三個字節存儲一箇中文字符的。
Python2x中Str&Unicode
接下來我們來討論Python2x中的Str和Unicode。
首先我打開Python2.7的編譯環境。輸入以下代碼:

>>>a="中文"
>>>a
'\xd6\xd0\xce\xc4'
>>>type(a)
<type 'str'>

>>>b=a.decode('gbk')
>>>b
u'\u4e2d\u6587'
>>>type(b)
<type 'unicode'>

>>>a.decode('ascii')
<UnicodeDecodeError>

可以看到在Python2.7shell中,我們輸入”中文“這兩個中文字符時,用瞭解釋器的默認編碼格式,在windows下即爲GBK,所以我們看到的是上面第一個圖中的十六進制表達。可以看到此時a的類型是str。
當我們對str類型的a用”gbk“解碼後賦給變量b,可以b的類型是unicode,十六進制的表達式類似上面第二個圖。
但是我們用ASCII編碼去解碼卻得到DecodeError的結果。
所以我們得到一個結論:[str].decode(str對應的編碼)=[unicode],並且用什麼碼編碼就用什麼碼解碼。

>>>c=b.encode('utf8')
>>>c
'\xe4\xb8\xad\xe6\x96\x87'
>>>type(c)
<type 'str'>
>>>print c
涓?枃

我們可以看到對unicode類型的變量b用utf8編碼後變成了str類型的c,它的底層十六進制表達如上面的第三張圖。unicode轉換成utf8的規則可以參看這個網址上的博客:傳送門
當我們print c的時候就打印出來亂碼了,原因是此時的str變量c用的是utf8編碼,但是解釋器的編碼是gbk,相當於用gbk的編碼方式去解釋用utf8編碼的字符串,自然打印出來的是亂碼。(而且,上面說過gbk是兩個字節表示一箇中文字符,utf8是三個字節表示一箇中文字符,所以打印出來是三個亂碼的字符)
所以我們得到另一個結論:[unicode].encode(你想要的編碼方式)=[str],解釋器編碼方式要與字符串的編碼方式一樣才能解出正確的字符。

好了,說到這讀者應該知道了Python裏的str和unicode轉換是怎麼回事,以及底層那串”0101“的數字發生了什麼變化。再說一句,Python2x中可以直接對str調用encode函數進行編碼,我們可以這麼理解他的背後實際做法:(str).decode(默認的編碼).encode(你想要的編碼)。那有讀者要疑惑了,unicode decode是什麼呢,Python2x中我試了下還是原unicode,我猜是Python不希望它出錯,故意那這個接口留着,卻不作爲。

Python3x中的str/bytes轉換

好吧,有些讀者可能剛理解了上面說的Python2x中的unicode和str是怎麼回事,不就是str解個碼變成unicode,然後unicode編個碼變成你想要的編碼編的str嘛,簡單!
然而到了Python3x後發現,咦,str不能解碼了,str對象壓根沒有decode屬性了,這還怎麼入手?
首先我們要知道,在Python3x中不在有unicode這一說了,只有str和bytes這兩個概念了(其實是Python把unicode隱藏了)。來看下面的代碼

a = '中' 
a.decode('utf8')                #AttributeError: 'str' object has no attribute 'decode'                     
print(type(a))                  #<class 'str'>
print(a.encode('utf8'))         #b'\xe4\xb8\xad'
print(type(a.encode('utf8')))   #<class 'bytes'>


b = bytes(a,"utf8")
print (b)                       #b'\xe4\xb8\xad'
print(type(b))                  #<class 'bytes'>
print(b.decode('utf8'))         #中
print(type(b.decode('utf8')))   #<class 'str'>

我們可以看到:
Python3x中(str).encode(編碼)=(bytes)
(bytes).decode(‘bytes對應的編碼’)=(str)
str不能上來就解碼了
這是爲什麼呢?其實我們可以把unicode概念加上,幫助你理解,這裏的str其實是unicode類型的字符串,你可以把它看成Python2x中的unicode,而bytes只不過帶有指定編碼的字節序列,可以看成Python2x中的str。這下你再看,是不是str(unicode)編碼變bytes(str),bytes(str)解碼變str(unicode)。可以這麼想,我們輸入的字符串直接被Python3x處理成Unicode形式的字符串,那麼你可以幹嘛,只能編碼後纔會變成字節序列啊。
說了這些可能你會覺得有點暈,那麼來看下面的對比:
圖一

圖二

圖一是Python3.4環境下的shell,圖二是Python2.7環境下的shell,要讀取的文本是以UTF-8的編碼格式保存的,還是”中文“二字,我們可以想象,底層的字節序列應該是”\xe4\xb8\xad\xe6\x96\x87“。
可以看到Python2.7可以讀取,並且底層的字節序列是怎麼保存的他就怎麼讀取。但是Python3.4呢,他報錯了,是decode錯誤,很顯然,他在讀取的時候想把UTF-8編碼的字節序列用GBK去解碼,想把它變成unicode,自然就出現了錯誤。我試了用ASNI編碼保存的話,他就會正常讀取,而且type是str。
所以我說實際上Python3x一旦讀取了文本中的字符串,事實上它已經替你解碼成unicode了,但是告訴你這是str類型。

總結

我們來總結一下,Python2x中,保存了什麼字節序列,他就原封不動的讀取進來,然後告訴你這是str類型(底層是字節序列),那麼你可以把它decode一下,變成可以跨平臺使用的unicode。好了,你要輸出到文件中去了,那你得以一個具體的規則輸出,也就是把unicode encode一下又變str(字節序列)。
Python3x中,文本中保存了字節序列,讀取的時候Python解釋器用設定的默認編碼去解碼了一道,變成了unicode,所以你不需要操作了,但是它告訴你這是str,你就當不知道,反正不管怎麼誇平臺都不會出錯了。到了要輸出的時候,再把unicode encode成bytes(字節序列)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章