python2.7中的字符編碼問題

0. 寫在前面python

原由:以前寫個數據預處理程序的時候遇到了點問題,用re模塊的正則查找方法search時老是找不出來(找錯了或者出亂碼),因而搗鼓搗鼓。後端

通過:查資料,作實驗,發現用utf8編碼的str類型的字符串在search方法中行不通,由於str是字節串,和字符之間沒有固定的一一對應的關係,正則無法用字節串來進行正確匹配。安全

結果:把正則式和目標字符串都使用unicode類型,unicode和字符之間是兩個字節對應一個字符的關係,正則能夠根據這個來對字符進行匹配。python2.7

後續:忽然以爲應該總結一下編碼問題,防止再次入坑。因而有了此文。機器學習

 

1. ascii, unicode, utf8函數

  ascii碼:最先的編碼,只有127個字符,包含英文字母,數字,標點符號和一些其它符號。一個字節表示一個字符。學習

  unicode(統一碼):一個字節不夠放,全世界有各類語言的字符須要編碼,因而unicode給全部的字符都設定了惟一編碼。一般都是用兩個字節表示一個字符(有些生僻的字要用四個字節)。因此,要理解一點:下文中提到到的unicode編碼是雙字節編碼(一個字符兩個字節)。編碼

  uft8:對於ascii編碼的那些字符,只須要1個字節,unicode給這些字符也設定2個字節,若是一篇文章全是英文(ascii字符),就浪費了不少空間(原本1個字節能夠存儲的,用了2個字節),因此產生了utf8。utf8是一種變長的編碼方式,根據不一樣的符號變化字節長度,把ascii編碼成1個字節,漢字一般編碼成3個字節,一些生僻的字符編碼成4~6個字節。spa

  在計算機內存中,統一使用Unicode編碼。debug

  在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纔是真正意義上的字符串類型

 

來看點現實的例子,好比咱們要從一個文件中找出中全部後兩位是'學習'的詞語,在進行判斷的時候:

s = '機器學習'
s[-2:] == '學習‘ 
# 返回false,平時寫程序可能會覺得相等。
# 這裏的」學習是用開頭的encoding聲明解釋的,我開頭用的是utf8,漢字佔3個字節,因此「學習」佔了6個字節),而s[-2:]取的是最後兩個」雙字節「,因此不相同。

s = u'機器學習'
s[-2:] == u'學習’ 
# 返回true,這也是爲何說unicode是真正意義上的字符串類型。由於使用的是unicode,」學習「佔的是兩個」雙字節「,一個"雙字節「一個字。

對於常常處理中文字符串的人,統一用unicode就能夠避免這個坑了。

雖然有些字符串處理函數用str也能夠,應該是函數裏面幫你處理了編碼問題。

 

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,python會用默認的系統編碼encode成str類型,再用你給出的編碼進行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')

 

關於系統默認編碼發揮做用的地方,來看看另外一個例子。

import sys
print sys.getdefaultencoding()  # 輸出ascii

s = 'u華南理工大學'
print s[-2:] == '大學'   # 返回False,並有warning提醒

reload(sys)
sys.setdefaultencoding('utf8')

print s[-2:] == '大學'  # 返回True 

根據結果得知:python在用==比較時,若是第一個操做符是unicode而第二個不是的話,會自動用系統默認編碼幫第二個操做符decode。

 

PS:爲何須要reload(sys)呢。首先,reload是用於從新加載以前import的模塊。

這裏須要從新加載sys的緣由是:python在加載模塊時候刪除了sys中的setdefaultencoding方法(多是出於安全起見),因此須要reload這個sys模塊。

這裏再舉個簡單例子,好比我要修改keras的後端,從tensorflow改爲theano,修改後須要從新加載keras的backend模塊才能修改爲功。

import keras.backend as K

k.backend()    # 一開始是u'tensorflow'

import os

os.environ['KERAS_BACKEND'] = 'theano'

K.backend() # 修改後仍是u'tensorflow'

reload(K)

k.backend() # reload以後後端才變成u'theano'

 

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麻煩的,能夠考慮平時統一用utf8編碼的str,有些問題須要用unicode的再轉爲unicode,遇到編碼問題時能夠思考是否是沒有統一用unicode的問題(本文開頭就給出了一個須要統一用unicode的狀況)

其實弄清楚上面的思路,遇到什麼編碼問題也可以查錯。

相關文章
相關標籤/搜索