繼上一篇文章字符集和編碼詳解總結(jié)了常見字符編碼后双谆,這篇文章會對python中常見的編碼問題進(jìn)行分析和總結(jié)柳洋。由于python3.x版本和python2.x版本在字符編碼方面有很大差異次伶,所以本文都是以Python2.7.5來分析2.x版本中的字符編碼問題。
1.Python編碼基礎(chǔ)
1.1 str和unicode
python中有兩種數(shù)據(jù)模型來支持字符串這種數(shù)據(jù)類型,str和unicode,它們的基類都是basestring辉懒。比如s = "中文"
就是str類型的字符串,而u=u"中文"
就是一個unicode類型的字符串谍失。unicode是由str類型的字符串解碼后得到眶俩,unicode也可以編碼成str類型。即
str --> decode -->unicode
unicode --> encode --> str
嚴(yán)格來說快鱼,str也許應(yīng)該叫做字節(jié)串仿便,因為對于UTF-8編碼的str類型"中文",使用len()函數(shù)得到的結(jié)果是6攒巍,因為UTF-8編碼的str類型“中文”
實際是"\xe4\xb8\xad\xe6\x96\x87"
。而對于unicode類型u“中文”(實際是u"\u4e2d\u6587"
)荒勇,使用len()函數(shù)得到結(jié)果是2.
1.2 頭部編碼聲明
在python源代碼文件中如果有用到非ascii字符柒莉,比如中文,那么需要在源碼文件頭部聲明源代碼字符編碼沽翔,格式如下:
#-*- coding: utf-8 -*-
這個格式看起比較復(fù)雜兢孝,其實python只檢查#窿凤、coding,編碼等字符串跨蟹,可以簡寫成#coding:utf-8雳殊,甚至還可以寫成#coding:u8。
2.Python2.x常見編碼問題
2.1 頭部編碼聲明和文件編碼問題
文件頭部編碼聲明決定了python解析源碼中的str的編碼選擇方式窗轩,比如頭部聲明的是utf-8編碼夯秃,則代碼中s="中文"
python就會按照utf-8編碼格式來解析,通過repr(s)
可以看到字符編碼是"\xe4\xb8\xad\xe6\x96\x87"
痢艺,如果頭部聲明的編碼是gbk編碼仓洼,則python會對s采用gbk編碼解析,結(jié)果是"\xd6\xd0\xce\xc4"
堤舒。
需要注意的是色建,文件本身的編碼要跟文件頭部聲明編碼一致,不然就會出現(xiàn)問題舌缤。文件本身的編碼在Linux下面可以在vim下用命令set fenc
來查看箕戳。如果文件本身編碼是gbk,而源碼文件頭部聲明的編碼是utf-8国撵,這樣如果源碼中有中文就會有問題了陵吸,因為本身中文str存儲是按照gbk編碼來的,而python在解析str的時候又以為是utf-8編碼卸留,這樣就會報SyntaxError: (unicode error) 'utf8' codec can't decode byte
錯誤走越。
2.2 默認(rèn)編碼問題
下面看個python默認(rèn)編碼導(dǎo)致的問題:
#coding: utf-8
u = u"中文"
print repr(u) # u'\u4e2d\u6587'
s = "中文"
print repr(s) # '\xe4\xb8\xad\xe6\x96\x87'
u2 = s.decode("utf-8")
print repr(u2) # u'\u4e2d\u6587'
#s2 = u.decode("utf-8") #編碼錯誤
#u2 = s.encode("utf-8") #解碼錯誤
注意實例中注釋掉的2行代碼,對于unicode最好不要直接調(diào)用decode耻瑟,str最好不要直接調(diào)用encode方法旨指。因為如果是直接調(diào)用,則相當(dāng)于u.encode(default_encoding).decode("utf-8")
喳整,default_encoding是python的unicode實現(xiàn)中用的默認(rèn)編碼谆构,即sys.getdefaultencoding()
得到的編碼,如果你沒有設(shè)置過,那么默認(rèn)編碼就是ascii框都,如果你的unicode本身超出了ascii編碼范圍就會報錯搬素。同理,如果對str直接調(diào)用encode方法魏保,那么默認(rèn)會先對str進(jìn)行解碼栽惶,即s.decode(default_encoding).encode("utf-8"),如果str本身是中文忆蚀,而default_encoding是ascii的話,解碼就會出錯,從而導(dǎo)致上面這兩行會分別報UnicodeEncodeError: 'ascii' codec can't encode characters in position...
錯誤和UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position...
錯誤墓律。
上面例子中注釋掉的兩行代碼如果執(zhí)行就會報錯猜煮,當(dāng)然舀寓,如果本身str或者unicode都在ascii編碼范圍,就沒有問題胯舷。比如s = "abc"; s.encode("utf-8")
就不會有問題,語句執(zhí)行后會返回一個跟s的id不同的str绊含。
那如果要解決實例1中的問題桑嘶,有兩種方法,其一是明確指定編碼躬充,如下所示:
#coding: utf-8
u = u"中文"
print repr(u) # u'\u4e2d\u6587'
s = "中文"
print repr(s) # '\xe4\xb8\xad\xe6\x96\x87'
u2 = s.decode("utf-8")
print repr(u2) # u'\u4e2d\u6587'
s2 = u.encode("utf-8").decode("utf-8") # OK
u2 = s.decode("utf8").encode("utf-8") # OK
第二種方法就是更改python的默認(rèn)編碼為文件編碼格式逃顶,如下所示(這里只所以要reload sys模塊,是因為python初始化后刪除了setdefaultencoding方法):
#coding:utf-8
import sys
reload(sys)
sys.setdefaultencoding("utf-8") #更改默認(rèn)編碼為utf-8
u = u"中文"
print repr(u) # u'\u4e2d\u6587'
s = "中文"
print repr(s) # '\xe4\xb8\xad\xe6\x96\x87'
u2 = s.decode("utf-8")
print repr(u2) # u'\u4e2d\u6587'
s2 = u.decode("utf-8")
u2 = s.encode("utf-8")
2.3讀寫文件編碼
采用python的open()方法打開文件時口蝠,read()讀取的是str津坑,編碼就是文件本身的編碼。而調(diào)用write()寫文件時疆瑰,如果參數(shù)是unicode眉反,則需要用指定編碼encode,如果write()參數(shù)是unicode而且沒有指定編碼穆役,則會采用python默認(rèn)編碼encode后再寫入寸五。
#coding:utf-8
f = open("testfile")
s = f.read()
f.close()
print type(s) # <type 'str'>
u = s.decode("utf-8") #testfile是utf-8編碼
f = open("testfile", "w")
f.write(u.encode("gbk")) #以gbk編碼寫入,testfile為gbk編碼
f.close()
此外耿币,python的codecs模塊提供了一個open()方法梳杏,可以指定編碼打開文件,使用這個方法打開文件讀取返回是unicode淹接。寫入時十性,如果write參數(shù)是unicode,則使用打開文件時的編碼寫入塑悼,如果是str,則先使用默認(rèn)編碼解碼成unicode后再以打開文件的編碼寫入(這里需要注意如果str是中文厢蒜,而默認(rèn)編碼sys.getdefaultencoding()是ascii的話會報解碼錯誤)。
#coding:gbk
import codecs
f = codecs.open('testfile', encoding='utf-8')
u = f.read()
f.close()
print type(u) # <type 'unicode'>
f = codecs.open('testfile', 'a', encoding='utf-8')
f.write(u) #寫入unicode
# 寫入gbk編碼的str愕贡,自動進(jìn)行解碼編碼操作
s = '漢'
print repr(s) # '\xba\xba'
# 這里會先將GBK編碼的str解碼為unicode再編碼為UTF-8寫入
#f.write(s) #默認(rèn)編碼為ascii時,這會報解碼錯誤巷屿。
f.close()