【轉載】不得不知道的Python字符串編碼相關的知識

原文地址:http://www.cnblogs.com/Xjng/p/5093905.htmlhtml

 

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

下面的python環境都是在2.7,據說在3.X中已經沒有編碼的問題了,由於全部的字符串都是unicode了,以後裝個3.X試一下。redis

若是不知道什麼是decode和encode,建議先看一下:這裏數據庫

1、encoding的做用

1.在python文件中,若是有中文,就必定要在文件的第一行標記使用的編碼類型,例如 #encoding=utf-8 ,就是使用utf-8的編碼,這個編碼有什麼做用呢?會改變什麼呢?
demo1.pybash

# encoding=utf-8 test='測試test' print type(test) print repr(test)

輸出:ide

<type 'str'> '\xe6\xb5\x8b\xe8\xaf\x95test'

咱們經過print把一個變量輸出到終端的時候,IDE或者系統通常都會幫咱們的輸出做轉換,例如中文字符會轉成中文,因此就看不到變量的原始內容。
repr函數能夠看這個變量的給python看的形式,也就是看到這個變量的原始內容
從上面的輸出能夠看到test變量的str類型,它的編碼是utf-8的(怎麼知道是utf-8,請看第三部分),也就是的encoding類型
若是咱們把encoding改成gbk
demo2.py函數

# encoding=gbk test='測試test' print type(test) print repr(test)

輸出測試

<type 'str'> '\xb2\xe2\xca\xd4test'

這樣test的編碼類型就變爲gbk了。
因此這個encoding會決定在這個py文件中定義的字符串變量的編碼方式。
而若是一個變量是從其餘py文件導入,或者從數據庫,redis等讀取出來的話,它的編碼又是怎樣的?
a.pyui

# encoding=utf-8 test='測試test'

b.py編碼

# encoding=gbk from a import test print repr(test)

輸出

'\xe6\xb5\x8b\xe8\xaf\x95test'

a.py中定義test變量,a.py的編碼方式是utf-8,b.py的編碼方式是gbk,b從a中導入test,結果顯示test依然爲utf-8編碼,也就是a.py的編碼
因此encoding只會決定本py文件的編碼方式,不會影響導入的或者從其餘地方讀取的變量的編碼方式

2、常見報錯codec can't encode characters的緣由

python的程序常常會報錯 codec can't encode characters 或 codec can't decode characters

在python中定義一個字符串,

import sys print sys.getdefaultencoding() # 輸出 ascii unicode_test=u'測試test' print repr(str(unicode_test))

上面的代碼會報錯

'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

除了str方法外,若是操做兩個都有中文的字符串,也會報錯,可是隻有其中一個有中文,卻不會報錯

unicode_test = u'測試test%s{0}' print '%stest' % unicode_test # 不會報錯 print '%s測試' % unicode_test #會報錯 print unicode_test % 'test' #不會報錯 print unicode_test % '測試' #會報錯 print unicode_test.format('test') #不會報錯 print unicode_test.format('測試') #會報錯 print unicode_test.split('test') #不會報錯 print unicode_test.split('測試') #報錯 print unicode_test + 'test' #不會報錯 print unicode_test + '測試' #會報錯

爲何會這樣?
這緣由下面再解答,這裏先列出這個報錯的解決方法:
解決方法是:把系統的默認編碼設置爲utf-8

import sys reload(sys) sys.setdefaultencoding('utf-8') print sys.getdefaultencoding() unicode_test=u'測試test'

demo3.py
# encoding=utf-8
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
unicode_test=u'測試test'
utf8_test='測試test'
gbk_test=unicode_test.encode('gbk')

#合併unicode和utf-8 merge=unicode_test+utf8_test print type(merge) print repr(merge) #合併unicode和gbk merge=unicode_test+gbk_test print type(merge) print repr(merge) print merge #合併utf-8和gbk merge=utf8_test+gbk_test print type(merge) print repr(merge) print merge

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

<type 'unicode'> u'\u6d4b\u8bd5test\u6d4b\u8bd5test'

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

'utf8' codec can't decode byte 0xb2 in position 0: invalid start byte

因此咱們能夠推測:
在python對兩個字符串進行操做的時候,若是這兩個字符串有一個是unicode編碼,有一個是非unicode編碼,python會將非unicode編碼的字符串decode成unicode編碼,再進行字符串操做
例如合併字符串的操做能夠寫成如下的function:

def merge_str(str1, str2): if isinstance(str1, unicode) and not isinstance(str2, unicode): str2 = str2.decode(sys.getdefaultencoding()) elif not isinstance(str1, unicode) and isinstance(str2, unicode): str1 = str1.decode(sys.getdefaultencoding()) return str1 + str2

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 import sys reload(sys) sys.setdefaultencoding('utf-8') unicode_test = u'測試test' utf8_test = unicode_test.encode('utf-8') gbk_test = unicode_test.encode('gbk') merge = utf8_test + gbk_test print type(merge) print repr(merge) print merge

這裏文件的encoding是gbk,sys.getdefaultencoding()設置爲utf-8,結果是:

<type 'str'> '\xe6\xb5\x8b\xe8\xaf\x95test\xb2\xe2\xca\xd4test' 測試test����test

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

3、怎麼判斷一個字符串(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'))
```
輸出:

{'confidence': 0.99, 'encoding': 'utf-8'} {'confidence': 0.99, 'encoding': 'GB2312'}

chardet模塊能夠計算這個字符串是某個編碼的機率,基本對於99%的應用場景,這個模塊都夠用了。

4、string_escape和unicode_escape

1. string_escape

在str中,\x是保留字符,表示後面的兩位字符表示一個字符單元(暫且這麼叫,不知道對不對),例如'\xe6',通常三個字符單元表示一箇中文字符
因此在定義變量時,a='\xe6\x88\x91',是表明定義了一箇中文字符「我」,可是有時候,咱們不但願a這個變量表明中文字符,而是表明3*4=12個英文字符,可使用encode('string_escape')來轉換:

'\xe6\x88\x91'.encode('string_escape')='\\xe6\\x88\\x91'

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')來轉換:

u'\u6211'.encode('unicode-escape')='\u6211'

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

3. 例子

#正常的str和unicode字符 str_char='我' uni_char=u'我' print repr(str_char) # '\xe6\x88\x91' print repr(uni_char) # u'\u6211' # decode('unicode-escape') s1='\u6211' s2=s1.decode('unicode-escape') print repr(s1) # '\\u6211' print repr(s2) # u'\u6211' # encode('unicode-escape') s1=u'\u6211' s2=s1.encode('unicode-escape') print repr(s1) # u'\u6211' print repr(s2) # '\\u6211' # decode("string_escape") s1='\\xe6\\x88\\x91' s2=s1.decode('string_escape') print repr(s1) # '\\xe6\\x88\\x91' print repr(s2) # '\xe6\x88\x91' # encode("string_escape") s1='\xe6\x88\x91' s2=s1.encode('string_escape') print repr(s1) # '\xe6\x88\x91' print repr(s2) # '\\xe6\\x88\\x91'

4. 應用

  1. 內容是unicode,可是type是str,就可使用decode("unicode_escape")轉換爲內容和type都是unicode

    s1='\u6211' s2=s1.decode('unicode-escape')
  2. 內容是str,可是type是unicode,就可使用encode("unicode_escape").decode("string_escape")轉換爲內容和type都是str

    s1=u'\xe6\x88\x91' s2=s1.encode('unicode_escape').decode("string_escape")

博文爲做者原創,未經容許,禁止轉載。

相關文章
相關標籤/搜索