開發經常會遇到各種字符串編碼的問題,例如報錯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 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()的編碼來解碼。
三、怎么判斷一個字符串(string)的編碼方式
-
沒有辦法准確地判斷一個字符串的編碼方式,例如gbk的“\aa”代表甲,utf-8的“\aa”代表乙,如果給定“\aa”怎么判斷是哪種編碼?它既可以是gbk也可以是utf-8
-
我們能做的是粗略地判斷一個字符串的編碼方式,因為上面的例如的情況是很少的,更多的情況是gbk中的'\aa'代表甲,utf-8中是亂碼,例如�,這樣我們就能判斷'\aa'是gbk編碼,因為如果用utf-8編碼去解碼的結果是沒有意義的
-
而我們經常遇到的編碼其實主要的就只有三種: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
代表一個漢字
- unicode一般是
-
使用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. 應用
-
內容是unicode,但是type是str,就可以使用
decode("unicode_escape")
轉換為內容和type都是unicodes1='\u6211' s2=s1.decode('unicode-escape')
-
內容是str,但是type是unicode,就可以使用
encode("unicode_escape").decode("string_escape")
轉換為內容和type都是strs1=u'\xe6\x88\x91' s2=s1.encode('unicode_escape').decode("string_escape")
博文為作者原創,未經允許,禁止轉載。