Python編碼問題

Python編碼問題總結

繼上一篇文章字符集和編碼詳解總結了常見字符編碼後,這篇文章會對python中常見的編碼問題進行分析和總結。由於python3.x版本和python2.x版本在字符編碼方面有很大差異,所以本文都是以Python2.7.5來分析2.x版本中的字符編碼問題。


1.Python編碼基礎

1.1 str和unicode

python中有兩種數據模型來支持字符串這種數據類型,str和unicode,它們的基類都是basestring。比如s = "中文"就是str類型的字符串,而u=u"中文"就是一個unicode類型的字符串。unicode是由str類型的字符串解碼後得到,unicode也可以編碼成str類型。即

str --> decode -->unicode
unicode --> encode --> str

嚴格來說,str也許應該叫做字節串,因爲對於UTF-8編碼的str類型"中文",使用len()函數得到的結果是6,因爲UTF-8編碼的str類型“中文”實際是"\xe4\xb8\xad\xe6\x96\x87"。而對於unicode類型u“中文”(實際是u"\u4e2d\u6587"),使用len()函數得到結果是2.

1.2 頭部編碼聲明

在python源代碼文件中如果有用到非ascii字符,比如中文,那麼需要在源碼文件頭部聲明源代碼字符編碼,格式如下:

#-*- coding: utf-8 -*-

這個格式看起比較複雜,其實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編碼解析,結果是"\xd6\xd0\xce\xc4"

需要注意的是,文件本身的編碼要跟文件頭部聲明編碼一致,不然就會出現問題。文件本身的編碼在Linux下面可以在vim下用命令set fenc來查看。如果文件本身編碼是gbk,而源碼文件頭部聲明的編碼是utf-8,這樣如果源碼中有中文就會有問題了,因爲本身中文str存儲是按照gbk編碼來的,而python在解析str的時候又以爲是utf-8編碼,這樣就會報SyntaxError: (unicode error) 'utf8' codec can't decode byte錯誤。

2.2 默認編碼問題

下面看個python默認編碼導致的問題:

#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最好不要直接調用decode,str最好不要直接調用encode方法。因爲如果是直接調用,則相當於u.encode(default_encoding).decode("utf-8"),default_encoding是python的unicode實現中用的默認編碼,即sys.getdefaultencoding()得到的編碼,如果你沒有設置過,那麼默認編碼就是ascii,如果你的unicode本身超出了ascii編碼範圍就會報錯。同理,如果對str直接調用encode方法,那麼默認會先對str進行解碼,即s.decode(default_encoding).encode("utf-8"),如果str本身是中文,而default_encoding是ascii的話,解碼就會出錯,從而導致上面這兩行會分別報UnicodeEncodeError: 'ascii' codec can't encode characters in position...錯誤和UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position...錯誤。

上面例子中註釋掉的兩行代碼如果執行就會報錯,當然,如果本身str或者unicode都在ascii編碼範圍,就沒有問題。比如s = "abc"; s.encode("utf-8")就不會有問題,語句執行後會返回一個跟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的默認編碼爲文件編碼格式,如下所示(這裏只所以要reload sys模塊,是因爲python初始化後刪除了setdefaultencoding方法):
#coding:utf-8                                                                   

import sys 
reload(sys)
sys.setdefaultencoding("utf-8") #更改默認編碼爲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,編碼就是文件本身的編碼。而調用write()寫文件時,如果參數是unicode,則需要用指定編碼encode,如果write()參數是unicode而且沒有指定編碼,則會採用python默認編碼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參數是unicode,則使用打開文件時的編碼寫入,如果是str,則先使用默認編碼解碼成unicode後再以打開文件的編碼寫入(這裏需要注意如果str是中文,而默認編碼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,自動進行解碼編碼操作
s = '漢'
print repr(s) # '\xba\xba'
# 這裏會先將GBK編碼的str解碼爲unicode再編碼爲UTF-8寫入
#f.write(s) #默認編碼爲ascii時,這會報解碼錯誤。
f.close()


3.參考資料

發佈了121 篇原創文章 · 獲贊 133 · 訪問量 68萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章