標籤:

讓人困擾的Python 2編碼

當我開始玩爬蟲的時候,就深深體會到了Python2編碼的惡意。網頁上的編碼類型層出不窮,在編解碼的時候總會遇到各種奇葩錯誤,上網查文檔,問大神,改好了也是一頭霧水。也因為編碼問題一度想棄坑Python2轉入Python3。

看過一些介紹python編碼的好文章,但我覺得還是自己總結一些的好,去除背景知識,只把實用的留下來。 請移步《Python字元編碼詳解》, 這是其間很好的一篇文章,每次講起編碼問題總是忍不住再讀一遍。《python字元串編碼及亂碼解決方案》這篇文章也是極好的。

下面獻上我疏陋的理解。

str&unicode, decode&encode

在Python2 當中,對於編碼主要有兩個概念,一個是字元,一個是位元組,字元就是我們實際上看到的字,用來顯示文本,比如一個漢字,一個符號,一個英文字幕等等。而位元組就是我們又來存儲的數據,是一串二進位的01序列。

字元和位元組是可以相互轉換的,比如我們用編輯器去打開文本,看到的是字元,但是將文本存儲到磁碟當中去,就要將字元轉化為位元組。 這中間也涉及了兩個概念,一個是編碼(encode),也就是將字元轉化成位元組的過程。一個是解碼,也就它的逆過程。編碼是為了存儲和傳輸的方便,解碼是為了方便顯示閱讀。

Python2 的編碼難題大都在於,python2本身採用ascii作為其默認的編碼方式,如果涉及到中文,因為ascii不支持中文,就會出現編碼錯誤。這也就是為什麼我們在用編輯器寫python腳本的時候,如果腳本裡面有中文或者其他特殊符號,我們就要在文首聲明utf-8這種編碼模式。

python 2里把字元串分為兩種,一個是str,一個是unicode,它們是basestring的子類。本質上str是一串二進位序列也就是我們所說的位元組,而unicode則對應著字元。 讓我們來舉個栗子,一目了然。

>>> s=>>> sxc8xcb>>> u=u人>>> uuu4eba>>> isinstance(s,str)True>>> isinstance(s,unicode)False>>> isinstance(s,basestring)True>>> isinstance(u,str)False>>> isinstance(u,unicode)True>>> isinstance(u,basestring)True>>>

因此把unicode轉化成str,我們要用到encode方法去編碼。把str解析成unicode字元我們就要用到decode方法去解碼。如果方法應用不當就會返回encode/decodeError。

有兩個典型的編解碼錯誤:

UnicodeEncodeError

當嘗試把unicode字元串轉換成str位元組序列的時候,可能會遇到這個錯誤。當我們想把一個字元串寫入文本的時候,如果這個字元串是str類型,那麼可以將它直接寫入文件中去,這沒有問題(這個str寫入的格式,就是在編輯器文首聲明的編碼模式編碼後的字元串,如果你用的是命令行,那麼在win下是gbk,在linux下是utf-8)。下面是我在windows python shell下得到的。

>>> sxc8xcb>>> print s>>>>>> s.decode(gbk)uu4eba>>> s.decode(utf-8)Traceback (most recent call last): File "", line 1, in File "C:Python27libencodingsutf_8.py", line 16, in decode return codecs.utf_8_decode(input, errors, True)UnicodeDecodeError: utf8 codec cant decode byte 0xc8 in position 0: invalid continuation byte>>> sys.getdefaultencoding()ascii>>> print s.decode(gbk).encode(utf-8)>>> s.decode(gbk).encode(utf-8)xe4xbaxba>>> print (xe4xba)

再給你們看個有意思的,我在notepad++裡面寫的源代碼,指定encode類型為utf-8。 但是當我直接在windows cmd下運行腳本,可以看到直接print s的輸出不是我寫入的那個字元,只有當我把它用utf-8解碼在用gbk編碼才可以得到正確的字元串表示。windows下的print是用gbk進行解碼,但是utf-8有三位,cmd本應該解碼不出來,但是它沒報錯,只把utf-8編碼的前兩位用gbk進行解碼,所以才得到了其他的字。而且我們還發現,在cmd下print unicode編碼,它會自動把它轉換成gbk後輸出。在windows下就有這麼多坑了,一旦你換個平台,甚至換個IDE,誰知道還會出現什麼奇葩事情,所以對漢字來說,最好別輸入成str的形式,不妨在字元串前加個u,變成unicode,就少去了很多編解碼的困擾。linux是用utf-8作為默認編解碼方式的,可能會省去不少麻煩。

#-*- coding:utf-8 -*-import sysprint sys.getdefaultencoding()s=print sprint s.decode(utf-8).encode(utf-8)print s.decode(utf-8).encode(gbk)print type(s)print s.decode(utf-8)print s.decode(gbk)#輸出結果:>>>ascii<type str>Traceback (most recent call last): File "C:UserscchenDesktopcode_test.py", line 10, in print s.decode(gbk)UnicodeDecodeError: gbk codec cant decode byte 0xba in position 2: incomplete multibyte sequencePress any key to continue . . .

但是當字元串是unicode類型,那麼它將會調用encode方法把unicode字元串轉換成str形式後再寫入到文件中去,python用的默認的編碼為ascii(這裡和你在編輯器文首聲明的類型無關,那個給源文件存儲用的),這樣如果這個字元串是中文或其他特殊字元,ascii就無法對它編碼,從而返回錯誤。

>>> f=open(rC:UserscchenDesktop 1.txt,w+)>>> uuu4eba>>> sxc8xcb>>> f.write(u)Traceback (most recent call last): File "<stdin>", line 1, in <module>UnicodeEncodeError: ascii codec cant encode character uu4eba in position 0: ordinal not in range(128)>>> f.write(s)>>> f.write(u.encode(utf-8))>>>> sys.getdefaultencoding()ascii

我這裡寫入文本也不妥,一個是以gbk寫入的,一個是以utf-8寫入的,只是為了演示一下。

UnicodeDecodeError

上面的示例代碼中出現過這麼一個錯誤,簡單來說,就是將str解碼成unicode時的解碼失敗。我嘗試將一個utf-8編碼的str用gbk解碼,解碼失敗出現錯誤。 此外當我們嘗試用『+』來拼接一個str和一個unicode時,python會把str類型的位元組序列解碼成unicode,因為它默認也用ascii解碼,這裡也經常出現錯誤:

>>> uuu4eba>>> sxc8xcb>>> u+sTraceback (most recent call last): File "<stdin>", line 1, in <module>UnicodeDecodeError: ascii codec cant decode byte 0xc8 in position 0: ordinal not in range(128)>>> u+s.decode(gbk)uu4ebau4eba>>>

sys.defaultencoding()

我們可以把這個默認的ascii解碼配置成gbk,這樣就默認用gbk解碼了。

>>> import sys>>> sys.getdefaultencoding()ascii>>> reload(sys)>>> sys.setdefaultencoding(gbk)>>> sys.getdefaultencoding()gbk>>> uuu4eba>>> sxc8xcb>>> u+suu4ebau4eba>>>>>> s.encode(utf-8)xe4xbaxba

看到沒,s可以直接encode,神奇不神奇,其實下面是這樣的,它用默認解碼方式先解碼然後進行沖編碼:

>>>s.decode(sys.getdefaultencoding()).encode(utf-8)>>> reload(sys)>>> sys.setdefaultencoding(ascii)>>> sys.getdefaultencoding()ascii>>> s.encode(utf-8)Traceback (most recent call last): File "", line 1, in UnicodeDecodeError: ascii codec cant decode byte 0xc8 in position 0: ordinal not in range(128)>>>

文件頭的聲明編碼

實際上Python只檢查#,coding和編碼字元串,其他的字元都是為了美觀加上的。

頂部的:# -- coding: utf-8 --或者# coding: utf-8目前有三個作用

  • 如果代碼中有中文注釋,就需要此聲明。
  • 比較高級的編輯器(比如emacs),會根據頭部聲明,將此作為代碼文件的格式。
  • 程序會通過頭部聲明,解碼初始化 u"人生苦短",這樣的unicode對象,(所以頭部聲明和代碼的存儲格式要一致)。

推薦閱讀:

Python3 CookBook | 字元串和文本
我的詞典我做主!python3.5生成自己的詞性詞典
史上最全神經網路結構圖畫圖工具介紹,沒有之一!
Python魔法方法指南

TAG:Python | 編碼 |