python 正則指北之個人總結

本文經本人搜索網絡加上我的理解整理而成,若有侵權,請告知,會當即刪除!html

正則引擎大致上可分爲不一樣的兩類:DFA和NFA,而NFA又基本上能夠分爲傳統型NFA和POSIX NFA。

DFA Deterministic finite automaton 肯定型有窮自動機

NFA Non-deterministic finite automaton 非肯定型有窮自動機

Traditional NFA

POSIX NFA

DFA引擎由於不須要回溯,因此匹配快速,但不支持捕獲組,因此也就不支持反向引用和$number這種引用方式,目前使用DFA引擎的語言和工具主要有awk、egrep 和 lex。

POSIX NFA主要指符合POSIX標準的NFA引擎,它的特色主要是提供longest-leftmost匹配,也就是在找到最左側最長匹配以前,它將繼續回溯。同DFA同樣,非貪婪模式或者說忽略優先量詞對於POSIX NFA一樣是沒有意義的。

大多數語言和工具使用的是傳統型的NFA引擎,它有一些DFA不支持的特性:

  捕獲組、反向引用和$number引用方式;

  環視(Lookaround,(?<=…)、(?<!…)、(?=…)、(?!…)),或者有的有文章叫作預搜索;

  忽略優化量詞(??、*?、+?、{m,n}?、{m,}?),或者有的文章叫作非貪婪模式;

  佔有優先量詞(?+、*+、++、{m,n}+、{m,}+,目前僅Java和PCRE支持),
       固化分組(?>…)。==》 不支持。。。

 條件匹配
    (?(id)yes_exp|no_exp):對應id的子表達式若是匹配到內容,則這裏匹配yes_exp,不然匹配no_exp

相關進階知識
python屬於perl風格,屬於傳統型NFA引擎,與此相對的是POSIX NFA和DFA等引擎。因此大部分討論都針對傳統型NFA
傳統型NFA中的順序問題
NFA是基於表達式主導的引擎,同時,傳統型NFA引擎會在找到第一個符合匹配的狀況下當即中止:即獲得匹配以後就中止引擎。
而POSIX NFA 中不會馬上中止,會在全部可能匹配的結果中尋求最長結果。這也是有些bug在傳統型NFA中不會出現,可是放到後者中,會暴露出來。
引伸一點,NFA學名爲」非肯定型有窮自動機「,DFA學名爲」肯定型有窮自動機「
這裏的非肯定和肯定均是對被匹配的目標文本中的字符來講的,在NFA中,每一個字符在一次匹配中即便被檢測經過,也不能肯定他是否真正經過,由於NFA中會出現回溯!甚至不止一兩次。圖例見後面例子。而在DFA中,因爲是目標文本主導,全部對象字符只檢測一遍,到文本結束後,過就是過,不過就不過。這也就是」肯定「這個說法的緣由。


擴展型括號
(?aiLmsx)
a        re.A
i        re.I    #忽略大小寫
L        re.L
m        re.M
s        re.S    #點號匹配包括換行符
x        re.X    #能夠多行寫表達式
如:
re_lx = re.compile(r'(?iS)\d+$')
re_lx = re.compile(r'\d+',re.I|re.S)    #這兩個編譯表達式等價

一圖說盡正則 perl 風格

須要重點注意的地方

# 數量詞的貪婪模式與非貪婪模式
正則表達式一般用於在文本中查找匹配的字符串。Python裏數量詞默認是貪婪的(在少數語言裏也多是默認非貪婪),老是嘗試匹配儘量多的字符;非貪婪的則相反,老是嘗試匹配儘量少的字符。例如:正則表達式"ab*"若是用於查找"abbbc",將找到"abbb"。而若是使用非貪婪的數量詞"ab*?",將找到"a"。
以下 加?爲非貪婪匹配即儘量少的匹配,不加則爲貪婪匹配儘量多的匹配,python 中老是默認貪婪匹配的
>>> import re
>>> re.findall(r'[a-z]*?','abcd')
['', '', '', '', '']
>>> re.findall(r'[a-z]+?','abcd')
['a', 'b', 'c', 'd']
>>> re.findall(r'[a-z]??','abcd')
['', '', '', '', '']
>>> re.findall(r'[a-z]*','abcd')
['abcd', '']
# 零寬斷言以及不捕獲分組,命名分組 ,註釋型括號
(?=X )  零寬度正先行斷言。僅當子表達式 X 在 此位置的右側匹配時才繼續匹配。例如,\w+(?=\d) 與後跟數字的單詞匹配,而不與該數字匹配。此構造不會回溯。
(?!X)   零寬度負先行斷言。僅當子表達式 X 不在 此位置的右側匹配時才繼續匹配。例如,例如,\w+(?!\d) 與後不跟數字的單詞匹配,而不與該數字匹配 。
(?<=X)  零寬度正後發斷言。僅當子表達式 X 在 此位置的左側匹配時才繼續匹配。例如,(?<=19)99 與跟在 19 後面的 99 的實例匹配。此構造不會回溯。
(?<!X)  零寬度負後發斷言。僅當子表達式 X 不在此位置的左側匹配時才繼續匹配。例如,(?<!19)99 與不跟在 19 後面的 99 的實例匹配
(?:)  不捕獲分組,對於 ‘abc’  用正則 r'(?:a)bc'  findall 或 search 獲得 ‘bc’   ,a 不會被捕獲
(?P<name>exp)   --》   (?P=name)   比 匿名分組 ()   --》 \1 的 好處是 能夠很直觀看到是如何反向引用分組的
>>> re.findall(r'(?P<alpha>[a-z])\d+(?P=alpha)','a123a456a')
['a']
(?#...)         #註釋型括號,此括號徹底被忽略
>>> re.match(r'(?#編譯)(?P<last_name>[a-zA-Z]+)\s(?P<first_name>[a-zA-Z]+)',name).groupdict()
{'last_name': 'Frank', 'first_name': 'Li'}

# 例子

text = "問:我用的是Windows XP+Service Pack 2,爲何沒法安裝輸入卡號和密碼的控件? 答:在Windows XP+Service Pack 二、Windows 2003等操做系統中,用戶能夠本身選擇是否安裝控件。 問:爲何我看到的卡號輸入框顯示爲*符號? 答:您的瀏覽器禁止下載執行ActiveX控件 , 對於這種狀況 , 您必須打開瀏覽器的ActiveX的相關權限。 操做方法:在瀏覽器菜單中選擇「工具」|「Internet選項」,在彈出的對話框中選擇」安全」 |」Internet」|」自定義級別」,在彈出的對話框中選擇」重置爲 安全級-中」 , 點」重置」按鈕,肯定。 問:看了以上幾個問題,仍是不能登陸,怎麼辦? 答:您的瀏覽器因爲其餘緣由不能安裝招商銀行登陸控件, 請下載並安裝招商銀行登陸控件下載版。 問:沒法出現我的網上銀行大衆版登陸界面。 答:這種狀況是因爲您的機器沒法和我行服務器創建安全鏈接,一般是由於代理服務器設置錯誤引發。若是您是撥號上網,請不要使用代理服務器;若是您過去安裝過我行SSL安全代理,請調用「添加-刪除程序」刪除SSL安全代理;若是您是通過代理訪問Internet,請聯繫您所在網的網絡管理員設置代理服務器。IE5.0瀏覽器設置代理服務器的步驟: Internet選項–>鏈接–>局域網設置–>使用代理服務器–>高級。 問:我在輸入帳號和卡號時,總出錯,該怎樣輸? 答:存摺帳號爲10位,按存摺本上的帳號輸入, 密碼爲6位。若是一卡通是12位卡號的,只需輸入地區碼後面的8位卡號,不須要輸入前面4位的地區碼,密碼爲6位。若是一卡通是16位卡號的,請將16位卡號所有輸入,密碼爲6位。 問:個人存摺沒有設密碼,怎樣在我的網上銀行大衆版中查詢餘額? 答:存摺必須設有密碼方可在 我的網上銀行大衆版 中查詢,所以請您到存摺開戶行給您的存摺設置密碼。 注:網上我的銀行是招商銀行爲我的客戶提供的網上銀行。 本頁面內容僅供參考,部分業務以當地網點的公告與具體規定爲準。"

import re
for q,a in re.findall(r'(?<=問:)(.*?)答:(.*?)(?=問|\Z)',text):
    print('Q: {}'.format(q))
    print('A: {}'.format(a))
Q: 我用的是Windows XP+Service Pack 2,爲何沒法安裝輸入卡號和密碼的控件? 
A: 在Windows XP+Service Pack 二、Windows 2003等操做系統中,用戶能夠本身選擇是否安裝控件。 
Q: 爲何我看到的卡號輸入框顯示爲*符號? 
A: 您的瀏覽器禁止下載執行ActiveX控件 , 對於這種狀況 , 您必須打開瀏覽器的ActiveX的相關權限。 操做方法:在瀏覽器菜單中選擇「工具」|「Internet選項」,在彈出的對話框中選擇」安全」 |」Internet」|」自定義級別」,在彈出的對話框中選擇」重置爲 安全級-中」 , 點」重置」按鈕,肯定。 
Q: 看了以上幾個問題,仍是不能登陸,怎麼辦? 
A: 您的瀏覽器因爲其餘緣由不能安裝招商銀行登陸控件, 請下載並安裝招商銀行登陸控件下載版。 
Q: 沒法出現我的網上銀行大衆版登陸界面。 
A: 這種狀況是因爲您的機器沒法和我行服務器創建安全鏈接,一般是由於代理服務器設置錯誤引發。若是您是撥號上網,請不要使用代理服務器;若是您過去安裝過我行SSL安全代理,請調用「添加-刪除程序」刪除SSL安全代理;若是您是通過代理訪
Q: 我在輸入帳號和卡號時,總出錯,該怎樣輸? 
A: 存摺帳號爲10位,按存摺本上的帳號輸入, 密碼爲6位。若是一卡通是12位卡號的,只需輸入地區碼後面的8位卡號,不須要輸入前面4位的地區碼,密碼爲6位。若是一卡通是16位卡號的,請將16位卡號所有輸入,密碼爲6位。 
Q: 個人存摺沒有設密碼,怎樣在我的網上銀行大衆版中查詢餘額? 
A: 存摺必須設有密碼方可在 我的網上銀行大衆版 中查詢,所以請您到存摺開戶行給您的存摺設置密碼。 注:網上我的銀行是招商銀行爲我的客戶提供的網上銀行。 本頁面內容僅供參考,部分業務以當地網點的公告與具體規定爲準。
# 經常使用 正則表達式
漢字 [\u4e00 - \u9f5a]

# match 和 search 命名分組
>>> name = 'Frank Li'
>>> import re
>>> re.match(r'(?P<First_name>[a-zA-Z]+)\s(?P<Last_name>[a-zA-Z]+)',name).groupdict()
{'First_Name': 'Frank', 'Last_name': 'Li'}
python
import re
 
s1 = 'adkkdk'
s2 = 'abc123efg'

def is_lowercase(s):
    print('{} is lower case',s) if re.match(r'[a-z]+$',s) else print('{} is not lower case!'.format(s))
    
is_lowercase(s1)
is_lowercase(s2)
import re
def get_abbr(s):
    pattern = re.compile(r'[A-Z][a-z]+\s?')
    tup_s = re.findall(pattern,s)
    return ''.join(list(map(lambda tup_s:tup_s[:1],tup_s)))
          
print(get_abbr('Federal Emergency Management Agency'))
import re
s = '123,000,000'
sub_s_2 = s.replace(',','')
sub_s = re.sub(',','',s)
print(sub_s)
print(sub_s_2)
#_*_coding:utf-8_*_
import re
m0 =  "在一九四九年新中國成立"
m1 =  "比一九九零年低百分之五點二"
m2 =  '人一九九六年擊敗俄軍,取得實質獨立'

def switch(s):
    return {'一':1,'二':2,'三':3,'四':4,'五':5,'六':6,'七':7,'八':8,'九':9}.get(s)


# num_dict = {'一':1,'二':2,'三':3,'四':4,'五':5,'六':6,'七':7,'八':8,'九':9}

# sorted(num_dict.items(),key=lambda tupl_num:tupl_num[1])

# print('|'.join(num_dict.keys()))
    
def get_year(m):
    num_dict = {'零':0,'一':1,'二':2,'三':3,'四':4,'五':5,'六':6,'七':7,'八':8,'九':9}
    pattern_string = r'|'.join(num_dict.keys())
    pattern_string = '['+pattern_string+']{4}'
    return re.search(pattern_string,m).group(0)

print(get_year(m0))
print(get_year(m1))
print(get_year(m2))

擴展部分, 優化等

1. 正則表達式語法
  1.1 字符與字符類
    1 特殊字符:\.^$?+*{}[]()|
      以上特殊字符要想使用字面值,必須使用\進行轉義
    2 字符類
        1. 包含在[]中的一個或者多個字符被稱爲字符類,字符類在匹配時若是沒有指定量詞則只會匹配其中的一個。
      2. 字符類內能夠指定範圍,好比[a-zA-Z0-9]表示a到z,A到Z,0到9之間的任何一個字符
      3. 左方括號後跟隨一個^,表示否認一個字符類,好比[^0-9]表示能夠匹配一個任意非數字的字符。
      4. 字符類內部,除了\以外,其餘特殊字符再也不具有特殊意義,都表示字面值。^放在第一個位置表示否認,放在其餘位置表示^自己,-放在中間表示範圍,放在字符類中的第一個字符,則表示-自己。
      5. 字符類內部可使用速記法,好比\d \s \w
    3 速記法
      . 能夠匹配除換行符以外的任何字符,若是有re.DOTALL標誌,則匹配任意字符包括換行
      \d 匹配一個Unicode數字,若是帶re.ASCII,則匹配0-9
      \D 匹配Unicode非數字
      \s 匹配Unicode空白,若是帶有re.ASCII,則匹配\t\n\r\f\v中的一個
      \S 匹配Unicode非空白
      \w 匹配Unicode單詞字符,若是帶有re.ascii,則匹配[a-zA-Z0-9_]中的一個
      \W 匹配Unicode非單子字符
  1.2 量詞
    1. ? 匹配前面的字符0次或1次
    2. * 匹配前面的字符0次或屢次
    3. + 匹配前面的字符1次或者屢次
    4. {m} 匹配前面表達式m次
    5. {m,} 匹配前面表達式至少m次
    6. {,n} 匹配前面的正則表達式最多n次
    7. {m,n} 匹配前面的正則表達式至少m次,最多n次
    注意點:
      以上量詞都是貪婪模式,會盡量多的匹配,若是要改成非貪婪模式,經過在量詞後面跟隨一個?來實現
  1.3 組與捕獲
    1 ()的做用:
      1. 捕獲()中正則表達式的內容以備進一步利用處理,能夠經過在左括號後面跟隨?:來關閉這個括號的捕獲功能
      2. 將正則表達式的一部份內容進行組合,以便使用量詞或者|
    2 反響引用前面()內捕獲的內容:
      1. 經過組號反向引用
        每個沒有使用?:的小括號都會分配一個組好,從1開始,從左到右遞增,能夠經過\i引用前面()內表達式捕獲的內容
      2. 經過組名反向引用前面小括號內捕獲的內容
        能夠經過在左括號後面跟隨?P<name>,尖括號中放入組名來爲一個組起一個別名,後面經過(?P=name)來引用 前面捕獲的內容。如(? P<word>\w+)\s+(?P=word)來匹配重複的單詞。
    3 注意點:
      反向引用不能放在字符類[]中使用。
   1.4 斷言與標記
    斷言不會匹配任何文本,只是對斷言所在的文本施加某些約束
    1 經常使用斷言:
      1. \b 匹配單詞的邊界,放在字符類[]中則表示backspace
      2. \B 匹配非單詞邊界,受ASCII標記影響
      3. \A 在起始處匹配
      4. ^ 在起始處匹配,若是有MULTILINE標誌,則在每一個換行符後匹配
      5. \Z 在結尾處匹配
      6. $ 在結尾處匹配,若是有MULTILINE標誌,則在每一個換行符前匹配
      7. (?=e) 正前瞻 
      8. (?!e) 負前瞻
      9. (?<=e) 正回顧
      10.(?<!e) 負回顧
    2 前瞻回顧的解釋
      前瞻: exp1(?=exp2) exp1後面的內容要匹配exp2
      負前瞻: exp1(?!exp2) exp1後面的內容不能匹配exp2
      後顧: (?<=exp2)exp1 exp1前面的內容要匹配exp2
      負後顧: (?<!exp2)exp1 exp1前面的內容不能匹配exp2
      例如:咱們要查找hello,可是hello後面必須是world,正則表達式能夠這樣寫:"(hello)\s+(?=world)",用來匹配"hello wangxing"和"hello world"只能匹配到後者的hello
  1.5 條件匹配
    (?(id)yes_exp|no_exp):對應id的子表達式若是匹配到內容,則這裏匹配yes_exp,不然匹配no_exp
  1.6 正則表達式的標誌
    1. 正則表達式的標誌有兩種使用方法
      1. 經過給compile方法傳入標誌參數,多個標誌使用|分割的方法,如re.compile(r"#[\da-f]{6}\b", re.IGNORECASE|re.MULTILINE)
      2. 經過在正則表達式前面添加(?標誌)的方法給正則表達式添加標誌,如(?ms)#[\da-z]{6}\b
    2. 經常使用的標誌
      re.A或者re.ASCII, 使\b \B \s \S \w \W \d \D都假定字符串爲假定字符串爲ASCII
      re.I或者re.IGNORECASE 使正則表達式忽略大小寫
      re.M或者re.MULTILINE 多行匹配,使每一個^在每一個回車後,每一個$在每一個回車前匹配
      re.S或者re.DOTALL 使.能匹配任意字符,包括回車
      re.X或者re.VERBOSE 這樣能夠在正則表達式跨越多行,也能夠添加註釋,可是空白鬚要使用\s或者[ ]來表示,由於默認的空白再也不解釋。如:
        re.compile(r"""
          <img\s +) #標籤的開始
          [^>]*? #不是src的屬性
          src= #src屬性的開始
          (?:
          (?P<quote>["']) #左引號
          (?P<image_name>[^\1>]+?) #圖片名字
          (?P=quote) #右括號
          """,re.VERBOSE|re.IGNORECASE)
2. Python正則表達式模塊
  2.1 正則表達式處理字符串主要有四大功能
    1. 匹配 查看一個字符串是否符合正則表達式的語法,通常返回true或者false
    2. 獲取 正則表達式來提取字符串中符合要求的文本
    3. 替換 查找字符串中符合正則表達式的文本,並用相應的字符串替換
    4. 分割 使用正則表達式對字符串進行分割。
  2.2 Python中re模塊使用正則表達式的兩種方法
    1. 使用re.compile(r, f)方法生成正則表達式對象,而後調用正則表達式對象的相應方法。這種作法的好處是生成正則對象以後能夠屢次使用。
    2. re模塊中對正則表達式對象的每一個對象方法都有一個對應的模塊方法,惟一不一樣的是傳入的第一個參數是正則表達式字符串。此種方法適合於只使用一次的正則表達式。
  2.3 正則表達式對象的經常使用方法
    1. rx.findall(s,start, end):
      返回一個列表,若是正則表達式中沒有分組,則列表中包含的是全部匹配的內容,
      若是正則表達式中有分組,則列表中的每一個元素是一個元組,元組中包含子分組中匹配到的內容,可是沒有返回整個正則表達式匹配的內容
    2. rx.finditer(s, start, end):
      返回一個可迭代對象
      對可迭代對象進行迭代,每一次返回一個匹配對象,能夠調用匹配對象的group()方法查看指定組匹配到的內容,0表示整個正則表達式匹配到的內容
    3. rx.search(s, start, end):
      返回一個匹配對象,假若沒匹配到,就返回None
      search方法只匹配一次就中止,不會繼續日後匹配
    4. rx.match(s, start, end):
      若是正則表達式在字符串的起始處匹配,就返回一個匹配對象,不然返回None
    5. rx.sub(x, s, m):
      返回一個字符串。每個匹配的地方用x進行替換,返回替換後的字符串,若是指定m,則最多替換m次。對於x可使用/i或者/g<id>id能夠是組名或者編號來引用捕獲到的內容。
      模塊方法re.sub(r, x, s, m)中的x可使用一個函數。此時咱們就能夠對捕獲到的內容推過這個函數進行處理後再替換匹配到的文本。
    6. rx.subn(x, s, m):
      與re.sub()方法相同,區別在於返回的是二元組,其中一項是結果字符串,一項是作替換的個數。
    7. rx.split(s, m):分割字符串
      返回一個列表
      用正則表達式匹配到的內容對字符串進行分割
      若是正則表達式中存在分組,則把分組匹配到的內容放在列表中每兩個分割的中間做爲列表的一部分,如:
      rx = re.compile(r"(\d)[a-z]+(\d)")
      s = "ab12dk3klj8jk9jks5"
      result = rx.split(s)
      返回['ab1', '2', '3', 'klj', '8', '9', 'jks5']
    8. rx.flags():正則表達式編譯時設置的標誌
    9. rx.pattern():正則表達式編譯時使用的字符串
  2.4 匹配對象的屬性與方法
    01. m.group(g, ...) 
      返回編號或者組名匹配到的內容,默認或者0表示整個表達式匹配到的內容,若是指定多個,就返回一個元組
    02. m.groupdict(default) 
      返回一個字典。字典的鍵是全部命名的組的組名,值爲命名組捕獲到的內容
      若是有default參數,則將其做爲那些沒有參與匹配的組的默認值。
    03. m.groups(default)
      返回一個元組。包含全部捕獲到內容的子分組,從1開始,若是指定了default值,則這個值做爲那些沒有捕獲到內容的組的值
    04. m.lastgroup()
      匹配到內容的編號最高的捕獲組的名稱,若是沒有或者沒有使用名稱則返回None(不經常使用)
    05. m.lastindex()
      匹配到內容的編號最高的捕獲組的編號,若是沒有就返回None。
    06. m.start(g):
      當前匹配對象的子分組是從字符串的那個位置開始匹配的,若是當前組沒有參與匹配就返回-1
    07. m.end(g)
      當前匹配對象的子分組是從字符串的那個位置匹配結束的,若是當前組沒有參與匹配就返回-1
    08. m.span()
      返回一個二元組,內容分別是m.start(g)和m.end(g)的返回值
    09. m.re()
      產生這一匹配對象的正則表達式
    10. m.string()
      傳遞給match或者search用於匹配的字符串
    11. m.pos() 
      搜索的起始位置。即字符串的開頭,或者start指定的位置(不經常使用)
    12. m.endpos() 
      搜索的結束位置。即字符串的末尾位置,或者end指定的位置(不經常使用)
  2.5 總結
    1. 對於正則表達式的匹配功能,Python沒有返回true和false的方法,但能夠經過對match或者search方法的返回值是不是None來判斷
    2. 對於正則表達式的搜索功能,若是隻搜索一次可使用search或者match方法返回的匹配對象獲得,對於搜索屢次可使用finditer方法返回的可迭代對象來迭代訪問
    3. 對於正則表達式的替換功能,可使用正則表達式對象的sub或者subn方法來實現,也能夠經過re模塊方法sub或者subn來實現,區別在於模塊的sub方法的替換文本可使用一個函數來生成
    4. 對於正則表達式的分割功能,可使用正則表達式對象的split方法,須要注意若是正則表達式對象有分組的話,分組捕獲的內容也會放到返回的列表中

2.1. 開始使用re
Python經過re模塊提供對正則表達式的支持。使用re的通常步驟是先將正則表達式的字符串形式編譯爲Pattern實例,而後使用Pattern實例處理文本並得到匹配結果(一個Match實例),最後使用Match實例得到信息,進行其餘的操做。
# encoding: UTF-8
import re
 
# 將正則表達式編譯成Pattern對象
pattern = re.compile(r'hello')
 
# 使用Pattern匹配文本,得到匹配結果,沒法匹配時將返回None
match = pattern.match('hello world!')
 
if match:
    # 使用Match得到分組信息
    print match.group()
 
### 輸出 ###
# hello
re.compile(strPattern[, flag]):
這個方法是Pattern類的工廠方法,用於將字符串形式的正則表達式編譯爲Pattern對象。 第二個參數flag是匹配模式,取值可使用按位或運算符'|'表示同時生效,好比re.I | re.M。另外,你也能夠在regex字符串中指定模式,好比re.compile('pattern', re.I | re.M)與re.compile('(?im)pattern')是等價的。 
可選值有:
re.I(re.IGNORECASE): 忽略大小寫(括號內是完整寫法,下同)
M(MULTILINE): 多行模式,改變'^'和'$'的行爲(參見上圖)
S(DOTALL): 點任意匹配模式,改變'.'的行爲
L(LOCALE): 使預約字符類 \w \W \b \B \s \S 取決於當前區域設定
U(UNICODE): 使預約字符類 \w \W \b \B \s \S \d \D 取決於unicode定義的字符屬性
X(VERBOSE): 詳細模式。這個模式下正則表達式能夠是多行,忽略空白字符,並能夠加入註釋。如下兩個正則表達式是等價的:
a = re.compile(r"""\d +  # the integral part
                   \.    # the decimal point
                   \d *  # some fractional digits""", re.X)
b = re.compile(r"\d+\.\d*")
re提供了衆多模塊方法用於完成正則表達式的功能。這些方法可使用Pattern實例的相應方法替代,惟一的好處是少寫一行re.compile()代碼,但同時也沒法複用編譯後的Pattern對象。這些方法將在Pattern類的實例方法部分一塊兒介紹。如上面這個例子能夠簡寫爲:
m = re.match(r'hello', 'hello world!')
print m.group()
re模塊還提供了一個方法escape(string),用於將string中的正則表達式元字符如*/+/?等以前加上轉義符再返回,在須要大量匹配元字符時有那麼一點用。
2.2. Match
Match對象是一次匹配的結果,包含了不少關於這次匹配的信息,可使用Match提供的可讀屬性或方法來獲取這些信息。
屬性:
1.string: 匹配時使用的文本。
2.re: 匹配時使用的Pattern對象。
3.pos: 文本中正則表達式開始搜索的索引。值與Pattern.match()和Pattern.seach()方法的同名參數相同。
4.endpos: 文本中正則表達式結束搜索的索引。值與Pattern.match()和Pattern.seach()方法的同名參數相同。
5.lastindex: 最後一個被捕獲的分組在文本中的索引。若是沒有被捕獲的分組,將爲None。
6.lastgroup: 最後一個被捕獲的分組的別名。若是這個分組沒有別名或者沒有被捕獲的分組,將爲None。
方法:
1.group([group1, …]): 
得到一個或多個分組截獲的字符串;指定多個參數時將以元組形式返回。group1可使用編號也可使用別名;編號0表明整個匹配的子串;不填寫參數時,返回group(0);沒有截獲字符串的組返回None;截獲了屢次的組返回最後一次截獲的子串。
2.groups([default]): 
以元組形式返回所有分組截獲的字符串。至關於調用group(1,2,…last)。default表示沒有截獲字符串的組以這個值替代,默認爲None。
3.groupdict([default]): 
返回以有別名的組的別名爲鍵、以該組截獲的子串爲值的字典,沒有別名的組不包含在內。default含義同上。
4.start([group]): 
返回指定的組截獲的子串在string中的起始索引(子串第一個字符的索引)。group默認值爲0。
5.end([group]): 
返回指定的組截獲的子串在string中的結束索引(子串最後一個字符的索引+1)。group默認值爲0。
6.span([group]): 
返回(start(group), end(group))。
7.expand(template): 
將匹配到的分組代入template中而後返回。template中可使用\id或\g<id>、\g<name>引用分組,但不能使用編號0。\id與\g<id>是等價的;但\10將被認爲是第10個分組,若是你想表達\1以後是字符'0',只能使用\g<1>0。
import re
m = re.match(r'(\w+) (\w+)(?P<sign>.*)', 'hello world!')
 
print "m.string:", m.string
print "m.re:", m.re
print "m.pos:", m.pos
print "m.endpos:", m.endpos
print "m.lastindex:", m.lastindex
print "m.lastgroup:", m.lastgroup
 
print "m.group(1,2):", m.group(1, 2)
print "m.groups():", m.groups()
print "m.groupdict():", m.groupdict()
print "m.start(2):", m.start(2)
print "m.end(2):", m.end(2)
print "m.span(2):", m.span(2)
print r"m.expand(r'\2 \1\3'):", m.expand(r'\2 \1\3')
 
### output ###
# m.string: hello world!
# m.re: <_sre.SRE_Pattern object at 0x016E1A38>
# m.pos: 0
# m.endpos: 12
# m.lastindex: 3
# m.lastgroup: sign
# m.group(1,2): ('hello', 'world')
# m.groups(): ('hello', 'world', '!')
# m.groupdict(): {'sign': '!'}
# m.start(2): 6
# m.end(2): 11
# m.span(2): (6, 11)
# m.expand(r'\2 \1\3'): world hello!
2.3. Pattern
Pattern對象是一個編譯好的正則表達式,經過Pattern提供的一系列方法能夠對文本進行匹配查找。
Pattern不能直接實例化,必須使用re.compile()進行構造。
Pattern提供了幾個可讀屬性用於獲取表達式的相關信息:
1.pattern: 編譯時用的表達式字符串。
2.flags: 編譯時用的匹配模式。數字形式。
3.groups: 表達式中分組的數量。
4.groupindex: 以表達式中有別名的組的別名爲鍵、以該組對應的編號爲值的字典,沒有別名的組不包含在內。
import re
p = re.compile(r'(\w+) (\w+)(?P<sign>.*)', re.DOTALL)
 
print "p.pattern:", p.pattern
print "p.flags:", p.flags
print "p.groups:", p.groups
print "p.groupindex:", p.groupindex
 
### output ###
# p.pattern: (\w+) (\w+)(?P<sign>.*)
# p.flags: 16
# p.groups: 3
# p.groupindex: {'sign': 3}
實例方法[ | re模塊方法]:
1.match(string[, pos[, endpos]]) | re.match(pattern, string[, flags]): 
這個方法將從string的pos下標處起嘗試匹配pattern;若是pattern結束時仍可匹配,則返回一個Match對象;若是匹配過程當中pattern沒法匹配,或者匹配未結束就已到達endpos,則返回None。 
pos和endpos的默認值分別爲0和len(string);re.match()沒法指定這兩個參數,參數flags用於編譯pattern時指定匹配模式。 
注意:這個方法並非徹底匹配。當pattern結束時若string還有剩餘字符,仍然視爲成功。想要徹底匹配,能夠在表達式末尾加上邊界匹配符'$'。 
示例參見2.1小節。
2.search(string[, pos[, endpos]]) | re.search(pattern, string[, flags]): 
這個方法用於查找字符串中能夠匹配成功的子串。從string的pos下標處起嘗試匹配pattern,若是pattern結束時仍可匹配,則返回一個Match對象;若沒法匹配,則將pos加1後從新嘗試匹配;直到pos=endpos時仍沒法匹配則返回None。 
pos和endpos的默認值分別爲0和len(string));re.search()沒法指定這兩個參數,參數flags用於編譯pattern時指定匹配模式。 
# encoding: UTF-8 
import re 
 
# 將正則表達式編譯成Pattern對象 
pattern = re.compile(r'world') 
 
# 使用search()查找匹配的子串,不存在能匹配的子串時將返回None 
# 這個例子中使用match()沒法成功匹配 
match = pattern.search('hello world!') 
 
if match: 
    # 使用Match得到分組信息 
    print match.group() 
 
### 輸出 ### 
# world
3.
4.split(string[, maxsplit]) | re.split(pattern, string[, maxsplit]): 
按照可以匹配的子串將string分割後返回列表。maxsplit用於指定最大分割次數,不指定將所有分割。 
import re
 
p = re.compile(r'\d+')
print p.split('one1two2three3four4')
 
### output ###
# ['one', 'two', 'three', 'four', '']
5.
6.findall(string[, pos[, endpos]]) | re.findall(pattern, string[, flags]): 
搜索string,以列表形式返回所有能匹配的子串。 
import re
 
p = re.compile(r'\d+')
print p.findall('one1two2three3four4')
 
### output ###
# ['1', '2', '3', '4']
7.
8.finditer(string[, pos[, endpos]]) | re.finditer(pattern, string[, flags]): 
搜索string,返回一個順序訪問每個匹配結果(Match對象)的迭代器。 
import re
 
p = re.compile(r'\d+')
for m in p.finditer('one1two2three3four4'):
    print m.group(),
 
### output ###
# 1 2 3 4
9.
10.sub(repl, string[, count]) | re.sub(pattern, repl, string[, count]): 
使用repl替換string中每個匹配的子串後返回替換後的字符串。 
當repl是一個字符串時,可使用\id或\g<id>、\g<name>引用分組,但不能使用編號0。 
當repl是一個方法時,這個方法應當只接受一個參數(Match對象),並返回一個字符串用於替換(返回的字符串中不能再引用分組)。 
count用於指定最多替換次數,不指定時所有替換。 
import re
 
p = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'
 
print p.sub(r'\2 \1', s)
 
def func(m):
    return m.group(1).title() + ' ' + m.group(2).title()
 
print p.sub(func, s)
 
### output ###
# say i, world hello!
# I Say, Hello World!
11.
12.subn(repl, string[, count]) |re.sub(pattern, repl, string[, count]): 
返回 (sub(repl, string[, count]), 替換次數)。 
import re
 
p = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'
 
print p.subn(r'\2 \1', s)
 
def func(m):
    return m.group(1).title() + ' ' + m.group(2).title()
 
print p.subn(func, s)
 
### output ###
# ('say i, world hello!', 2)
# ('I Say, Hello World!', 2)



多選結構
多選結構在傳統型NFA中, 既不是匹配優先也不是忽略優先。而是按照順序進行的。因此有以下的利用方式

1.在結果保證正確的狀況下,應該優先的去匹配更可能出現的結果。將可能性大的分支儘量放在靠前。
2.不能濫用多選結構,由於當匹配到多選結構時,緩存會記錄下相應數目的備用狀態。舉例子:[abcdef]和‘a|b|c|d|e|f’這兩個表達式,雖然都能完成你的某個目的,可是儘可能選擇字符型數組,由於後者會在每次比較時創建6個備用狀態,浪費資源。
一些優化的理念和技巧
平衡法則
好的正則表達式需尋求以下平衡:
1.只匹配指望的文本,排除不指望的文本。(善於使用非捕獲型括號,節省資源)
2.必須易於控制和理解。避免寫整天書。。
3.使用NFA引擎,必需要保證效率(若是可以匹配,必須很快地返回匹配結果,若是不能匹配,應該在儘量短的時間內報告匹配失敗。)

處理不指望的匹配
在處理過程當中,咱們老是習慣於使用星號等非硬性規定的量詞(實際上是個很差的習慣),
這樣的結果可能致使咱們使用的匹配表達式中沒有必須匹配的字符,例子以下:
'[0-9]?[^*]*\d*'    #只是舉個例子,沒有實際意義。
上面的式子就是這種狀況,在目標文本是「理想」時,可能出現不了什麼問題,可是若是自己數據有問題。那麼這個式子的匹配結果就徹底不可預知。 
緣由就在於他沒有一部分是必須的!它匹配任何內容都是成功的。。。 
對數據的瞭解和假設
其實在處理不少數據的時候,咱們的操做數據狀況都是不同的, 有時會很規整,那麼咱們能夠省掉考慮複雜表達式的狀況, 可是反過來,當來源很雜亂的時候,就須要思考多一些,對各類可能的情形作相應的處理。

引擎中通常存在的優化項
編譯緩存
反覆使用編譯對象時,應該在使用前,使用re.compile()方法來進行編譯,這樣在後面調用時沒必要每次從新編譯。節省時間。尤爲是在循環體中反覆調用正則匹配時。
錨點優化
配合一些引擎的優化,應儘可能將錨點單獨凸顯出來。對比^a|^b,其效率便不如^(a|b)
一樣的道理,系統也會處理行尾錨點優化。因此在寫相關正則時,若是有可能的話,將錨點使用出來。
量詞優化
引擎中的優化,會對如.* 這樣的量詞進行統一對待,而不是按照傳統的回溯規則,因此,從理論上說'(?:.)*' 和'.*'是等價的,不過具體到引擎實現的時候,則會對'.*'進行優化。速度就產生了差別。
消除沒必要要括號以及字符組
這個在python中是否有 未知。只是在支持的引擎中,會對如[.]中轉化成\.,由於顯而後者的效率更高(字符組處理引發額外開銷)

以上是一些引擎帶的優化,天然其實是咱們沒法控制的的,不過了解一些後,對咱們後面的一些處理和使用有很大幫助。
其餘技巧和補充內容
過分回溯問題
消除指數級匹配
形以下面:
(\w+)*
這種狀況的表達式,在匹配長文本的時候會遇到什麼問題呢,若是在文本匹配失敗時(別忘了,若是失敗,則說明已經回溯了 全部的可能),想象一下,*號退一個狀態,裏面的+號就包括其他的 全部狀態,驗證都失敗後,回到外面,*號 退到倒數第二個備用狀態,再進到括號內,+號又要回溯一邊比上一輪差1的 備用狀態數,當字符串很長時, 就會出現指數級的回溯總數。系統就會'卡死'。甚至當有匹配時,這個匹配藏在回溯總數的中間時,也是會形成卡死的狀況。因此,使用NFA的引擎時,必需要注意這個問題!
咱們採用以下思路去避免這個問題:
佔有優先量詞(python中使用前向斷言加反向引用模擬)
道理很簡單,既然龐大的回溯數量都是被儲存的備用狀態致使的,那麼咱們直接使引擎放棄這些狀態。說究竟是擺脫(regex*)* 這種形式。
import re
re_lx = re.compile(r'(?=(\w+))\1*\d')
效率測試代碼
在測試表達式的效率時,可藉助如下代碼比較所需時間。在兩個可能的結果中擇期優者。
import reimport time
re_lx1 = re.compile(r'your_re_1')
re_lx2 = re.compile(r'your_re_2')

starttime = time.time()
repeat_time = 100for i in range(repeat_time):
    s='test text'*10000
    result = re_lx1.search(s)
time1 = time.time()-starttime
print(time1)

starttime = time.time()for i in range(repeat_time):
    s='test text'*10000
    result = re_lx2.search(s)
time2 = time.time()-starttime
print(time2)
量詞等價轉換
如今來看看大括號量詞的效率問題
1,當大括號修飾的對象是相似於字符數組或者\d這種 非肯定性字符時,使用大括號效率高於重複疊加對象。即:
\d{5}優於\d\d\d\d\d
經測試在python中後者優於前者。會快不少.
2,可是當重複的字符時肯定的某一個字符時,則簡單的重複疊加對象的效率會高一些。這是由於引擎會對單純的字符串內部優化(雖然咱們不知道具體優化是如何作到的)
aaaaa 優於a{5}
整體上說'\d' 確定是慢於'1'
我使用的python3中的re模塊,經測試,不使用量詞會快。
綜上,python中整體上使用量詞不如簡單的列出來!(與書中不一樣!)
錨點優化的利用
下面這個例子假設出現匹配的內容在字符串對象的結尾,那麼下面的第一個表達式是快於第二個表達式的,緣由在於前者有錨點的優點。
re_lx1 = re.compile(r'\d{5}$')    
re_lx2 = re.compile(r'\d{5}')    #前者快,有錨點優化
排除型數組的利用
繼續,假設咱們要匹配一段字符串中的5位數字,會有以下兩個表達式供選擇:
通過分析,咱們發現\w是包含\d的,當使用匹配優先時,前面的\w會包含數字,之因此能匹配成功,或者肯定失敗,是後面的\d迫使前面的量詞交還一些字符。
知道這一點,咱們應該儘可能避免回溯,一個順其天然的想法就是不讓前面的匹配優先量詞涉及到\d
re_lx1 = re.compile(r'^\w+(\d{5})')
re_lx2 = re.compile(r'^[^\d]+\d{5}')    #優於上面的表達式
整體來講,在咱們沒有時間去深刻研究模塊代碼的時候,只能經過嘗試和反覆修改來獲得最終的複合預期的表達式。
常識優化措施
然而咱們利用可能的提高效果去嘗試修改的時候頗有可能 拔苗助長 , 由於某些咱們看來緩慢的回溯在正則引擎內部會進行必定的優化 ,
「取巧」的修改又可能會關閉或者避開了這些優化,因此結果也許會令咱們很失望。
如下是書中提到的一些 常識性優化措施:
避免從新編譯(循環外建立對象)
使用非捕獲型括號(節省捕獲時間和回溯時狀態的數量)
善用錨點符號
不濫用字符組
提取文本和錨點。將他們從可能的多選分支結構中提取出來,會提取速度。
最可能的匹配表達式放在多選分支前面


一個很好用的核心公式
’opening normal*(special normal*)* closing‘
這個公式 特別用來對於匹配在兩個特殊分界部分(可能不是一個字符)內的normal文本,special則是處理當分界部分也許和normal部分混亂的狀況。
有以下的三點避免這個公式無休止匹配的發生。
1.special部分和normal部分匹配的開頭不能重合。必定保證這兩部分在任何狀況下不能匹配相同的內容,否則在沒法出現匹配時遍歷全部狀況,此時引擎的路徑就不能肯定。
2.normal部分必須匹配至少一個字符
3.special部分必須是固定長度的
舉個例子:
[^\\"]+(\\.[^\\"]+)* #匹配兩個引號內的文本,可是不包括被轉義的引號
[參考博客](https://www.cnblogs.com/sthu/p/7639589.html)
相關文章
相關標籤/搜索