解決python2.7中re模塊的search使用utf8的str出問題,然後做個總結。 ...
0. 寫在前面
起因:之前寫個數據預處理程式的時候遇到了點問題,用re模塊的正則查找方法search時總是找不出來(找錯了或者出亂碼),於是搗鼓搗鼓。
經過:查資料,做實驗,猜想:發現用utf8編碼的str類型的字元串在search方法中行不通,因為str是位元組串,和字元之間沒有固定的一一對應的關係,正則沒法用位元組串來進行正確匹配。
結果:把正則式和目標字元串都使用unicode類型,unicode和字元之間是兩個位元組對應一個字元的關係,正則可以根據這個來對字元進行匹配。
後續:突然覺得應該總結一下編碼問題,防止再次入坑。於是有了此文。
1. ascii, unicode, utf8
ascii碼:最早的編碼,只有127個字元,包含英文字母,數字,標點符號和一些其它符號。一個位元組表示一個字元。
unicode(統一碼):一個位元組不夠放,全世界有各種語言的字元需要編碼,於是unicode給所有的字元都設定了唯一編碼。通常都是用兩個位元組表示一個字元(有些生僻的字要用四個位元組)。所以,要理解一點:下文中提到到的unicode編碼是雙位元組編碼(一個字元兩個位元組)。
uft8:對於ascii編碼的那些字元,只需要1個位元組,unicode給這些字元也設定2個位元組,如果一篇文章全是英文(ascii字元),就浪費了很多空間(本來1個位元組可以存儲的,用了2個位元組),所以產生了utf8。utf8是一種變長的編碼方式,根據不同的符號變化位元組長度,把ascii編碼成1個位元組,漢字通常編碼成3個位元組,一些生僻的字元編碼成4~6個位元組。
在電腦記憶體中,統一使用Unicode編碼。
在python中,建議程式過程中統一使用unicode編碼,保存文件和讀取文件時使用utf8(在讀寫磁碟文件時候用utf8進行相應的decode和encode,關於decode和encode見下文第4點)。
2. encoding聲明
python預設使用ascii編碼去解釋源文件。
如果源文件中出現了非ASCII碼字元,不在開頭聲明encoding會報錯。
可以聲明為utf8,告訴解釋器用utf8去讀取文件代碼,這個時候源文件有中文也不會報錯。
# encoding=utf8 如果不加這一行會報錯 print '解釋器用相應的encoding去解釋python代碼'
3. python2.7中的str和unicode
debugger的時候會發現,python2.7中的字元串一般有兩種類型,unicode和str。
str為位元組碼,會根據某種編碼把字元串轉成一個個位元組,這個時候字元和位元組沒有所謂固定的一一對應的關係。
unicode則是用unicode編碼的字元串,這個時候一個字元是對應兩個位元組的,一一對應。
直接賦值字元串,類型為str,str為位元組串,會按照開頭的encoding來編碼成一個個的位元組。
賦值的時候在字元串前面加個u,類型則為unicode,直接按照unicode來編碼。
s1 = '位元組串' print type(s1) #輸出 <type 'str'>,按照開頭的encoding來編碼成相應的位元組。 print len(s1) #輸出9,因為按utf8編碼,一個漢字占3個位元組,3個字就占9個位元組。 s2 = u'統一碼' print type(s2) #輸出 <type 'unicode'>,用unicode編碼,2個位元組1個字元。 print len(s2) #輸出3,unicode用字元個數來算長度,從這個角度上看,unicode才是真正意義上的字元串類型
4. python2.7中的encode和decode
encode的正常使用:對unicode類型進行encode,得到位元組串str類型。也即是unicode -> encode(根據指定編碼) -> str。
decode的正常使用:對str類型進行decode,得到unicode類型。也即是str -> decode(根據指定編碼) -> unicode。
註意:encode和decode的時候都是需要指定編碼的。
因為在編碼的時候要知道原來的編碼是什麼和按照什麼新編碼方式進行編碼,要用到兩種編碼,這裡預設有一個unicode,所以需要再指定一個編碼方式。解碼的時候也是一個道理。
這兩個方法就是在unicode和str之間用指定編碼進行轉換。
s3 = u'統一碼'.encode('utf8') print type(s3) # 輸出 <type 'str'> s4 = '位元組串'.decode('utf8') print type(s4) #輸出 <type 'unicode'>
encode的不正常使用(不建議):對str類型進行encode,因為encode需要的是unicode類型,這個時候python會用預設的系統編碼decode成unicode類型,再用你給出編碼進行encode。(註意這裡的系統編碼不是開頭的encoding,具體例子見下文第5點)
decode的不正常使用:對unicode類型進行decode,直接報錯。
5. 修改系統預設編碼
系統預設使用ascii編碼,需要進行相應的修改。
這個編碼和開頭的encoding不同之處在於,開頭的encoding是對於文件內容的編碼,這裡的編碼是一些python方法中預設使用的編碼,比如對str進行encode的時候預設先decode的編碼,比如文件寫操作write的encode的編碼(具體見下文第7點)
import sys reload(sys) sys.setdefaultencoding('utf8') s = '位元組串str' s.encode('utf8') #等價於 s.decode(系統編碼).encode('utf8')
6. 查看文件編碼
import chardet with open(filename,'r') as f: data = f.read() return chardet.detect(data)
7. 文件讀寫(雖然全是字,但是很重要)
首先要記住,讀出和寫入,這兩個文件的關口都是用str類型的,就是一個個位元組。
python中內置的預設open在讀取文件的時候以位元組串str的形式,讀出一個個位元組。讀取後要用正確的編碼才能decode成正確的unicode,所以要知道原來在文件中的編碼。
寫文件的時候也是一個道理,用str類型,以位元組的形式寫入,這個str是以某種編碼方式編碼的,要註意用正確的編碼方式編碼,一般是按utf8編碼後寫文件。
如果你用unicode類型寫入,python會根據系統預設編碼來把unicode編碼成str再寫入文件。因為寫入文件需要的是str,是str就寫,不是我就把你轉成str再寫。
簡單原則,儘量用str寫入,避免使用預設編碼,這樣也不用在開頭修改預設編碼。
python中模塊codecs中的open方法可以指定一個編碼。它保證了讀入和寫出的位元組都是按照這個指定編碼進行編碼的。
這樣在讀文件的時候:會把讀出的str按照指定編碼decode成unicode。
寫文件的時候:如果是unicode,會根據指定編碼encode成str然後寫入;如果是str,會根據系統預設編碼把str進行decode得到unicode,再根據指定編碼encode成str進行寫入。
簡單原則,儘量用unicode寫入,避免使用預設編碼,這樣也不用在開頭修改預設編碼。
註意一下,對於其它方式讀寫文件,需要自行debugger看看編碼的問題。比如我在python中讀取excel的時候讀出來就直接是unicode而不是str。
8. 一般的處理要點
(1) 首先把源文件的預設encoding和系統預設編碼改為utf8
(2) 程式執行過程統一使用unicode類型
(3) 對於讀寫文件(用python內置的預設open來說),得到的是str,對str進行相應的encode和decode就可以了。
總結一下就是:
設置相應的預設編碼為utf8;
讀文件拿到str類型:str -> decode('utf8') -> unicode
程式處理:用unicode
寫文件:unicode -> encode('utf8') -> str,用str類型寫入文件
當然前提是文件都是utf8格式的啦,包括源文件和讀寫的數據文件。
另外想說一下:
對於寫程式的過程中統一使用unicode類型這一點,只是一個建議,有時候遇到編碼問題可以思考是不是沒有統一用unicode的問題(本文開頭就給出了一個需要統一用unicode的情況)
嫌全部弄成unicode挺麻煩的,可以考慮平時統一用utf8編碼的str,有些問題需要用unicode的再轉為unicode,
其實弄清楚上面的思路,遇到什麼編碼問題也能夠查錯。