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


開發經常會遇到各種字符串編碼的問題,例如報錯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

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

輸出:

<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.py

# 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文件的編碼方式,不會影響導入的或者從其他地方讀取的變量的編碼方式

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

python的程序經常會報錯 codec can't encode characterscodec 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()的編碼來解碼。

三、怎么判斷一個字符串(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%的應用場景,這個模塊都夠用了。

四、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")
    

博文為作者原創,未經允許,禁止轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM