python中,命令行和腳本等,裏面都會對轉義符號作處理,此時的字符串會和正則表達式的引擎產生衝突。即在python中字符串'\n'會被認爲是換行符號,這樣的話傳入到re模塊中時便再也不是‘\n’這字面上的兩個符號,而是一個換行符。因此,咱們在傳入到正則引擎時,必須讓引擎單純的認爲是一個'\'和一個'n',因此須要加上轉義符成爲'\\n',針對這個狀況,python中使用raw_input方式,在字符串前加上r,使字符串中的轉義符再也不特殊處理(即python中不處理,通通丟給正則引擎來處理),那麼換行符就是r'\n' html
. #普通模式下,匹配除換行符外的任意字符。(指定DOTALL標記以匹配全部字符)
* #匹配前面的對象0個或多個。千萬不要忽略這裏的0的狀況。 + #匹配前面的對象1個或多個。這裏面的重點是至少有一個。 ? #匹配前面的對象0個或1個。 {m} #匹配前面的對象m次 {m,n} #匹配前面的對象最少m次,最多n次。
^ #匹配字符串開頭位置,MULTILINE標記下,能夠匹配任何\n以後的位置 $ #匹配字符串結束位置,MULTILINE標記下,能夠匹配任何\n以前的位置
\m m是數字,所謂的反向引用,即引用前面捕獲型括號內的匹配的對象。數字是對應的括號順序。 \A 只匹配字符串開頭 \b 能夠理解一個錨點的符號,此符號匹配的是單詞的邊界。這其中的word定義爲連續的字母,數字和下劃線。 準確的來講,\b的位置是在\w和\W的交界處,固然還有字符串開始結束和\w之間。 \B 和\b對應,自己匹配空字符,可是其位置是在非"邊界"狀況下. 好比r'py\B'能夠匹配'python',但不能匹配'py,','py.' 等等 \d 匹配數字 \D 匹配非數字 \s 未指定UNICODE和LOCALE標記時,等同於[ \t\n\r\f\v](注意\t以前是一個空格,表示也匹配空格) \S 與\s相反 \w 未指定UNICODE和LOCALE標記時,等同於[a-zA-Z0-9_] \W 和\w相反 \Z 只匹配字符串結尾 其餘的一些python支持的轉移符號也都有支持,如前面的'\t'
[]
尤爲注意,這個字符集最終 只匹配一個字符(既不是空,也不是一個以上!),因此前面的一些量詞限定符,在這裏失去了原有的意義。
另外,'-'符號放在兩個字符之間的時候,表示ASCII字符之間的全部字符,如[0-9],表示0到9.
而放在字符集開頭或者結尾,或者被'\'轉義時候,則只是表示特指'-'這個符號
最後,當在開頭的地方使用'^',表示排除型字符組. python
(...) 普通捕獲型括號,能夠被\number引用。
(?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) #這兩個編譯表達式等價
(?:......) #非捕獲型括號,此括號不記錄捕獲內容,可節省空間
(?P<name>...) #此捕獲型括號可使用name來調用,而沒必要依賴數字。使用(?P=name)調用。
(?#...) #註釋型括號,此括號徹底被忽略
(?=...) #positive lookahead assertion 若是後面是括號中的,則匹配成功
(?!...) #negative lookahead assertion 若是後面不是括號中的,則匹配成功
(?<=...) #positive lookbehind assertion 若是前面是括號中的,則匹配成功
(?<!...) #negative lookbehind assertion 若是前面不是括號中的,則匹配成功
#以上四種類型的斷言,自己均不匹配內容,只是告知正則引擎是否開始匹配或者中止。
#另外在後兩種後項斷言中,必須爲定長斷言。
(?(id/name)yes-pattern|no-pattern)
#若有由id或者name指定的組存在的話,將會匹配yes-pattern,不然將會匹配no-pattern,一般狀況下no-pattern能夠省略。
如'??'會先匹配沒有的狀況,而後纔是1個對象的狀況。而{m,n}?則是優先匹配m個對象,而不是佔多的n個對象。
正則表達式
python屬於perl風格,屬於傳統型NFA引擎,與此相對的是POSIX NFA和DFA等引擎。因此大部分討論都針對傳統型NFA
express
NFA是基於表達式主導的引擎,同時,傳統型NFA引擎會在找到第一個符合匹配的狀況下當即中止:即獲得匹配以後就中止引擎。
而POSIX NFA 中不會馬上中止,會在全部可能匹配的結果中尋求最長結果。這也是有些bug在傳統型NFA中不會出現,可是放到後者中,會暴露出來。
引伸一點,NFA學名爲」非肯定型有窮自動機「,DFA學名爲」肯定型有窮自動機「
這裏的非肯定和肯定均是對被匹配的目標文本中的字符來講的,在NFA中,每一個字符在一次匹配中即便被檢測經過,也不能肯定他是否真正經過,由於NFA中會出現回溯!甚至不止一兩次。圖例見後面例子。而在DFA中,因爲是目標文本主導,全部對象字符只檢測一遍,到文本結束後,過就是過,不過就不過。這也就是」肯定「這個說法的緣由。 數組
做爲對比說明,下面是目標文本不能匹配時,引擎走過的路徑:
以下圖,咱們看到此時POSIX NFA和傳統型NFA的匹配路徑是一致的。 緩存
以上的例子引起了一個匹配時的思考,不少時候咱們應該儘可能避免使用'.*' ,由於其老是能夠匹配到最末或者行尾,浪費資源。
既然咱們只尋求引號之間的數據,每每能夠藉助排除型數組來完成工做。
此例中,使用'[^'']*'這個來代替'.*'的做用顯而易見,咱們只匹配非引號的內容,那麼遇到第一個引號便可退出*號控制權。 學習
'\w+:'
這個表達式在進行匹配時的流程是這樣的,會優先去匹配全部的符合\w的字符,假如字符串的末尾沒有':',即匹配沒有找到冒號,此時觸發回溯機制,他會迫使前面的\w+釋放字符,而且在交還的字符中從新嘗試與':'做比對。
可是問題出如今這裏: \w是不包含冒號的,顯然不管如何都不會匹配成功,但是依照回溯機制,引擎仍是得硬着頭皮往前找,這就是對資源的浪費。
因此咱們就須要避免這種回溯,對此的方法就是將前面匹配到的內容固化,不令其存儲備用狀態!,那麼引擎就會由於沒有備用狀態可用而只得結束匹配過程。大大減小回溯的次數! 測試
雖然python中不支持,但書中提供了利用前向斷言來模擬固化過程。 優化
(?=(...))\1自己, 斷言表達式中的結果是不會保存備用狀態的,並且他也不匹配具體字符,可是經過巧妙的 添加一個捕獲型括號來反向引用這個結果,就達到了固化分組的效果!對應上面的例子則是:
'(?=(\w+))\1:'
多選結構在傳統型NFA中, 既不是匹配優先也不是忽略優先。而是按照順序進行的。因此有以下的利用方式
spa
在處理過程當中,咱們老是習慣於使用星號等非硬性規定的量詞(實際上是個很差的習慣),
這樣的結果可能致使咱們使用的匹配表達式中沒有必須匹配的字符,例子以下:
'[0-9]?[^*]*\d*' #只是舉個例子,沒有實際意義。上面的式子就是這種狀況,在目標文本是「理想」時,可能出現不了什麼問題,可是若是自己數據有問題。那麼這個式子的匹配結果就徹底不可預知。
(\w+)*這種狀況的表達式,在匹配長文本的時候會遇到什麼問題呢,若是在文本匹配失敗時(別忘了,若是失敗,則說明已經回溯了 全部的可能),想象一下,*號退一個狀態,裏面的+號就包括其他的 全部狀態,驗證都失敗後,回到外面,*號 退到倒數第二個備用狀態,再進到括號內,+號又要回溯一邊比上一輪差1的 備用狀態數,當字符串很長時, 就會出現指數級的回溯總數。系統就會'卡死'。甚至當有匹配時,這個匹配藏在回溯總數的中間時,也是會形成卡死的狀況。因此,使用NFA的引擎時,必需要注意這個問題!
咱們採用以下思路去避免這個問題:
佔有優先量詞(python中使用前向斷言加反向引用模擬)
道理很簡單,既然龐大的回溯數量都是被儲存的備用狀態致使的,那麼咱們直接使引擎放棄這些狀態。說究竟是擺脫(regex*)* 這種形式。
import re re_lx = re.compile(r'(?=(\w+))\1*\d')
在測試表達式的效率時,可藉助如下代碼比較所需時間。在兩個可能的結果中擇期優者。
import re import time re_lx1 = re.compile(r'your_re_1') re_lx2 = re.compile(r'your_re_2') starttime = time.time() repeat_time = 100 for 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)
下面這個例子假設出現匹配的內容在字符串對象的結尾,那麼下面的第一個表達式是快於第二個表達式的,緣由在於前者有錨點的優點。
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}') #優於上面的表達式
整體來講,在咱們沒有時間去深刻研究模塊代碼的時候,只能經過嘗試和反覆修改來獲得最終的複合預期的表達式。
避免從新編譯(循環外建立對象) 使用非捕獲型括號(節省捕獲時間和回溯時狀態的數量) 善用錨點符號 不濫用字符組 提取文本和錨點。將他們從可能的多選分支結構中提取出來,會提取速度。 最可能的匹配表達式放在多選分支前面
舉個例子:
[^\\"]+(\\.[^\\"]+)* #匹配兩個引號內的文本,可是不包括被轉義的引號