正則表達(dá)式有很多流派,也有很多的特性,不同的語言支持度也是不一樣的。本篇文章是寫Python中的正則表達(dá)式的用法的,介紹了一些可用特性,也指出了某些特性是不支持的。
本篇文章僅為學(xué)習(xí)筆記,不對(duì)正則表達(dá)式做深入研究,只想用最短的時(shí)間入門正則。
實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)。你以為的并不是你以為的,如有問題還是要寫一個(gè)例子用多個(gè)工具驗(yàn)證下最好。
字符組 [ ]
在同一位置可能出現(xiàn)的各種字符,也可以稱之為字符集。
寫法有很多種。有原始寫法,然后是范圍表示法,然后是排除型字符組,然后是字符組簡記法,最后還有一個(gè)字符組運(yùn)算(Python不支持)。
[3645970128] --> [0123456789] --> [0-9] --> \d
[0-9A-Za-z_] --> [\dA-Za-z_] --> \w
"-"用來表示范圍 范圍的確定依靠碼值來確定,一般據(jù)ASCⅡ表值。
排除型字符組由脫字符"^"來指定,"^"排在第一位,緊靠在"["之后,需要排除的按上面寫法寫在"^"之后。
[^0-9] # 排除數(shù)字,也可以寫成[^\d]
如果需要排除"-",則必須將"-"寫在開頭"^"之后,如果一定要寫在后面,則必須用"\"轉(zhuǎn)義。
字符組簡記 | 說明 |
---|---|
\d | 數(shù)字(digit) [0-9] |
\w | 單詞(word) [0-9A-Za-z_] 字母數(shù)字下劃線 |
\s | 空白(space) [ \t\r\n\f\v] 空格、制表符、回車、換行等顯示空白字符 |
\D 、\W 、\S是與之對(duì)應(yīng)的排除型字符組簡記。
以上說的\d 、\w 、\s的匹配規(guī)則都是針對(duì)ASCⅡ編碼規(guī)則而言的,也叫ASCⅡ匹配規(guī)則。在Unicode編碼中,全角數(shù)字0、1、2之類的也算數(shù)字,也可以由\d匹配;中文字符也可以算是單詞字符,由\w匹配;同樣全角空格也由\s匹配。
Python3默認(rèn)采用Unicode匹配規(guī)則,但也可以顯示指定采用ASCⅡ匹配規(guī)則(表達(dá)式最開始用(?a)指定ASCⅡ模式)。
另外還有一個(gè)POSIX字符組(POSIX Character Class)。POSIX(Portable Operating System Interface for uniX),它是含有正則表達(dá)式規(guī)范的一系列規(guī)范。這種字符組只在java、php、ruby中支持,經(jīng)過測(cè)試以及查閱資料并沒有顯示Python是支持的,不過也有可能是POSIX字符組需要變化一下才能使用,就像其他語言那樣。
量詞
以上說的都是匹配單個(gè)字符,如果多了怎么辦。所以就出現(xiàn)了量詞。
量詞限定之前的元素的出現(xiàn),這個(gè)元素可能是一個(gè)字符,也可能是一個(gè)字符組,還可以是一個(gè)表達(dá)式。
一般形式量詞 | 說明 |
---|---|
{n} | 之前的元素必須出現(xiàn)n次 |
{m,n} | 之前的元素最少出現(xiàn)m次,最多出現(xiàn)n次 |
{m,} | 之前的元素最少出現(xiàn)m次,最高沒有限制 |
{0,n} | 之前的元素可以不出現(xiàn),也可以出現(xiàn),最多出現(xiàn)n次 |
常用量詞 | 等價(jià)形式 | 說明 |
---|---|---|
* | {0,} | 可能出現(xiàn),也可能不出現(xiàn),出現(xiàn)次數(shù)沒有上限 |
+ | {1,} | 至少出現(xiàn)1次,最多沒有限制 |
? | {0,1} | 可以不出現(xiàn),也可以就出現(xiàn)1次 |
所有的量詞默認(rèn)都是貪婪量詞,能匹配的,絕不停止匹配。它會(huì)優(yōu)先嘗試匹配,并記下這個(gè)狀態(tài),以便將來反悔。這個(gè)返回的過程稱之為回溯。
量詞也可以變成懶惰的,稱之為懶惰量詞,能不匹配就不匹配,如果之后的表達(dá)式匹配失敗,則會(huì)回溯進(jìn)行匹配。變成懶惰量詞是在現(xiàn)有量詞后加上?
。
還有一種量詞稱之為占有量詞。它和貪婪量詞很像,但是占有量詞不會(huì)進(jìn)行回溯,優(yōu)點(diǎn)是速度快,但是失敗也會(huì)很快。變成占有量詞是在現(xiàn)有量詞后加上+
。
分組 ( )
分組,顧名思義就是將某些東西分成一組,對(duì)外來看是一個(gè)整體。通過()
將表達(dá)式括起來,此時(shí)表達(dá)式就是一個(gè)整體一個(gè)可整體操作的元素。括號(hào)內(nèi)的表達(dá)式稱之為“子表達(dá)式”。
多選結(jié)構(gòu)
多選結(jié)構(gòu)的形式是(···|···)
,在括號(hào)中以豎線|
分隔開多個(gè)子表達(dá)式,這些子表達(dá)式也叫多選分支;在一個(gè)多選結(jié)構(gòu)中,多選分支的數(shù)目沒有限制。在匹配時(shí),整個(gè)多選結(jié)構(gòu)被視為單個(gè)元素,只要某個(gè)子表達(dá)式能夠匹配,整個(gè)多選結(jié)構(gòu)的匹配就成功。
一般而言ab|cd
依舊是多選結(jié)構(gòu),等價(jià)于(ab|cd)
,建議使用后者,更加明確,也能夠避免一些錯(cuò)誤。例如^ab|cd$
其實(shí)是(^ab|cd$)
而不是^(ab|cd)$
,因?yàn)樨Q線|
的優(yōu)先級(jí)很低。
多選結(jié)構(gòu)的匹配順序問題。比如表達(dá)式(aaa|aaabbb)去匹配aaabbb,匹配結(jié)果到底是aaa還是aaabbb呢?Python中多選結(jié)構(gòu)都會(huì)優(yōu)先選擇最左側(cè)的分支,所以匹配出來是aaa。不過在實(shí)際使用中請(qǐng)避免這種情況,分支中存在重復(fù)會(huì)大大增加回溯的計(jì)算量。
引用分組
分組之后,正則表達(dá)式會(huì)保存每一個(gè)分組真正匹配的文本(不是正則表達(dá)式可匹配的文本),匹配完成后,可通過編號(hào)進(jìn)行引用當(dāng)時(shí)捕獲的內(nèi)容。
如果出現(xiàn)多個(gè)嵌套括號(hào),那編號(hào)的計(jì)數(shù)規(guī)則是怎樣的呢?首先整個(gè)正則表達(dá)式默認(rèn)存在一個(gè)的編號(hào)為0的分組,其他分組的編號(hào)是依據(jù)從左到右開括號(hào)出現(xiàn)的順序決定的,第幾個(gè)括號(hào)就是第幾組。
-
反向引用
在正則表達(dá)式內(nèi)部引用之前的捕獲分組匹配的文本,之前捕獲分組中錨點(diǎn)表示的位置信息不會(huì)被保留下來。
引用形式:
\num
,其中num表示所引用分組的編號(hào)。 -
sub替換中引用
re.sub(pattern,replacement,string),在replacement中使用引用分組。引用形式同樣是:
\num
。 -
匹配結(jié)果中g(shù)roup引用
對(duì)匹配完成后,可對(duì)匹配結(jié)果對(duì)象調(diào)用group(num)獲取分組中匹配的數(shù)據(jù)。
\num引用的二義性問題:\10
是第10個(gè)引用還是第1個(gè)引用后再加一個(gè)0呢?
Python中將其解釋為第10個(gè)引用,可通過\g<num>
,消除二義性。
注:反向引用只引用之前的捕獲分組匹配的文本,之前捕獲分組中的錨點(diǎn)表示的位置信息不會(huì)被保留下來。
命名分組
鑒于數(shù)字編號(hào)不夠直觀,并且括號(hào)多了難免混淆,所以就有了命名分組的出現(xiàn)。Python中用(?P<name>...)
來分組,其中name是分組的名稱。
反向引用必須使用(?P=name)
來引用;替換則需要寫作\g<name>
;匹配結(jié)果中則使用group(name)
。
非捕獲分組
無論是否需要分組,只要出現(xiàn)了括號(hào),正則表達(dá)式再匹配的時(shí)候就會(huì)把括號(hào)內(nèi)的子表達(dá)式存儲(chǔ)起來,提供引用。如果并不需要引用,保存這些信息無疑會(huì)影響正則表達(dá)式的性能。
正則表達(dá)式提供了非捕獲分組(?:...)
,這樣的括號(hào)叫做非捕獲型括號(hào),他只能限定量詞的作用范圍,不捕獲任何文本。在引用分組時(shí),分組的編號(hào)同樣會(huì)按照開括號(hào)出現(xiàn)的順序從左到右計(jì)數(shù),非捕獲分組會(huì)略過。
原子分組
另一種非捕獲分組就是原子分組(?>...)
。這種分組可以將回溯操作關(guān)閉,但它只針對(duì)原子分組內(nèi)的部分,而不針對(duì)整個(gè)正則表達(dá)式。經(jīng)過測(cè)試發(fā)現(xiàn)這個(gè)表達(dá)式?>
是不識(shí)別的。但是一篇文章(英文原版)給出了一個(gè)等價(jià)方案:
可以通過使用零寬度先行assert(
(?= RE)
)來模擬它,它從當(dāng)前點(diǎn)匹配相同的語義想要,將一個(gè)命名的組((?P RE)
)放在lookahead中,然后使用一個(gè)命名的反引用((?P = name)
恰恰是零寬度斷言匹配.組合在一起,這給了你相同的語義,代價(jià)是創(chuàng)建一個(gè)額外的匹配組和很多語法.
匹配模式
所謂匹配模式,指的是匹配時(shí)使用的規(guī)則。設(shè)定特定的模式,可能會(huì)改變對(duì)正則表達(dá)式的識(shí)別,也可能會(huì)改變正則表達(dá)式中字符的匹配規(guī)定。常用的匹配模式一共有4種:不區(qū)分大小寫模式、單行模式、多行模式、注釋模式。通常有兩種指定方式:以模式修飾符指定(?modifier)
,或者以預(yù)定義的常量作為特殊參數(shù)傳入來指定。
Python中模式修飾符只要出現(xiàn),無論在什么位置,都對(duì)整個(gè)正則表達(dá)式生效。Python中不支持失效修飾符(?-modifier)
。
不區(qū)分大小寫模式
不區(qū)分大小寫模式的修飾符是i
(case Insensitive),則(?i)it
可以匹配的有it It iT IT
。
單行模式
修飾符是s
(Single line)。
這種模式下所有的文本只在一行里,換行符只是其中一個(gè)普通字符。單行模式影響的是點(diǎn)號(hào).
的匹配規(guī)則。
多行模式
修飾符是m
(Multiline)。
多行模式影響的是^
和$
的匹配規(guī)則。
注釋模式
修飾符是x
(eXtended mode 擴(kuò)展模式)。
有時(shí)正則表達(dá)式可能非常復(fù)雜,不但難于編寫和閱讀,也難以維護(hù)。python中支持使用(?#comment)
的記法添加注釋;還有一種是使用注釋模式,此時(shí)正則表達(dá)式對(duì)應(yīng)的字符串可以跨越多行,注釋則以#comment
的形式添加在正則表達(dá)式內(nèi)部,每一條注釋從#
開始,到行末結(jié)束,同時(shí)還可以使用縮進(jìn)表示層級(jí)結(jié)構(gòu)更加方便閱讀和維護(hù)。
斷言
常見的斷言有三類:單詞邊界、行起始/結(jié)束位置、環(huán)視。斷言并不真正的匹配文本,他匹配的是位置,他只負(fù)責(zé)判斷在某個(gè)位置左/右側(cè)的文本是否符合要求。
單詞邊界
單詞邊界記為\b
,他匹配的是“單詞邊界”位置,而不是字符。也就是說\b
能夠匹配這樣的位置:一邊是單詞字符,另一邊不是單詞字符(包括沒有字符)。不同的模式下\w
不同,所以\b
也就不同。
另外還有\B
非單詞邊界。
行起始/結(jié)束為位置
^
匹配字符串開始的位置。
$
匹配字符串結(jié)束的位置。
前面已經(jīng)說了:多行模式影響的是^
和$
的匹配規(guī)則。
在不指定多行模式的情況下:^
匹配的是整個(gè)字符串的開始位置,$
匹配的是整個(gè)字符串的結(jié)束位置。
當(dāng)在多行模式下:^
匹配的是字符串的起始位置以及換行之后行終止符之后的位置,如果字符串末尾出現(xiàn)了行終止符,則依舊匹配行終止符之后的那個(gè)位置;$
匹配的是字符串的結(jié)束位置,但是這個(gè)位置是在字符結(jié)束之后行終止符之前的位置。
python中還有一個(gè)特殊標(biāo)記\Z
,它和$
類似,但是他不受多行模式的影響,\Z
不管行終止符,他匹配的是整個(gè)字符串的結(jié)束位置,在行終止符之后的位置。
^
和$
的另一個(gè)特點(diǎn)是在進(jìn)行正則表達(dá)式替換的時(shí)候并不會(huì)被替換。也就是說,在起始和結(jié)束位置進(jìn)行替換,只會(huì)在起始和結(jié)束位置添加一些字符,位置本身依然存在。
環(huán)視
環(huán)視是對(duì)匹配字符周圍字符的驗(yàn)證,他匹配的并不是字符,而是位置。
環(huán)視大致分為以下四種:
名稱 | 又稱 | 說明 |
---|---|---|
肯定順序環(huán)視 | 正前瞻 | ...(?=...) |
否定順序環(huán)視 | 反前瞻 | ...(?!...) |
肯定逆序環(huán)視 | 正后顧 | (?<=...)... |
否定逆序環(huán)視 | 反后顧 | (?<!...)... |
肯定環(huán)視和否定環(huán)視的區(qū)別就是:肯定環(huán)視要想判斷成功,字符串中必須有字符由環(huán)視結(jié)構(gòu)中的表達(dá)式匹配;而否定環(huán)視要判斷成功卻有兩種情況1.字符串的字符不能匹配2.根本就沒有字符,這個(gè)位置可能是起始或結(jié)束位置。
環(huán)視的組合:
-
包含
環(huán)視中包含環(huán)視,這個(gè)和環(huán)視的并列還不太一樣,這個(gè)外層環(huán)視是限定的位置,但是這個(gè)內(nèi)層環(huán)視是限定外層環(huán)視的。
(?=...(?!...))
-
多環(huán)視并列
環(huán)視的并列要求每一個(gè)環(huán)視對(duì)當(dāng)前位置的匹配都必須成功,而各個(gè)環(huán)視之間是沒有任何聯(lián)系的,他們都將作用于該位置。
-
環(huán)視作為多選分支
將若干個(gè)環(huán)視作為多選分支排列在多選結(jié)構(gòu)中,只要有一個(gè)環(huán)視分支成立,整個(gè)多選就成立。
分組的編號(hào)只與捕獲型括號(hào)相關(guān),而不受其他任何括號(hào)類型的影響,所以環(huán)視結(jié)構(gòu)的括號(hào)并不影響分組。但是環(huán)視結(jié)構(gòu)中出現(xiàn)了捕獲型括號(hào),則會(huì)影響分組。
環(huán)視結(jié)構(gòu)中的捕獲型括號(hào)一旦匹配完成,就不能回溯。e.g.(?<=(\d+))\w+\1
無法在123a12
中找到匹配。
Python支持順序環(huán)視,但對(duì)于逆序環(huán)視,Python只支持匹配固定長度文本表達(dá)式。(?<=dogs?)
和(?<=(dog|cats))
都是不合法的,遇到這樣的可與選擇多選結(jié)構(gòu)來改造,但是如果出現(xiàn)*和+之類的量詞,就不能用多選了。
至此,整個(gè)正則表達(dá)式部分就介紹完畢,以下部分Python正則API。
Python中的正則表達(dá)式模塊是re模塊,通過import re 導(dǎo)入re模塊來使用正則表達(dá)式。
在正則表達(dá)式中,大多數(shù)的函數(shù)是類似re.search(pattern,string)的形式,其中pattern通常是正則表達(dá)式的字符串,這可以視為“靜態(tài)方法”。其實(shí)也可以將RegexObject對(duì)象作為pattern參數(shù)傳入,或者直接對(duì)RegexObject對(duì)象調(diào)用相同名字的成員方法,結(jié)果相同,但是后面這兩個(gè)方法可以顯示的緩存RegexObject對(duì)象。
除了正則表達(dá)式對(duì)象RegexObject,還有一個(gè)MatchObject對(duì)象,MatchObject對(duì)象提供了一些方法和屬性,通過他們可以獲取匹配的信息。
方法 | 描述 | 注釋 |
---|---|---|
start(n) | 返回編號(hào)為n的捕獲分組匹配的開始位置,如果對(duì)應(yīng)分組不存在或者未匹配,則返回-1 | 如果沒有設(shè)定n,則返回整個(gè)表達(dá)式匹配的開始位置,此時(shí)n等于0 |
pos | 返回整個(gè)表達(dá)式匹配的開始位置 | |
end(n) | 返回編號(hào)為n的捕獲分組匹配的結(jié)束位置,如果對(duì)應(yīng)分組不存在或未匹配,則返回-1 | 如果沒有設(shè)定n,則返回整個(gè)表達(dá)式匹配的結(jié)束位置,此時(shí)n等價(jià)于0 |
endpos | 返回整個(gè)表達(dá)式匹配的結(jié)束位置 | 等價(jià)于end(0) |
group(n) | 返回編號(hào)為n的捕獲分組匹配的文本 | 如果沒有設(shè)定n,則返回整個(gè)表達(dá)式匹配的文本,此時(shí)n等價(jià)于0 |
groups() | 返回各捕獲分組匹配的文本構(gòu)成的tuple | 如果表達(dá)式中不存在捕獲分組,則返回空tuple |
span(n) | 返回編號(hào)為n的捕獲分組匹配的開始-結(jié)束位置 | 如果沒有設(shè)定n,則返回整個(gè)表達(dá)式匹配的開始-結(jié)束位置,此時(shí)n等價(jià)于0 |
re | 返回匹配時(shí)所使用的正則表達(dá)式對(duì)象 | |
lastindex | 返回匹配成功的編號(hào)最大的捕獲分組的編號(hào) | 如果沒有,則返回None |
string | 返回用來嘗試用正則表達(dá)式來匹配的字符串 | |
expand(str) | 返回一個(gè)字符串,可以在其中引用捕獲分組匹配的文本 |
-
re.compile(regex [, flags])
這個(gè)方法用于顯示“編譯”正則表達(dá)式對(duì)象。如果需要多次使用同一個(gè)正則表達(dá)式,那么每次使用編譯好的正則表達(dá)式對(duì)象比每次臨時(shí)編譯表達(dá)式對(duì)象要快得多。另外compile()還有一個(gè)功能,那就是添加第二個(gè)參數(shù)re.DEBUG來觀察整個(gè)正則表達(dá)式的詳細(xì)信息。
-
re.search(pattern, string [, flags])
這個(gè)方法用來測(cè)試正則表達(dá)式pattern能否在string中找到匹配,flags是可選參數(shù)是匹配模式(aiLmsux)的按位OR結(jié)果。如果能找到匹配,則返回MatchObject,否則返回None。
-
re.match(pattern, string [, flags])
re.match()和re.search()非常相似,參數(shù)相同,返回值也行通,非常容易混淆,唯一的區(qū)別在于:如果re.match()匹配成功,那么pattern匹配的字符串必須開始于string的最左端。也就是說,re.match()只會(huì)在字符串的開始的位置嘗試匹配,re.search()則沒有這個(gè)限制。MULTILINE 多行模式下,match也只匹配string的開頭部分。
-
re.findall(pattern, string [, flags])
這個(gè)函數(shù)會(huì)一次性的找出正則表達(dá)式pattern在string中的所有匹配,返回一個(gè)list。
如果pattern中存在捕獲分組,則返回list的元素并非整個(gè)表達(dá)式所匹配的文本,而是各個(gè)捕獲分組所匹配文本構(gòu)成的tuple。返回的tuple中是沒有默認(rèn)的分組編號(hào)0所匹配的文本元素的,如果需要返回可以在整個(gè)正則表達(dá)式外再加一個(gè)括號(hào)。tuple中文本的順序就是分組編號(hào)的順序。
-
re.finditer(pattern, string [, flags])
如果文本內(nèi)容很多,使用re.findall()一次性找到所有匹配的成本可能很高,使用re.finditer()可以返回一個(gè)迭代器(iterator),他可以按照從左到右的順序逐個(gè)遍歷每次匹配的MatchObject對(duì)象,依次進(jìn)行處理或檢驗(yàn)。
-
re.split(pattern, string [, maxsplit=0, flags=0])
這個(gè)函數(shù)用正則表達(dá)式pattern能夠匹配的文本切分字符串string。如果正則表達(dá)式在開頭或者結(jié)尾位置找到匹配,那么結(jié)果的開頭或結(jié)尾會(huì)出現(xiàn)空字符串。也可以明確設(shè)定maxsplit。在Python中這個(gè)參數(shù)表示切分的次數(shù)。也就是說,返回?cái)?shù)組一般會(huì)包含maxsplit+1個(gè)元素。如果maxsplit設(shè)定負(fù)數(shù),則表示不做任何切分;若設(shè)定0,則等于沒有設(shè)定;若設(shè)定的值為正數(shù),且小于實(shí)際可切分的次數(shù),則按設(shè)定的值進(jìn)行切分,若大于實(shí)際可切分的次數(shù),則以實(shí)際可切分的次數(shù)為準(zhǔn)。
-
re.sub(patern ,repl, string [, count=0, flags=0])
這個(gè)函數(shù)用來進(jìn)行正則表達(dá)式替換,它將字符串string中的正則表達(dá)式pattern能夠匹配的文本替換為repl指定的文本。如果要在replacement字符串中引用之前的某個(gè)捕獲分組匹配的文本,則應(yīng)該使用
\num
。Python中也提供了跟清晰的方式在repl中引用分組,寫作\g<num>
,這種記法也可以使用命名分組\g<name>
。repl也可以是一個(gè)函數(shù),他接受一個(gè)MatchObject對(duì)象,返回一個(gè)String對(duì)象。也可以設(shè)定count參數(shù),他指定替換操作最多能發(fā)生的次數(shù)。 -
re.subn(patern ,repl, string [, count=0, flags=0])
與sub()相同,但返回一個(gè)元祖,其中包含新字符串和替換次數(shù)。
-
re.escape(string)
返回一個(gè)字符串,其中所有的非字母數(shù)字字符都帶有反斜杠。