不得不知道的Python字串編碼相關的知識

發表於2016-12-15

開發經常會遇到各種字串編碼的問題,例如報錯SyntaxError: Non-ASCII character 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128),又例如顯示亂碼。
由於之前不知道編碼的原理,遇到這些情況,就只能不斷的用各種編碼decode和encode。。。。。
今天整理一個python中的各種編碼問題的原因和解決方法,以後遇到編碼問題,就不會像莽頭蒼蠅一樣,到處亂撞了。

下面的python環境都是在2.7,聽說在3.X中已經沒有編碼的問題了,因為所有的字串都是unicode了,之後裝個3.X試一下。

如果不知道什麼是decode和encode,建議先看一下:這裡

一、encoding的作用

1.在python檔案中,如果有中文,就一定要在檔案的第一行標記使用的編碼型別,例如 #encoding=utf-8 ,就是使用utf-8的編碼,這個編碼有什麼作用呢?會改變什麼呢?
demo1.py

輸出:

我們通過print把一個變數輸出到終端的時候,IDE或者系統一般都會幫我們的輸出作轉換,例如中文字元會轉成中文,所以就看不到變數的原始內容。
repr函式可以看這個變數的給python看的形式,也就是看到這個變數的原始內容
從上面的輸出可以看到test變數的str型別,它的編碼是utf-8的(怎麼知道是utf-8,請看第三部分),也就是的encoding型別
如果我們把encoding改為gbk
demo2.py

輸出

這樣test的編碼型別就變為gbk了。
所以這個encoding會決定在這個py檔案中定義的字串變數的編碼方式。
而如果一個變數是從其他py檔案匯入,或者從資料庫,redis等讀取出來的話,它的編碼又是怎樣的?
a.py

b.py

輸出

a.py中定義test變數,a.py的編碼方式是utf-8,b.py的編碼方式是gbk,b從a中匯入test,結果顯示test依然為utf-8編碼,也就是a.py的編碼
所以encoding只會決定本py檔案的編碼方式,不會影響匯入的或者從其他地方讀取的變數的編碼方式

二、常見報錯codec can't encode characters的原因

python的程式經常會報錯 codec can't encode characterscodec can't decode characters

在python中定義一個字串,

上面的程式碼會報錯

除了str方法外,如果操作兩個都有中文的字串,也會報錯,但是隻有其中一個有中文,卻不會報錯

為什麼會這樣?
這原因下面再解答,這裡先列出這個報錯的解決方法:
解決方法是:把系統的預設編碼設定為utf-8

demo3.py

這裡定義三個分別是unicode,utf-8和gbk編碼的字串,unicode_test,utf8_test和gbk_test
1.合併unicode和utf-8的時候,輸出:

合併的結果的編碼是unicode編碼。
2.合併unicode和gbk,會報錯:

所以我們可以推測:
在python對兩個字串進行操作的時候,如果這兩個字串有一個是unicode編碼,有一個是非unicode編碼,python會將非unicode編碼的字串decode成unicode編碼,再進行字串操作
例如合併字串的操作可以寫成以下的function:

PS:sys.getdefaultencoding()的初始值是ascii
所以,
codec can't encode(decode) characters這個報錯是encode或decode這兩個方法產生的,而這個方法的引數是sys.getdefaultencoding()。如果用ascii編碼對帶有中文的字串進行解碼,就會報錯。所以修改系統的預設編碼可以避免這個報錯。
當執行 str 操作時,python會執行 unicode_test.encode(sys.getdefaultencoding()) ,所以也會報錯。

3.#合併utf-8和gbk的時候卻不會報錯,python會直接把兩個字串合併,不會有decode或encode的操作,但是輸出的時候,部分字串會亂碼。
demo4.py

這裡檔案的encoding是gbk,sys.getdefaultencoding()設定為utf-8,結果是:

即gbk的部分亂碼了。所以輸出的時候會按照sys.getdefaultencoding()的編碼來解碼。

三、怎麼判斷一個字串(string)的編碼方式

1.沒有辦法準確地判斷一個字串的編碼方式,例如gbk的“\aa”代表甲,utf-8的“\aa”代表乙,如果給定“\aa”怎麼判斷是哪種編碼?它既可以是gbk也可以是utf-8

2.我們能做的是粗略地判斷一個字串的編碼方式,因為上面的例如的情況是很少的,更多的情況是gbk中的’\aa’代表甲,utf-8中是亂碼,例如�,這樣我們就能判斷’\aa’是gbk編碼,因為如果用utf-8編碼去解碼的結果是沒有意義的

3.而我們經常遇到的編碼其實主要的就只有三種:utf-8,gbk,unicode

  • unicode一般是 \u 帶頭的,然後後面跟四位數字或字串,例如 \u6d4b\u8bd5 ,一個\u對應一個漢字
  • utf-8一般是 \x 帶頭的,後面跟兩位字母或數字,例如 \xe6\xb5\x8b\xe8\xaf\x95\xe5\x95\x8a ,三個 \x 代表一個漢字
  • gbk一般是 \x 帶頭的,後面跟兩位字母或數字,例如 \xb2\xe2\xca\xd4\xb0\xa1,兩個個 \x 代表一個漢字

4.使用chardet模組來判斷

import chardet
raw = u'我是一隻小小鳥'
print chardet.detect(raw.encode('utf-8'))
print chardet.detect(raw.encode('gbk'))

輸出:

chardet模組可以計算這個字串是某個編碼的概率,基本對於99%的應用場景,這個模組都夠用了。

四、string_escape和unicode_escape

1. string_escape

在str中,\x是保留字元,表示後面的兩位字元表示一個字元單元(暫且這麼叫,不知道對不對),例如'\xe6',一般三個字元單元表示一箇中文字元
所以在定義變數時,a='\xe6\x88\x91',是代表定義了一箇中文字元“我”,但是有時候,我們不希望a這個變數代表中文字元,而是代表3*4=12個英文字元,可以使用encode('string_escape')來轉換:

decode就是反過來。
轉換前後的型別都是string。
還有一個現象,定義a='\x',a='\x0'都是會報錯ValueError: invalid \x escape的,而定義a='\a',即反斜槓後面不是跟x,都會沒問題,而定義a='\x00',即x後面跟兩個字元,也是沒問題的。

2. unicode_escape

同理在unicode中,\u是保留字元,表示後面的四個字元表示一箇中文字元,例如b=u'u6211',表示“我:”,同理我們希望b變數,表示6個英文字元,而不是一箇中文字元,就可以使用encode(‘unicode-escape’)來轉換:

注意encode前是unicode,轉換後是string。
在unicode中,\u是保留字元,但是在string中,就不是了,所以只有一個反斜槓,而不是兩個。
decode就是反過來。
同理,a='\u'也是會報錯的

3. 例子

4. 應用

      1. 內容是unicode,但是type是str,就可以使用decode("unicode_escape")轉換為內容和type都是unicode
      2. 內容是str,但是type是unicode,就可以使用encode("unicode_escape").decode("string_escape")轉換為內容和type都是str

相關文章