python下含中文字串正則表示式的編碼問題
前言
Python檔案預設的編碼格式是ascii ,無法識別漢字,因為ascii碼中沒有中文。
所以py檔案中要寫中文字元時,一般在開頭加 # -*- coding: utf-8 -*- 或者 #coding=utf-8。
這是指定一種編碼格式,意味著用該編碼儲存中文字元(也可以是gbk、gb2312等)。
關於測試的幾點注意 ------------------------------------------------------------------------------------
注1:程式碼中有中文,就要在頭部指定編碼方式,如果用編輯器寫程式碼,還要注意IDE的檔案儲存編碼格式(一般在setting)
注2:python3.x的原始碼檔案預設使用utf-8編碼,可以解析中文,開頭不指定也行,但為了規範和避免一些意想不到的問題,都指定一下為好
注3:linux互動式命令(左)和py檔案(右)的執行結果會有不同:
左圖,因為我cmd設定了gbk編碼格式,所以u是s用gbk解碼後的unicode物件,配套的解編碼才能使原中文字元在print下正常顯示,所以再用gbk編碼;右圖,py檔案指定了utf8編碼,所以u是s用utf8解碼後的unicode物件(其他方式會執行錯誤),而且想要在螢幕上打印出中文,還須encode成cmd設定的編碼(其他方式顯示亂碼)。
注4:測試中文字元的顯示和匹配時,最好用py檔案寫,否則遇到兩邊不一樣的情況就會感到十分坑爹
-----------------------------------------------------------------------------------------------------------
下面實驗是基於python2.7和linux系統,不測試windows控制檯和windows下的IDE;
下面實驗是關於為了正常顯示中文和正則匹配中文的轉碼測試。
(一)python的str和中文字串
簡單理解,編碼意味著 unicode -> ch-str,解碼意味著 ch-str -> unicode,
關於print顯示中文。舉個例子,用gb18030和utf-8編碼的內容相同的兩份文件測試:
#coding=utf-8
import sys
with open('ch_input_gbk', 'r') as f1, open('ch_input_utf', 'r') as f2:
for l1 in f1:
lines = l1.strip().split('\t') # lines是list, 通過列印它可以看看str不同編碼的內容
sent = lines[0] # sent是ch-str
print lines, sent
for l2 in f2:
lines = l2.strip().split('\t')
sent = lines[0]
print lines, sent
print sent.decode('utf8').encode('gbk')
#print str(sent).decode('string_escape').decode('utf8').encode('gbk')
輸出:
['\xd3\xc4\xc8\xcb\xd6\xf1\xc9\xa3\xd4\xb0'] 幽人竹桑園
['\xb9\xe9\xce\xd4\xbc\xc5\xce\xde\xd0\xfa'] 歸臥寂無喧
['\xce\xef\xc7\xe9\xbd\xf1\xd2\xd1\xbc\xfb'] 物情今已見
['\xb4\xd3\xb4\xcb\xd3\xfb\xce\xde\xd1\xd4'] 從此欲無言
['\xe5\xb9\xbd\xe4\xba\xba\xe7\xab\xb9\xe6\xa1\x91\xe5\x9b\xad'] 騫戒漢絝規鍥
幽人竹桑園
['\xe5\xbd\x92\xe5\x8d\xa7\xe5\xaf\x82\xe6\x97\xa0\xe5\x96\xa7'] 褰掑崸瀵傛棤鍠
歸臥寂無喧
['\xe7\x89\xa9\xe6\x83\x85\xe4\xbb\x8a\xe5\xb7\xb2\xe8\xa7\x81'] 鐗╂儏浠婂凡瑙
物情今已見
['\xe4\xbb\x8e\xe6\xad\xa4\xe6\xac\xb2\xe6\x97\xa0\xe8\xa8\x80'] 浠庢嬈叉棤璦
從此欲無言
- line7,f1的sent正常顯示是因為,txt是gb18030編碼,讀入後仍為此(這與首行的#coding可不一樣),我的cmd同樣也是gb18030
- line11,f2的sent亂碼顯示是因為,txt是utf8編碼,讀入後仍為此,但是print對str是按cmd設定的編碼格式解讀的
- line12,sent又能正常顯示是因為,utf8解碼 -> unicode -> 編碼為gb18030,所以print可以正常解讀了
- line13,有時讀入或抓取的中文不是\xd3\xc4而是這個樣子的\\xd3\\xc4,這是\被轉義了,對它無法做decode轉換編碼,先用str(sent).decode('string_escape'),把反斜槓的轉義去掉,然後就和第12行一樣了
附,12行如果直接寫 sent.encode('gbk') 會報錯:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0
是說,當前位置這個str不能被py預設的ascii解碼,因為它是中文str;要用它的實際編碼來解碼而不是ascii:
1 修改py預設編碼,由ascii改為當前str實際編碼(utf8或gb18030等)
reload(sys)
sys.setdefaultencoding('utf8')
2 但是讀入多個文件含有多個編碼方式時,1的方法就不方便,還是對不同的ch-str都採用unicode轉換編碼較好 (上面例子中的方式)
(二)中文字串的正則匹配
只有一項標準,匹配字串和原字串編碼統一,
還是舉例子,
# -*- coding: utf-8 -*-
import re
def findPart(regex, text, name):
res = re.findall(regex, text)
print "There are %d %s parts:" % (len(res), name)
for r in res:
print r.encode('gbk')
sample = '''en: Regular expression is a powerful tool for manipulating text.
zh: 正則表示式是一種很有用的處理文字的工具。
jp: 正規表現は非常に役に立つツールテキストを操作することです。
jp-char: あアいイうウえエおオ
kr:정규 표현식은 매우 유용한 도구 텍스트를 조작하는 것입니다.
puc: ,。?!:,.?!:《》%&*#<>%&*#
'''
#convert the utf8 to unicode
usample = unicode(sample,'utf8') #相當於usample = sample.decode('utf8')
#get each language parts:
findPart(u"[\u4e00-\u9fa5]+", usample, "unicode chinese")
findPart(u"[\uac00-\ud7ff]+", usample, "unicode korean")
findPart(u"[\u30a0-\u30ff]+", usample, "unicode japanese katakana")
findPart(u"[\u3040-\u309f]+", usample, "unicode japanese hiragana")
findPart(u"[\u3000-\u303f\ufb00-\ufffd]+", usample, "unicode cjk Punctuation")
- line20,目標字串usample是unicode型別,故匹配字串regex也要同(如u"[\u4e00-\u9fa5]+"),u就是轉成unicode;
- line7,usample是unicode,要想print正確顯示,則需要r.encode('gbk'),根據cmd的編碼格式;
- 正則匹配規則不多述,[\u????-\u????] 是不同語言的unicode編碼段,該例輸出是,
中文6個part:正則表示式是一種很有用的處理文字的工具、正規表現、非常、役、立、操作
韓文8個part:정규、표현식은、매우、유용한、도구、텍스트를、조작하는、것입니다
日文片假名6個part:ツールテキスト、ア、イ、ウ、エ、オ
日文平假名11個part:は、に、に、つ、を、することです、あ、い、う、え、お
非英文標點4個part:。、。、,。?!:、《》%&*#
另外,簡單的正則匹配,舉幾個例子,
s1 = '天天天向上天天向上'
print (re.sub(ur'[\u4e00-\u9fa5]{1,}', u'1', s1.decode('utf8'))).encode('gbk') # 1
print (re.sub(ur'([\u4e00-\u9fa5])\1{1,}', u'1', s1.decode('utf8'))).encode('gbk') # 1向上1向上
print (re.sub(ur'([\u4e00-\u9fa5])\1{2,}', u'1', s1.decode('utf8'))).encode('gbk') # 1向上天天向上
s2 = '【aa】天天[email protected]'
print (re.sub(ur'【.*】', u'1', s2.decode('utf8'))).encode('gbk') # 1天天[email protected]
print (re.sub(ur'@', u'1', s2.decode('utf8'))).encode('gbk') # 【aa】天天bb1cc
- line2,{1,}匹配1~n個前面表示式,故6個漢字全部匹配
- line3,()內為一個group,\1指第一個group,{1,}要再匹配1~n個前面group內容(若group內是1個字, {1,}要匹配第2個及往後的字),故匹配了3個天和2個天
- line6和7,中英文標點符號匹配,regex沒有轉義符的話可以不寫r,若text全是英文也可以不寫u