python的re模塊--細(xì)說正則表達(dá)式
可能是東半球最詳細(xì)最全面的re教程,翻譯自官方文檔,因?yàn)楣俜轿臋n寫的是真的好,所以,盡量保持原本的東西,做最少的改動,保持原汁原味!干貨十足。如果能耐著性子看完,一定會感覺編程能力上一個(gè)新的檔次!
大家有空也可以到我的個(gè)人博客上看看
前言
什么是正則表達(dá)式,regular expression在英語中是有規(guī)則的表達(dá)式,也就是說,該表達(dá)式只是一條的規(guī)則,而正則表達(dá)式引擎能夠根據(jù)這條規(guī)則,幫你在字符串中尋找所有符合規(guī)則的部分,比如,我有一條字符串"hello world 123"
而規(guī)則,可以很具體,比如hello
那么引擎會幫你把hello
從字符串中找出來,規(guī)則也可以比較抽象,比如\d
這個(gè)表示數(shù)字,也就是引擎會幫你把字符串中的數(shù)字尋找出來,根據(jù)正則表達(dá)式的理論,我們可以把規(guī)則串聯(lián)起來,變成一條更復(fù)雜的規(guī)則。想要從一條字符串中找到對你來說有意義的部分,你的任務(wù)可能不僅僅是從字符串中提取數(shù)字這么簡單,因此需要設(shè)計(jì)非常復(fù)雜的規(guī)則,而本教程則會告訴你,如何去制定非常那些非常復(fù)雜的規(guī)則。事實(shí)上,制定好規(guī)則之后,引擎不僅能夠根據(jù)規(guī)則進(jìn)行查找,還可以進(jìn)行分割,替換等復(fù)雜的操作,能讓你隨心所欲得處理字符串。本教程默認(rèn)你懂得一些python的簡單語句尤其是對str處理時(shí)的語句。
python中的re模塊提供無比強(qiáng)大的正則表達(dá)式功能。如果需要更加逆天的正則表達(dá)式功能,第三方regex模塊具有與標(biāo)準(zhǔn)庫re模塊兼容的API,但提供了額外的功能和更全面的Unicode支持。不過python自帶的re模塊已經(jīng)足夠牛逼了,只要你能夠掌握下面的內(nèi)容。
學(xué)習(xí)正則表達(dá)式的意義
為什么要學(xué)習(xí)正則表達(dá)式?不僅python,絕大多數(shù)語言的正則表達(dá)式的設(shè)計(jì)理念都是差不多的,你在這里學(xué)熟練了,那么對于其他語言的正則表達(dá)式也會融會貫通。學(xué)會正則表達(dá)式,不僅能提升編程能力,還能讓你的工作和生活更加方便。比如,有人在處理文章的時(shí)候,想把文章的數(shù)字標(biāo)號"第1章"變成漢字?jǐn)?shù)字編號"第一章",如果章節(jié)非常多,手動修改是非常痛苦的,而word自帶的替換功能已經(jīng)無能為力了,那么可以考慮使用一個(gè)功能強(qiáng)大的編輯器比如sublime然后通過正則表達(dá)式進(jìn)行替換。類似的文本處理在工作和生活中經(jīng)常出現(xiàn),掌握正則表達(dá)式絕對會讓你能夠更加輕松應(yīng)對這些場景。此外,學(xué)會正則表達(dá)式能夠做的東西非常多,也非常酷,比如本教程最后講的分詞器,就是編寫編譯器的關(guān)鍵步驟,沒錯(cuò),學(xué)完正則表達(dá)式,是開發(fā)一個(gè)屬于你自己的語言的關(guān)鍵步驟,說不定你的語言能夠和python一樣廣受歡迎。
關(guān)于\
正則表達(dá)式使用反斜線字符 \
作為Escape符,讓特殊字符失去含義。這與Python在字符串的使用相沖突,所以,如果我們需要匹配反斜杠,我們需要輸入的正則表達(dá)式應(yīng)該是\\
也就是我們要輸入兩個(gè)反斜杠,這樣一來,要告訴python的字符串,這里有兩個(gè)反斜杠,那么,我們的python字符串里面就需要有四個(gè)反斜杠\\\\
。
!!!如果你覺得很難理解,沒關(guān)系,只要記得下面的解決方案就可以了。
解決方案是將Python的原始字符串表示法用于正則表達(dá)式模式;在以 'r' 為前綴的字符串文字中不以任何特殊方式處理反斜杠。所以 r"\n"
是包含 \
和 'n' 的兩個(gè)字符的字符串,而 "\n" 是包含換行符的單字符字符串。re讀取了 \
和 n
連續(xù)兩個(gè)字符,他就知道,這種表達(dá)式是要匹配一個(gè)換行符。總而言之,使用的時(shí)候一定要使用r'str'這樣的模式,你在查找資料的時(shí)候看到也經(jīng)常是這種情況。
!!!以上內(nèi)容看不懂沒關(guān)系,只要記住,python的正則表達(dá)式都是 r"str" 這樣的形式就可以了!
正則表達(dá)式規(guī)則
規(guī)則是相通的,除了python,其他語言的正則表達(dá)式規(guī)則基本上都是一樣的,只是語言的實(shí)現(xiàn)不一樣,因此,我們先看看什么是正則表達(dá)式規(guī)則。
正則表達(dá)式(RE)實(shí)際上就是一條規(guī)則,能讓字符串的某些部分匹配上,或者說該部分符合這條規(guī)則。有時(shí)候規(guī)則比較具體,如找出字符串中所有的hello
,那么就字符串中一定要是要有'hello'才能匹配,其他的'Hello',或者'HELLO',再或者有錯(cuò)別字的'gello'都是不符規(guī)則的。有時(shí)候呢,可以放寬一些規(guī)則,比如允許第一個(gè)字母是錯(cuò)別字,只要是小寫字母就好了,那么就可以這樣寫規(guī)則[a-z]ello
,其中 [a-z]
表示a到z中的任意一個(gè)。我們可以看到,在規(guī)則[a-z]ello
中既有ello
這樣的普通字符,又有[a-z]
這樣的特殊規(guī)則,組成了一條完整的規(guī)則,我們也能看出,只有普通字符那么規(guī)則是非常具體嚴(yán)格的,加上[a-z]
這樣的特殊符號之后規(guī)則變得更加靈活。所以,特殊符號才是正則表達(dá)式強(qiáng)大所在,我們可以使用不同的特殊符號進(jìn)而構(gòu)造更加復(fù)雜的規(guī)則。
下面就看看python的正則表達(dá)式有哪些特殊符號。(其他的語言中的特殊符號也都大同小異)
值得說明一下的是,下面的規(guī)則有些非常復(fù)雜,很多人可能這輩子都用不上,所以,看到你覺得復(fù)雜或者看不懂的規(guī)則,你可以跳過去,沒有任何影響,或許你在看完全部的內(nèi)容之后再回過來看這些特殊的符號,特殊的規(guī)則,會有新的領(lǐng)悟,所以,學(xué)習(xí)正則表達(dá)式不可能一下就能學(xué)會的,一定要多看幾遍,多練習(xí),才能融會貫通,因?yàn)閷W(xué)習(xí)最主要的還是要運(yùn)用。
特殊符號(又稱特殊字符)如下:
.
在默認(rèn)模式下,它匹配除換行符以外的任何字符。(如果已經(jīng)指定了DOTALL標(biāo)志,則它匹配包括換行符的任何字符。至于如何設(shè)定這個(gè),我們之后再講)
因此我們的設(shè)定規(guī)則 .ello
的時(shí)候,符合這條規(guī)則的字符串有'hello','8ello','kello'...非常多,不管開頭是什么字符,只要后面是'ello'就符合規(guī)則。
^
匹配字符串的開頭,并且在MULTILINE模式下,也會在每個(gè)換行符后立即匹配。
我們設(shè)定規(guī)則^hello
那么字符串 'hello world'里面的'hello'能夠符合規(guī)則,而'world hello'里面的'hello'不符合規(guī)則,因?yàn)樗辉谧址拈_頭。
由于在Multiline模式下,也匹配每個(gè)換行符后,所以,在開啟了multiline模式下,
'world\nhello'中的'hello'也符合規(guī)則。
$
匹配字符串的結(jié)尾或緊挨在字符串末尾的換行符之前,并且在MULTILINE模式下也匹配換行符之前的匹配。
foo
匹配 'foo' 和 'foobar' ,而正則表達(dá)式 foo$
只匹配 'foo'或者'hello foo'。 更有趣的是,在 "foo1\nfoo2\n" 中搜索 foo.$
通常與 'foo2' 匹配,而在MULTILINE模式下得到的是'foo1'。在'foo\n'中搜索單個(gè) $
將會找到兩個(gè)(空)匹配:一個(gè)位于換行符之前,另一個(gè)位于字符串末尾。
*
對
*
前面的RE匹配0次或多次,并盡可能多的重復(fù)。
我們設(shè)定規(guī)則 ab*
,意思是對b
可以重復(fù)匹配。 將匹配'a','ab'或'a'后跟任意數(shù)量的'b'。而且*
默認(rèn)的是貪婪模式,也就是說對于字符串'abbb'規(guī)則認(rèn)為整個(gè)字符串符合規(guī)則,而不是子字符串符合規(guī)則。將來我們要求返回符合規(guī)則的部分時(shí),將會返回整個(gè)字符串a(chǎn)bbb而不僅僅是ab或者a。
+
對于'+'前面的RE進(jìn)行一個(gè)或多個(gè)重復(fù)。
ab+
將與 'ab' 匹配,后面還可以跟任意個(gè)'b' ,但這條規(guī)則不會匹配 'a' 。值得注意的是,這條規(guī)則也是貪婪的。
?
對
?
前面的RE進(jìn)行0或1個(gè)重復(fù)。
ab?
將匹配 'a' 或 'ab'。這條規(guī)則也是貪婪的。
*?, +?, ??
*
,+
和?
限定符都是貪婪的; 它們匹配盡可能多的文本。 有時(shí)候這種行為是不希望的; 如果RE<.*>
與 '<a>b<c>' 匹配,它將匹配整個(gè)字符串,而不僅僅是 '<a>' 。 添加? 在限定符之后以非貪婪或最小方式進(jìn)行匹配; 盡可能少的字符將被匹配。 使用RE<.*?>
將只匹配 '<a>'。
值得一提的是,我們看到 a*?
我們應(yīng)當(dāng)想到這是一條關(guān)于*
的規(guī)則,而不是關(guān)于?
的規(guī)則,后者只是為前者服務(wù)的。
{m}
指定應(yīng)該匹配
{m}
前一個(gè)RE的正好m個(gè)副本; 更少的匹配導(dǎo)致整個(gè)RE不匹配。 例如,a{6}
將完全匹配六個(gè) 'a' 字符,但不是五個(gè)。
如果多于6次,那么多出的部分被忽略。
{m,n}
使得到的RE匹配前面RE的m到n次重復(fù),試圖盡可能多地匹配重復(fù)。 例如,
a{3,5}
將匹配3到5個(gè) 'a' 字符。省略m則指定零作為下限,省略n指定無限上限。 例如,a{4,}b
會匹配 'aaaab' 或一千個(gè) 'a' 字符,后跟一個(gè) 'b',但不匹配 'aaab'。 逗號不能省略,否則修飾符會與之前描述的表單混淆。
這條規(guī)則也是貪婪的,它會盡可能到達(dá)重復(fù)n次
{m,n}?
使得到的RE從m到n重復(fù)前面的RE,嘗試匹配盡可能少的重復(fù)。 這是以前限定符的非貪婪版本。 例如,在6個(gè)字符的字符串'aaaaaa'上,
a{3,5}
將匹配5個(gè) 'a'字符,而a{3,5}?
只會匹配3個(gè)字符 'a'。
\
要么特殊字符失去意義,要么標(biāo)志一個(gè)特殊序列; 之后會討論特殊序列。
首先,你在python中寫規(guī)則的時(shí)候一定要用raw字符串,也就是r'...',否則就會被''的特殊性給繞暈。其次要記住,這個(gè)符號叫Escape符號,使得特殊符號逃離特殊功能,比如,我們要匹配一個(gè)'.',那么我們寫規(guī)則的時(shí)候就需要這樣寫 r'\.'
[]
用于指示一組(或者說一個(gè)集合)字符。在一組中:
- 字符可以單獨(dú)列出,例如
[amk]
將匹配 'a','m'或'k'。
- 字符的范圍可以通過給出兩個(gè)字符并用'-'分隔來指示,例如
[a-z]
將匹配任何小寫ASCII字母,[0-5][0-9]
將匹配所有的兩位數(shù)字00到59,[0-9A-Fa-f]
將匹配任何十六進(jìn)制數(shù)字。如果 - 被轉(zhuǎn)義(例如[a\-z]
),或者被放置為第一個(gè)或最后一個(gè)字符(例如[-a]
或[a-]
),它將匹配一個(gè)'-'。 - 特殊字符在集合內(nèi)部失去其特殊含義。例如,
[(+*)]
將匹配任何文字字符 '(' , '+' , '*' 或 ')' 。 - 字符類如
\w
或\S
也可以在一個(gè)集合內(nèi)接受,盡管它們匹配的字符取決于ASCII或LOCALE模式是否有效。(之后會有關(guān)于字符類更加詳細(xì)的說明) - 不在一個(gè)范圍內(nèi)的字符可以通過對該集合進(jìn)行補(bǔ)充來匹配。如果
[]
中的第一個(gè)字符是 '^' ,則不匹配的所有字符將被匹配。例如,[^5]將匹配除'5'以外的任何字符,并且[^^]
將匹配除 '^' 以外的任何字符。 '^' 如果它不是集合中的第一個(gè)字符,它沒有特別的意義。 - 在一個(gè)集合內(nèi)匹配']',在它之前加一個(gè)反斜杠,或者將它放在集合的開頭。例如, [()[]{}]和[{}]都將與括號匹配。
|
A|B,其中A和B可以是任意RE,創(chuàng)建一個(gè)匹配A或B的正則表達(dá)式。任意數(shù)量的RE可以用'|'分隔。通過這種方式,這可以在組內(nèi)使用(見下文)。當(dāng)目標(biāo)字符串被掃描時(shí),由'|'分隔的RE 從左到右嘗試。當(dāng)一個(gè)模式完全匹配時(shí),該分支被接受。 這意味著一旦A匹配,B將不會被進(jìn)一步測試,即使它會產(chǎn)生更長的整體匹配。 換句話說, '|'操作從不貪婪。要匹配'|',請使用
\|
,或?qū)⑵浞旁谧址愔?如[|]
中所示
\d
對于Unicode(str)模式:
- 匹配任何Unicode十進(jìn)制數(shù)字。這包括[0-9]以及許多其他數(shù)字字符。 如果使用ASCII標(biāo)志,則只匹配[0-9]。
對于8位(字節(jié))模式:
- 匹配任何十進(jìn)制數(shù)字; 這相當(dāng)于[0-9]。
\D
匹配任何不是十進(jìn)制數(shù)字的字符。 這與\d相反。 如果使用ASCII標(biāo)志,則相當(dāng)于[^ 0-9]。
\s
對于Unicode(str)模式:
- 匹配Unicode空白字符(包括[\t \n \r \f \v]以及許多其他字符,例如許多語言中的排版規(guī)則強(qiáng)制的非空白空格)。 如果使用ASCII標(biāo)志,則只匹配[\t \n \r \f \v]。
對于8位(字節(jié))模式:
- 在ASCII字符集中匹配被認(rèn)為是空白的字符; 這相當(dāng)于[\t \n \r \f \v]。
\S
匹配任何不是空白字符的字符。 這與\s相反。 如果使用ASCII標(biāo)志,則這等價(jià)于[^ \t \n \r \f \v]。
\w
對于Unicode(str)模式:
- 匹配Unicode字符; 這包括大多數(shù)可以是任何語言的單詞的一部分的字符,以及數(shù)字和下劃線。 如果使用ASCII標(biāo)志,則只匹配[a-zA-Z0-9_]。
對于8位(字節(jié))模式:
- 匹配ASCII字符集中被認(rèn)為是字母數(shù)字的字符; 這相當(dāng)于[a-zA-Z0-9_]。 如果使用LOCALE標(biāo)志,則匹配在當(dāng)前語言環(huán)境和下劃線中被認(rèn)為是字母數(shù)字的字符。
\W
匹配任何不是單詞字符的字符。 這與\ w相反。 如果使用ASCII標(biāo)志,則變成等效于[^ a-zA-Z0-9_]。 如果使用LOCALE標(biāo)志,則匹配在當(dāng)前語言環(huán)境和下劃線中被認(rèn)為是字母數(shù)字的字符。
\Z
只匹配字符串的末尾。只能放在正則表達(dá)式末尾
進(jìn)階規(guī)則
(...)
匹配括號內(nèi)的任何正則表達(dá)式,并指示組的開始和結(jié)束; 在匹配完成后可以檢索組的內(nèi)容,并且可以在后面的字符串中使用\number特殊序列進(jìn)行匹配,之后有詳細(xì)描述。 要匹配'('或')',請使用
\(
或\)
,或?qū)⑺鼈兎旁谧址愔?[(],[]]
。 舉個(gè)例子我有個(gè)字符串 'asdfa020-12345678bsaefga' 可以看到我們的電話號碼被一群字母圍住了,怎么把電話號碼拿出來,順便把區(qū)號和真正的號碼都分開呢? 可以用這樣的規(guī)則(\d{4})-(\d)* 這樣符合結(jié)果字符串就是 '020-12345678' 但是,返回的結(jié)果對象并不僅僅是一個(gè)字符串,他還有.group,其中 group(1) = '020' group(2)='12345678'
\number
匹配相同編號的組的內(nèi)容。 例如
(.+) \1
這個(gè)式子等價(jià)于(.+) (.+)
和'the the' 或 '55 55'匹配,但不匹配'thethe'(注意組之后的空格)。 該特殊序列只能用于匹配前99個(gè)組中的一個(gè)。 如果數(shù)字的第一個(gè)數(shù)字是0或數(shù)字是3個(gè)八進(jìn)制數(shù)字長度,則不會將其解釋為組匹配,而是將其解釋為具有八進(jìn)制數(shù)值的字符(這句話初學(xué)者可以略過)。
這個(gè)規(guī)則非常棒,能幫你省不少事情,比如一個(gè)字符串里面有兩個(gè)郵件地址,你想把它們都找出來,你寫好了一個(gè)檢測郵件地址規(guī)則,剩下那個(gè)你不想寫了,你可以引用一下就可以了。
溫馨提示,下面的特殊符號可能比較難,需要耐心去體會,或者干脆略過。
(?P<name>...)
與常規(guī)圓括號類似,但可以通過符號組名稱來訪問與該組匹配的子字符串。 組名稱必須是有效的Python標(biāo)識符,并且每個(gè)組名稱只能在正則表達(dá)式中定義一次。
相當(dāng)于給我們之前提到的組(...)
添加了一個(gè)名字而已,添加名字的好處是可以根據(jù)名字來引用這個(gè)組,而不是靠編號。
相當(dāng)于給我們之前提到的組(...)
添加了一個(gè)名字而已,添加名字的好處是可以根據(jù)名字來引用這個(gè)組,而不是靠編號。舉個(gè)之前的例子,有字符串 'asdfa020-12345678bsaefga' ,我想取出號碼,可以這樣寫
(?P<quhao>\d{4})-(?P<haoma>\d*)
這樣返回的結(jié)果字符串就是020-12345678,但是返回的對象還有g(shù)roup屬性,而且 group['quhao'] = 020 group['haoma'] = 12345678
命名組可以在三種情況下被引用。 比如我們想在句子中找到引號,這樣我們就能找到文章中引用的內(nèi)容,所以,我們可以這樣寫
(P<quote>['"])
這樣就能找到一個(gè)單引號或者雙引號,但是一般來說,我們需要找到一對單引號或者雙引號,這時(shí)我們不必在寫一個(gè)單引號或雙引號的正則表達(dá)式,只要引用一下之前的就好了,如(?P<quote>['"]).*?(?P=quote)
后面一個(gè)實(shí)際上就是對于前面的引用,這時(shí)再正則表達(dá)式中引用的情況,也就是下表中第一種情況。實(shí)際上不僅規(guī)則中很有可能會引用前面寫好的組,還有其他的兩種情況需要引用,請見表
引用'quote' | 引用的方法 |
---|---|
在正則表達(dá)式中 |
(?P=quote) \1
|
在匹配的結(jié)果中 |
m.group('quote') m.end('queto')
|
在re.sub中的repl里 |
\g<quote> \g<1> \1
|
(?P=name)
對指定組的反斜線引用; 它匹配與早先的組命名相匹配的任何文本。見上一條的說明
(?...)
這是一個(gè)擴(kuò)展符號。'?'后面的第一個(gè)字符決定了結(jié)構(gòu)的含義和進(jìn)一步語法。擴(kuò)展通常不會創(chuàng)建一個(gè)新的組。
(?P<name> ...)
是這個(gè)規(guī)則的唯一例外,以下是當(dāng)前支持的擴(kuò)展。
(?aiLmsux)
(?號后面跟著'a','i','L','m','s','u','x'的一個(gè)或多個(gè)字母)。這些字母設(shè)置相應(yīng)的標(biāo)志:re.A(僅ASCII匹配),re.I(忽略大小寫),re.L(依賴于語言環(huán)境),re.M(多行),re.S(點(diǎn)匹配全部) ,re.U(Unicode匹配)和re.X(詳細(xì)),用于整個(gè)正則表達(dá)式。這些標(biāo)志的作用在模塊內(nèi)容中有具體的描述。事實(shí)上,這些標(biāo)志可以通過flag參數(shù)傳給正則表達(dá)式,也可以像這樣直接寫進(jìn)正則表達(dá)式里面。
(?:...)
常規(guī)圓括號的非捕獲版本。 匹配括號內(nèi)的任何正則表達(dá)式,但匹配的子字符串在執(zhí)行匹配或稍后引用模式后無法檢索。
(?aiLmsux-imsx:...)
(來自'a','i','L','m','s','u','x'的零個(gè)或多個(gè)字母,可選地后面跟著' - ',后面跟著一個(gè)或多個(gè)來自'','m','s','x')。字母設(shè)置或刪除相應(yīng)的標(biāo)志:re.A(僅ASCII匹配),re.I(忽略大小寫),re.L(依賴于語言環(huán)境),re.M(多行),re.S(點(diǎn)全部匹配),re.U(Unicode匹配)和re.X(冗長),用于表達(dá)部分。 注意,這里上面提到的(?aiLmsux)的區(qū)別是,這里有一個(gè)冒號,后面跟正則表達(dá)式,因此這里設(shè)置的標(biāo)志僅適用于窄內(nèi)聯(lián)組,并且原始匹配模式在組外部恢復(fù)。在設(shè)置標(biāo)志的時(shí)候(?aiLmsux-imsx:...)等價(jià)于(?aLu:...),也就是沒必要刻意先在左邊寫進(jìn)去后邊再跟隨'-'號刪掉,但是,有時(shí)候'-'也是很有用的,比如之前設(shè)置了(?i)說明對所有的正則表達(dá)式都忽略大小寫,但是你對某一個(gè)部分需要強(qiáng)調(diào)大小寫,比如,你想要找superMAN對前面的super的大小寫無所謂,但要求MAN一定是大寫,可以這樣寫
(?i)super(?-i:MAN)
這樣 sUpeRMAN能匹配,而superMan則不能匹配
(?#...)
一條評論; 圓括號的內(nèi)容被簡單地忽略。
(?=...)
如果...匹配next,但不消耗任何字符串。 這被稱為前瞻斷言。 例如,
Isaac(?=Asimov)
只有跟隨著'Asimov'才會匹配'Isaac'。但返回的結(jié)果依然還是Isaac,所謂不消耗字符串,意思是,后面的Asimov依然可以被繼續(xù)匹配,如
Isaac(?=[A])AsimovAlab
匹配 'IsaacAsimovAlab'
而Isaac(?=[Asimov])G
則會無法匹配任何字符串,因?yàn)楦鶕?jù)(?=...)要求,Issac后面必須是Asimov,而它后面又要跟著一個(gè)G,所以,互相矛盾。
(?!...)
如果...不匹配。 這是一個(gè)負(fù)面的前瞻斷言。 例如,Isaac(?!Asimov)只有在沒有跟隨'Asimov'時(shí)才會匹配'Isaac'。
(?<=...)
匹配如果字符串中的當(dāng)前位置在...之前匹配...,并以當(dāng)前位置結(jié)束。 這被稱后行斷言。
(?<= abc)def
會在'abcdef' 中找到一個(gè)匹配項(xiàng),因?yàn)閘ookbehind會回看3個(gè)字符并檢查包含的模式是否匹配。 包含的模式只能匹配一些固定長度的字符串,這意味著abc
或a|b
是允許的,但a*
和a{3,4}
不是。 請注意,像這種向前看的搜索字符串模式在匹配開頭的時(shí)候會出現(xiàn)問題;因此 必須使用search()函數(shù)而不是match()函數(shù):仔細(xì)想想為什么?
>>> import re
>>> m = re.search('(?<=abc)def', 'abcdef')
>>> m.group(0)
'def'
本示例在連字符后面查找單詞:
>>> m = re.search(r'(<=-)\w+', 'spam-egg')
>>> m.group(0)
'egg'
(?<!...)
上一例子的不匹配情況
(?(id/name)yes-pattern|no-pattern)
如果存在給定id或名稱的組,則嘗試與yes-模式匹配,如果不存在,則使用無模式。 無圖案是可選的,可以省略。 例如,正則表達(dá)式
(<)?(\w+@\w+(?:\.\w +)+)(?(1)>|$)
是一個(gè)電子郵件匹配模式,它將匹配'<user@host.com >'以及'user@host.com',但不匹配'<user@host.com'和'user@host.com>'。為什么,就是因?yàn)楹竺嬗幸粋€(gè)(?(1)>|$)
我們先看看其中的(1)
表示什么,表示正則表達(dá)式最前面的那個(gè)括號,也就是郵件地址匹配正則表達(dá)式中的(<)
如果這個(gè)括號匹配到了內(nèi)容,也就是檢查看到了郵件地址以'<'字符開始,那么,我們對于該郵件地址末尾字符的檢測就是>
,如果(<)
檢測失敗,也就是郵件地址不以'<'開頭,那么我們對于該郵件地址末尾字符的檢測就是'$'
\A
只匹配字符串的開頭。只能放在正則表達(dá)式開頭
\b
匹配空字符串,但僅限于單詞的開頭或結(jié)尾。一個(gè)單詞被定義為一個(gè)單詞字符序列。 請注意,在形式上,
\b
被定義為\w
和\W
字符之間的界限,或\w
和字符串的開始/結(jié)尾之間的界限。這意味著r'\bfoo\b'
匹配 'foo' , 'foo.' , '(foo)' , 'bar foo baz' ,但不匹配'foobar'或'foo3'。
默認(rèn)情況下,Unicode字母數(shù)字是Unicode模式中使用的字母數(shù)字,但可以通過使用ASCII標(biāo)志來更改。如果使用LOCALE標(biāo)志,字邊界由當(dāng)前的區(qū)域設(shè)置確定。在字符范圍內(nèi),\ b代表退格字符,以便與Python中的字符串文字兼容。
\B
匹配空字符串,但僅限于它不在單詞的開頭或結(jié)尾。 這意味著
r'py\B'
匹配'python','py3','py2',但不匹配'py','py。'或'py!'。\B
與\b
相反,因此Unicode模式中的單詞字符(\w)是Unicode字母數(shù)字加下劃線。 如果使用LOCALE標(biāo)志,單詞的定義由當(dāng)前的區(qū)域設(shè)置確定。
正則表達(dá)式對象
我們寫好一條規(guī)則,如r'.ello',目前在python里面只是一個(gè)字符串,我們要讓這個(gè)規(guī)則字符串變成正則表達(dá)式,需要將這個(gè)字符串編譯一下,成為表達(dá)式對象,這個(gè)對象又稱pattern,即樣式。
re.compile(pattern, flags=0)
通過上面的模塊函數(shù),根據(jù)我們寫的規(guī)則字符串,生成一個(gè)正則表達(dá)式對象。
序列
# regex = r'你的規(guī)則'
pattern = re.compile(regex) #這里 regex只是規(guī)則字符串,pattern才是正則表達(dá)式對象
result = pattern.match(string)
等價(jià)于
result = re.match(regex, string)
使用re.compile()生成的正則表達(dá)式對象可以重用,更高效。因此,推薦使用第一種用法,第二種方法明顯是在背后偷偷編譯了個(gè) pattern,只是為了方便,快捷地使用而已。
正則表達(dá)式對象支持以下方法
pattern.search(string[, pos[, endpos]])
掃描字符串查找第一個(gè)能匹配上pattern的部分,并返回相應(yīng)的匹配對象。 如果字符串中沒有位置與模式匹配,則返回None; 請注意,這與在字符串中的某處找到零長度匹配不同。如果你本身要匹配的就是一個(gè)空字符,如上述規(guī)則中的r'\b',那么返回一個(gè)""表示的是找到了對應(yīng)的r'\b',也就是找到了空字符,而返回None找不到任何匹配的空字符。
可選的第二個(gè)參數(shù)pos在搜索要開始的字符串中給出一個(gè)索引,它默認(rèn)為0,從指定的pos開始搜索,也就是說避開字符串開始的部分。pattern.search("hello world") 表示在 hello world 中查找
pattern.search("hello world",2) 表示在 llo world 中查找
可選參數(shù)endpos表示搜索字符串的截止位置,也就是說,你想避開字符串結(jié)尾的部分。只有從pos到endpos - 1的字符才會被搜索到。如果endpos小于pos,則不會找到匹配,否則pattern.search(string,0,50)等同于pattern.search(string[:50],0)。
>>> pattern = re.compile("d")
>>> pattern.search("dog") # 能匹配
<re.Match object; span=(0, 1), match='d'>
>>> pattern.search("dog", 1) # 不能匹配
我們可以看到,我們首先,寫好一個(gè)規(guī)則d
,這是一個(gè)具體的規(guī)則,沒有用到任何特殊符號,接著,我們把這條規(guī)則編譯成pattern,這是一個(gè)正則表達(dá)式對象,然后使用pattern的search函數(shù),在字符串"dog"中搜索。匹配的結(jié)果返回一個(gè)Match對象,之后會詳細(xì)講解這個(gè)Match對象。
Pattern.match(string[, pos[, endpos]])
如果字符串開頭的零個(gè)或多個(gè)字符與此正則表達(dá)式匹配,則返回相應(yīng)的匹配對象。 如果字符串與模式不匹配,則返回None; 請注意,這與零長度匹配不同。
可選的pos和endpos參數(shù)與search()方法具有相同的含義。
>>> pattern = re.compile(r"o")
>>> pattern.match("dog") # 不匹配,因?yàn)?"o" 不是 "dog"的最前面.
>>> pattern.match("dog", 1) # 能匹配 "o" 是 "dog" 的第二個(gè)字母.
<re.Match object; span=(1, 2), match='o'>;
這個(gè)函數(shù)和search差不多,但是,規(guī)定一定要從起始位置就得匹配上,否則就不算匹配成功。比如,我們在"dog"中搜索o
但是,由于開始的位置不是o
所以匹配失敗。
如果您想在字符串中的任何位置找到匹配項(xiàng),請改用search()(另請參閱search()與match())。
pattern.fullmatch(string[, pos[, endpos]])
如果整個(gè)字符串匹配此正則表達(dá)式,則返回相應(yīng)的匹配對象。 如果字符串與模式不匹配,則返回None; 請注意,這與零長度匹配不同。
可選的pos和endpos參數(shù)與search()方法具有相同的含義。
>>> pattern = re.compile("o[gh]")
>>> pattern.fullmatch("dog") # No match as "o" is not at the start of "dog".
>>> pattern.fullmatch("ogre") # No match as not the full string matches.
>>> pattern.fullmatch("doggie", 1, 3) # Matches within given limits.
<re.Match object; span=(1, 3), match='og'>
這個(gè)函數(shù)規(guī)定整個(gè)字符串要跟pattern匹配上,才算匹配成功。
pattern.split(string, maxsplit=0)
平時(shí),如果我們想拆分一個(gè)字符串,python內(nèi)置的split函數(shù)需要寫入固定的分拆字符,比如下面的字符串"abc1efg1rgh" 我們可以以'1'字符來分拆這個(gè)字符串,但是萬一,分割字符串的不只是'1'而是其他數(shù)字怎么辦?如"abc1efg2hij3klm"python自帶的split就束手無策了。這時(shí)候就需要用到正則表達(dá)式的拆分了,你會發(fā)現(xiàn)解決這個(gè)問題是分分鐘的事。
>>> pattern = re.compile(r'[0-9]')
>>> pattern.split("abc1efg2hij3klm")
['abc', 'efg', 'hij', 'klm']
這個(gè)函數(shù)根據(jù)表達(dá)式規(guī)則分拆字符串,返回一個(gè)list。(溫馨提示,后面這點(diǎn)內(nèi)容可以跳過)如果在表達(dá)式中使用捕獲括號,則表達(dá)式中所有組的文本也會作為結(jié)果列表的一部分返回。 如果maxsplit不為零,則最多發(fā)生maxsplit分割,并且字符串的其余部分作為列表的最后一個(gè)元素返回。
>>> pattern = re.compile(r'\W+')
>>> pattern.split( 'Words,words,words.')
['Words', 'words', 'words', '']
>>> pattern = re.compile(r'(\W+)')
>>> pattern.split( 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> pattern = re.compile(r'\W+')
>>> pattern.split('Words, words, words.', 1)
['Words', 'words, words.']
>>> pattern = re.compile('[a-f]+',flags=re.IGNORECASE)
>>> pattern.split( '0a3B9' )
['0', '3', '9']
如果分隔符中有捕獲組,并且它在字符串的起始處匹配,則結(jié)果將以空字符串開頭。 字符串的結(jié)尾也是一樣:
>>> pattern = re.compile(r'(\W+)')
>>> pattern.split( '...words, words...')
['', '...', 'words', ', ', 'words', '...', '']
這樣,分隔符組件總是在結(jié)果列表中的相同索引處找到。
pattern.findall(string[, pos[, endpos]])
返回字符串中模式的所有非重疊匹配項(xiàng),作為字符串列表。 字符串從左到右掃描,匹配按照找到的順序返回。 如果模式中存在一個(gè)或多個(gè)組,返回組列表; 如果模式有多個(gè)組,這將是一個(gè)元組列表。 結(jié)果中包含空匹配項(xiàng)。
比如,我們想要查找一個(gè)字符串里面全部的數(shù)字
>>> pattern = re.compile(r'\d+')
>>> pattern.findall("a12b56c54d89")
['12', '56', '54', '89']
如果有分組,比如,我們需要把查找的數(shù)字的末尾作為分組提取出來,那么返回的就是數(shù)字的最后一位
>>> pattern = re.compile(r'\d*([0-9])')
>>> pattern.findall("a124b567c54d892")
['4', '7', '4', '2']
r'\d*([0-9])' 這條規(guī)則本來匹配的是下面的123,567,54,892但是這里由于有了后面的括號,所以只返回括號里面的
如果有多個(gè)分組,則返回這些分組的tuple,比如,要返回?cái)?shù)字的個(gè)位數(shù)和十位數(shù)
>>> pattern = re.compile(r'\d*([0-9])([0-9])')
>>> pattern.findall("a124b567c54d892")
[('2', '4'), ('6', '7'), ('5', '4'), ('9', '2')]
pattern.finditer(string[, pos[, endpos]])
返回一個(gè)迭代器,產(chǎn)生字符串中RE模式的所有非重疊匹配的匹配對象。 字符串從左到右掃描,匹配按照找到的順序返回。
和上面唯一的區(qū)別是返回的是一個(gè)iter而不是一個(gè)list,如果你對Python熟悉的話應(yīng)該了解這兩者的區(qū)別,如果不熟悉的話,建議使用上一種方法就好了,不要管這個(gè)。
接受可選的pos和endpos參數(shù),這些參數(shù)限制搜索區(qū)域,如search()。
pattern.sub(repl, string, count=0)
python自帶replace函數(shù)的加強(qiáng)版。
先通過正則表達(dá)式規(guī)則找到string中符合規(guī)則的部分,然后替換成repl
如果未找到能匹配規(guī)則的部分,則字符串將保持不變。 repl可以是一個(gè)字符串或一個(gè)函數(shù); 如果它是一個(gè)字符串,則處理其中的任何反斜杠轉(zhuǎn)義。 也就是\n被轉(zhuǎn)換為單個(gè)換行符,\r被轉(zhuǎn)換為回車符,等等。 像 & 一樣的未知轉(zhuǎn)義單獨(dú)保留。引用(例如\6)被替換為模式中由組6匹配的子字符串。 例如: 我們把分割字母的數(shù)字換成'000'
>>> pattern = re.compile(r'\d+')
>>> pattern.sub('000', '12abc34de56fg89')
'000abc000de000fg000'
repl可以引用分組,因此,假如我們要把分割字母的數(shù)字換成他們的個(gè)位數(shù),可以這樣.
注意這里repl需要用raw字符串,否則re模塊無法識別'\1',還是那個(gè)''的問題。所以,能用raw盡量用raw
>>> pattern = re.compile(r'\d*(\d)')
>>> pattern.sub(r'\1','12abc34de56fg89') # 這里的r'\1'對應(yīng)的就是上面 r'\d*(\d)'中的(\d)
'2abc4de6fg9'
如果repl是一個(gè)函數(shù),它會實(shí)現(xiàn)更加復(fù)雜的替換,比如我們需要把分割字母的數(shù)字翻倍。也就是,我們我們每個(gè)匹配到的數(shù)字,我們都要通過函數(shù)處理一下,以返回值作為repl 例如:
>>> def func(n):
... return str(int(n.group())*2)
...
>>> pattern = re.compile(r'\d+')
>>> pattern = re.sub(func,'12abc34de56fg89')
'24abc68de112fg178'
注意到,傳給func的是一個(gè)個(gè)匹配對象(后面會講到),該對象包裝了真正匹配到的數(shù)字,如'12','34','56','89',所以使用了一個(gè) n.group()來提取數(shù)字。匹配對象的強(qiáng)大之處還在于我們不僅可以提取完整的匹配數(shù)字如'12',我們還可以提取分組,比如,我們在pattern中設(shè)定了個(gè)位數(shù)字作為組,那么就可以提取出來,如下面的例子,把找到的數(shù)字替換成該數(shù)字的個(gè)位數(shù)的兩倍
>>> def func(n):
... return str(int(n.group(1))*2)
# 對比上面n.group()其實(shí)是n.group(0)表示匹配的字符串,也就是整個(gè)r'\d*(\d)' 能匹配到的字符串
# n.group(1) 對應(yīng)的就是 r'\d*(\d)' 中的 (\d)匹配到的東西
...
>>> pattern = re.compile(r'\d*(\d)')
>>> pattern.sub(func,'12abc34de56fg89')
'4abc8de12fg18'
關(guān)于匹配對象,我們在后面還會有詳細(xì)的講解。
pattern.subn(repl, string, count=0)
執(zhí)行與sub()相同的操作,但返回一個(gè)元組(new_string,number_of_subs_made),也就是不僅返回一個(gè)和上面函數(shù)一樣的字符串,還多返回了一個(gè)數(shù)字,代表了總共替換的次數(shù),像上面的例子
>>> pattern.subn(func,'12abc34de56fg89')
('4abc8de12fg18', 4)
正則表達(dá)式對象的屬性:
pattern.flags
正則表達(dá)式匹配標(biāo)志。在re.compile()函數(shù)中設(shè)定,比如要忽略大小寫
>>> pattern = re.compile(r'd',flags=re.IGNORECASE)
>>> pattern.findall('DOG')
['D']
如果要設(shè)置多個(gè)flags,可以用 |
隔開
>>> pattern = re.compile(r'd',flags=re.IGNORECASE | re.MULTILINE)
當(dāng)然,設(shè)置flags也可以通過內(nèi)聯(lián)的方式
```python
>>> pattern = re.compile(r'(?im)d')
>>> pattern = re.compile(r'd',flags=re.IGNORECASE | re.MULTILINE)
上面兩者是等價(jià)的。
pattern.groups
查看模式中的捕獲組數(shù)量。也就是你在規(guī)則中使用捕獲括號的數(shù)量
比如 p = re.compile(r'hello(\d)') 那么這時(shí) p.groups 就是 1
pattern.groupindex
如果你在規(guī)則設(shè)定的時(shí)候使用了(?P<name>)
,那么這個(gè)變量可以返回該特殊符號使用的情況。
將(?P<id>)
定義的任何符號組名稱映射到組編號的字典。 如果模式中沒有使用符號組,則字典為空。
pattern.pattern
模式對象編譯的模式字符串。就是那個(gè)寫下的規(guī)則。
>>> p = re.compile(r'(?im)d')
>>> p.pattern
'(?im)d'
re模塊自帶的函數(shù)
我們上面學(xué)習(xí)了使用正則表達(dá)式的兩個(gè)步驟,首先編譯出正則表達(dá)式對象pattern,然后,調(diào)用pattern的search,findall,match等函數(shù)進(jìn)行匹配等操作,實(shí)際上re直接提供了search,findall,match等快捷操作,允許我們直接操作,而不需先編譯pattern,而是直接把規(guī)則寫在操作函數(shù)之中。這樣的操作比上面提到的方法減少了一行,有了更好地便捷性,但是也犧牲了復(fù)用性等功能。
re.match(pattern, string, flags=0)
下面的兩個(gè)匹配操作是等價(jià)
>>> pattern = re.compile(r"o")
>>> pattern.match("dog") # No match as "o" is not at the start of "dog".
>>> re.match(r"o","dog") # No match as "o" is not at the start of "dog".
事實(shí)上,第二種直接操作在背后也是先用第一個(gè)參數(shù)編譯成正則表達(dá)式對象然后在讓這個(gè)對象調(diào)用match方法。其他的search,findall,split等操作也是一樣的。
re.search(pattern, string, flags=0)
參考pattern.search
re.fullmatch(pattern, string, flags=0)
參考pattern.full
re.split(pattern, string, maxsplit=0, flags=0)
參考pattern.split
re.findall(pattern, string, flags=0)
參考pattern.findall
re.finditer(pattern, string, flags=0)
參考pattern.finditer
re.sub(pattern, repl, string, count=0, flags=0)
參考pattern.sub
re.subn(pattern, repl, string, count=0, flags=0)
執(zhí)行與sub()相同的操作,但返回一個(gè)元組(new_string,number_of_subs_made)。
re.escape(pattern)
讓pattern中的特殊字符失去意義。 如果您想匹配任何可能具有正則表達(dá)式元字符的文字字符串,這非常有用。 例如:
>>> print(re.escape('python.exe'))
python\.exe
>>> p = re.compile(re.escape(r'.')) # 等價(jià)于 p = re.compile(r'\.')
>>> p.search("abc")
# 不能匹配
>>> p.search(".")
<_sre.SRE_Match object at 0x05370D08>
再來一個(gè)例子
>>> operators = ['+', '-', '*', '/', '**']
>>> print('|'.join(map(re.escape, sorted(operators, reverse=True))))
/|\-|\+|\*\*|\*
re模塊自帶的參數(shù)
re.A == re.ASCII
使\w,\W,\b,\B,\d,\D,\s和\S執(zhí)行僅ASCII匹配而不是完全Unicode匹配。 這只對Unicode模式有意義,并且在字節(jié)模式中被忽略。 對應(yīng)于內(nèi)聯(lián)標(biāo)志(?a)。
請注意,為了向后兼容,re.U標(biāo)志仍然存在(以及它的同義詞re.UNICODE及其嵌入對象(?u)),但這些在Python 3中是多余的,因?yàn)槟J(rèn)情況下匹配是Unicode的Unicode(和Unicode 字節(jié)不允許匹配)。
re.I == re.IGNORECASE
執(zhí)行不區(qū)分大小寫的匹配; 像[A-Z]這樣的表達(dá)式也將匹配小寫字母。 除非使用re.ASCII標(biāo)志來禁用非ASCII匹配,否則完全的Unicode匹配(例如ü匹配ü)也是有效的。 除非使用re.LOCALE標(biāo)志,否則當(dāng)前語言環(huán)境不會更改此標(biāo)志的效果。 對應(yīng)于內(nèi)聯(lián)標(biāo)志(?i)。
請注意,當(dāng)Unicode模式[a-z]或[A-Z]與IGNORECASE標(biāo)志組合使用時(shí),它們將與52個(gè)ASCII字母和另外4個(gè)非ASCII字母匹配:'?'(U + 0130,拉丁語大寫字母I與 (U + 0131,拉丁小字母無點(diǎn)i),'s'(U + 017F,拉丁小寫字母長)和'K'(U + 212A,開爾文符號)。 如果使用ASCII標(biāo)志,只匹配字母'a'到'z'和'A'到'Z'。
re.L == re.LOCALE
根據(jù)當(dāng)前語言環(huán)境,使\ w,\ W,\ b,\ B和不區(qū)分大小寫的匹配。 該標(biāo)志只能用于字節(jié)模式。 由于區(qū)域設(shè)置機(jī)制非常不可靠,因此不鼓勵使用此標(biāo)志,它一次只處理一種"文化",并且僅適用于8位語言環(huán)境。 對于Unicode(str)模式,默認(rèn)情況下,Unicode匹配已在Python 3中啟用,并且它能夠處理不同的語言環(huán)境/語言。 對應(yīng)于內(nèi)聯(lián)標(biāo)志(?L)。
在版本3.6中更改:re.LOCALE只能與字節(jié)模式一起使用,并且與re.ASCII不兼容。
在版本3.7中更改:使用re.LOCALE標(biāo)志編譯的正則表達(dá)式對象在編譯時(shí)不再依賴于語言環(huán)境。 匹配時(shí)只有語言環(huán)境會影響匹配結(jié)果。
re.M == re.MULTILINE
指定時(shí),模式字符'^'匹配字符串的開頭和每行的開頭(緊跟在每個(gè)換行符之后); 并且模式字符'$'匹配字符串的末尾和每行末尾(緊接在每個(gè)換行符之前)。 默認(rèn)情況下,'^'只匹配字符串的開頭,'$'只匹配字符串的末尾,緊接在字符串末尾的換行符(如果有的話)之前。 對應(yīng)于內(nèi)聯(lián)標(biāo)志(?m)。
re.S == re.DOTALL
制作'.' 特殊字符完全匹配任何字符,包括換行符; 沒有這個(gè)標(biāo)志,'.' 將匹配除換行符之外的任何內(nèi)容。 對應(yīng)于內(nèi)聯(lián)標(biāo)志(?s)。
re.X == re.VERBOSE
該標(biāo)志允許您通過一種編輯模式來編寫正則表達(dá)式,允許您在視覺上分離模式的邏輯部分并添加注釋,該正則表達(dá)式看起來更好,并且更易讀。模式中的空格被忽略,除非在字符類中,或者前面有一個(gè)未轉(zhuǎn)義的反斜杠,或者在諸如 *?
, (?:
或 (?P<...>)
。我們還可以通過#號進(jìn)行注釋。
這意味著匹配一個(gè)十進(jìn)制數(shù)的下面兩個(gè)正則表達(dá)式對象在功能上是相等的:
a = re.compile(r"""\d + # the integral part
\. # the decimal point
\d * # some fractional digits""", re.X)
b = re.compile(r"\d+\.\d*")
對應(yīng)于內(nèi)聯(lián)標(biāo)志(?x)。
Match Objects 匹配的結(jié)果對象
我們之前就提到過當(dāng)我們用寫好的規(guī)則去匹配一個(gè)字符串的時(shí)候,返回的不是匹配好的子字符串!而是返回一個(gè)匹配對象,里面除了包裝好子字符串,還提供其它的功能,因?yàn)?我們在寫規(guī)則的時(shí)候有通過捕獲括號對規(guī)則進(jìn)行分組,因此,我們也可以通過匹配對象把分組給提取出來。比如hello(\w)
規(guī)則能夠匹配 'helloA',如果直接返回'helloA',那我們設(shè)置的捕獲括號不就失去意義了嗎,而匹配對象則正是能夠完成這些功能的關(guān)鍵所在。
匹配對象始終具有布爾值True。 由于match()和search()在不匹配時(shí)返回None,因此可以通過簡單的if語句測試是否成功匹配:
match = re.search(pattern, string)
if match:
process(match)
匹配對象支持以下方法和屬性:
match.expand(template)
我們設(shè)定一個(gè)模板,來顯示我們查找到的子字符串,其中,我們可以反斜杠替換的方式嵌入捕獲括號所匹配到的子字符串。
數(shù)字引用(\1, \2)或命名反斜線引用(\g<1>,\g <name>)被替換為相應(yīng)組的內(nèi)容。
比如我們要展示某個(gè)字符串中的數(shù)字,并列出個(gè)位數(shù)。
import re
p = re.compile(r'\d+(\d)(\d)')
match = p.search("adsfaddf12345fgd")
print(match .expand(r'The number in the string is \g<0>.\nThe last number is \2'))
The number in the string is 12345.
The last number is 5
match.group([group1, ...])
返回匹配的一個(gè)或多個(gè)子組。 如果只有一個(gè)參數(shù),結(jié)果是一個(gè)單獨(dú)的字符串; 如果有多個(gè)參數(shù),則結(jié)果是一個(gè)tuple。
沒有參數(shù),group默認(rèn)為零(整個(gè)匹配被返回)。 如果group參數(shù)為零,則相應(yīng)的返回值是整個(gè)匹配的字符串; 如果參數(shù)在包含范圍[1..99]中,則它是匹配相應(yīng)括號組的字符串。 如果組編號為負(fù)數(shù)或大于模式中定義的組數(shù),則會引發(fā)IndexError異常。 如果一個(gè)組包含在不匹配的模式的一部分中,則相應(yīng)的結(jié)果為無。 如果一個(gè)組包含在多次匹配的模式的一部分中,則返回最后的匹配。
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m.group(0) # 整個(gè)的匹配結(jié)果
'Isaac Newton'
>>> m.group(1) # 第一個(gè)括號匹配到的結(jié)果
'Isaac'
>>> m.group(2) # 第二個(gè)括號匹配到的結(jié)果
'Newton'
>>> m.group(1, 2) # 返回多個(gè)結(jié)果
('Isaac', 'Newton')
如果正則表達(dá)式使用 (?P<name>...)
語法,則group參數(shù)也可以是通過組名稱標(biāo)識組的字符串。 如果字符串參數(shù)未在模式中用作組名稱,則會引發(fā)IndexError異常。
>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.group('first_name')
'Malcolm'
>>> m.group('last_name')
'Reynolds'
命名組也可以通過它們的索引來引用:
>>> m.group(1)
'Malcolm'
>>> m.group(2)
'Reynolds'
如果一個(gè)組匹配多次,只能訪問最后一場匹配:
>>> m = re.match(r"(..)+", "a1b2c3") # 由于貪婪模式,這個(gè)表達(dá)式明顯匹配的結(jié)果是全部的字符串
# 但是其中的括號匹配到的是什么呢?
>>> m.group(1) # 返回最后能匹配到的
'c3'
讀者可以自行試試這個(gè)例子,看看 m.group() m.group(0) m.group(1) m.group(2) 分別是什么
match.getitem(g)
如果你熟悉python就知道這個(gè)屬性就像dict一樣,讓我們能夠直接用[]的形式取出數(shù)據(jù),而不需要使用group函數(shù),更加方便快捷。
這與m.group(g)相同。 這允許從比賽中更容易地訪問個(gè)人組:
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m[0] # 等價(jià)于 m.group(0)
'Isaac Newton'
>>> m[1] #
'Isaac'
>>> m[2] #
'Newton'
match.groups(default=None)
返回一個(gè)包含匹配所有子組的元組,從1開始,直到模式中有多個(gè)組。 default參數(shù)用于補(bǔ)齊沒能成功匹配的組; 它默認(rèn)為None。
例如:
>>> m = re.match(r"(\d+)\.(\d+)", "24.1632")
>>> m.groups()
('24', '1632')
如果我們將小數(shù)點(diǎn)后的位置及其后的所有內(nèi)容都設(shè)為可選,則并非所有組都可以參與該匹配。 除非給出默認(rèn)參數(shù),否則這些組將默認(rèn)為None。
>>> m = re.match(r"(\d+)\.?(\d+)?", "24")
>>> m.groups() # 第二組默認(rèn)為 None.
('24', None)
>>> m.groups('0') # Now, the second group defaults to '0'.
('24', '0')
match.groupdict(default=None)
返回包含匹配的所有命名子組的字典的子集名稱。 缺省參數(shù)用于未參與匹配的組; 它默認(rèn)為None。 例如:
>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.groupdict()
{'first_name': 'Malcolm', 'last_name': 'Reynolds'}
match.start([group])
match.end([group])
返回按組匹配的子串在原字符串中開始和結(jié)束的位置;
組默認(rèn)為零,意味著整個(gè)匹配的子字符串,一個(gè)將從電子郵件地址中刪除remove_this的示例:
>>> email = "tony@tiremove_thisger.net"
>>> m = re.search("remove_this", email)
>>> email[:m.start()] + email[m.end():]
'tony@tiger.net'
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m.start(0) # 整個(gè)匹配到的是 "Isaac Newton" 其中"I"在原字符串的開頭,因此是 0
0
>>> m.end(0) # 整個(gè)匹配到的是 "Isaac Newton" 其中"n"在原字符串的第12位,因此是 12
12
>>> m.start(1) # 第一組匹配到的是 "Isaac" 其中"I"在原字符串的第0位,因此是 0
0
>>> m.end(1) # 第一組匹配到的是 "Isaac" 其中"c"在原字符串的第5位,因此是 5
5
>>> m.start(2) # 第2組匹配到的是 "Newton" 其中"N"在原字符串的第6位,因此是 6
6
>>> m.end(2) # 第2組匹配到的是 "Newton" 其中"n"在原字符串的第12位,因此是 12
12
match.span([group])
對于匹配m,返回2元組(m.start(group),m.end(group))。 請注意,如果組沒有參與匹配,則為(-1,-1)。 組默認(rèn)為零。對于上面的例子 m.span() 也就是 m.span(0) 返回 (0,12) m.span(1) 返回 (0,5) m.group(2) 返回 (6,12)
Match Object的一些參數(shù)
match.pos
傳遞給正則表達(dá)式對象的search()或match()方法的pos的值。 這是RE引擎開始尋找匹配的字符串的索引。根據(jù)此值,我們能夠知道在引擎搜索時(shí)設(shè)定的pos
match.endpos
傳遞給正則表達(dá)式對象的search()或match()方法的endpos的值。 這是RE引擎不會去的字符串的索引。
match.lastindex
最后一個(gè)匹配捕獲組的整數(shù)索引,或者如果沒有匹配組,則為None。 例如,如果將表達(dá)式(a)b,((a)(b))和((ab))應(yīng)用于字符串"ab",則lastindex == 1,而表達(dá)式(a)(b)將 如果應(yīng)用于相同的字符串,則lastindex == 2。
match.lastgroup
最后匹配的捕獲組的名稱,如果組沒有名稱,或者根本沒有匹配組,則為None。
match.re
正則表達(dá)式對象的match()或search()方法生成此匹配實(shí)例。
match.string
傳遞給match()或search()的字符串。
一些例子
尋找對子
在這個(gè)例子中,我們將使用以下輔助函數(shù)來更加優(yōu)雅地顯示匹配對象:
def displaymatch(match):
if match is None:
return None
return '<Match: %r, groups=%r>' % (match.group(), match.groups())
假設(shè)您正在編寫一個(gè)撲克程序,其中玩家的手牌為5個(gè)字符的字符串,每個(gè)字符代表一張牌,"a"代表王牌,"k"代表國王,"q"代表女王,"j"代表插孔, "t"為10,"2"至"9"代表具有該值的卡。
要查看給定的字符串是否是有效的,可以執(zhí)行以下操作:
>>> valid = re.compile(r"^[a2-9tjqk]{5}$")
>>> displaymatch(valid.match("akt5q")) # Valid.
"<Match: 'akt5q', groups=()>"
>>> displaymatch(valid.match("akt5e")) # Invalid.
>>> displaymatch(valid.match("akt")) # Invalid.
>>> displaymatch(valid.match("727ak")) # Valid.
"<Match: '727ak', groups=()>"
最后一只手牌,"727ak",包含一對,或兩個(gè)相同的價(jià)值卡。 為了與正則表達(dá)式匹配,可以使用反斜線引用:
>>> pair = re.compile(r".*(.).*\1")
>>> displaymatch(pair.match("717ak")) # Pair of 7s.
"<Match: '717', groups=('7',)>"
>>> displaymatch(pair.match("718ak")) # No pairs.
>>> displaymatch(pair.match("354aa")) # Pair of aces.
"<Match: '354aa', groups=('a',)>"
為了找出這對卡片組成的卡片,可以按照以下方式使用匹配對象的group()方法:
>>> pair.match("717ak").group(1)
'7'
# Error because re.match() returns None, which doesn't have a group() method:
>>> pair.match("718ak").group(1)
Traceback (most recent call last):
File "<pyshell#23>", line 1, in <module>
re.match(r".*(.).*\1", "718ak").group(1)
AttributeError: 'NoneType' object has no attribute 'group'
>>> pair.match("354aa").group(1)
'a'
search() vs. match()
Python提供了基于正則表達(dá)式的兩種不同的基本操作:re.match()僅在字符串的開始處檢查匹配,而re.search()檢查字符串中任意位置的匹配(這是Perl默認(rèn)執(zhí)行的操作)。
例如:
>>> re.match("c", "abcdef") # No match
>>> re.search("c", "abcdef") # Match
<re.Match object; span=(2, 3), match='c'>
以'^'開頭的正則表達(dá)式可以與search()一起用于限制字符串開始處的匹配:
>>> re.match("c", "abcdef") # No match
>>> re.search("^c", "abcdef") # No match
>>> re.search("^a", "abcdef") # Match
<re.Match object; span=(0, 1), match='a'>
但是請注意,在MULTILINE模式下match()只匹配字符串的開頭,而使用search()與以'^'開頭的正則表達(dá)式匹配每行的開頭。
>>> re.match('X', 'A\nB\nX', re.MULTILINE) # No match
>>> re.search('^X', 'A\nB\nX', re.MULTILINE) # Match
<re.Match object; span=(4, 5), match='X'>
做一個(gè)電話本
split()將字符串分割成由正則表達(dá)式分隔的列表。該方法對于將文本數(shù)據(jù)轉(zhuǎn)換為可由Python輕松讀取和修改的數(shù)據(jù)結(jié)構(gòu)非常有用,如以下創(chuàng)建電話簿的示例所示。
首先,這是輸入。通常它可能來自一個(gè)文件:
>>> text = """Ross McFluff: 834.345.1254 155 Elm Street
...
... Ronald Heathmore: 892.345.3428 436 Finley Avenue
... Frank Burger: 925.541.7625 662 South Dogwood Way
...
...
... Heather Albrecht: 548.326.4584 919 Park Place"""
條目由一個(gè)或多個(gè)換行符分隔。 現(xiàn)在我們將字符串轉(zhuǎn)換為一個(gè)列表,每個(gè)非空行都有自己的條目:
>>> entries = re.split("\n+", text)
>>> entries
['Ross McFluff: 834.345.1254 155 Elm Street',
'Ronald Heathmore: 892.345.3428 436 Finley Avenue',
'Frank Burger: 925.541.7625 662 South Dogwood Way',
'Heather Albrecht: 548.326.4584 919 Park Place']
最后,將每個(gè)條目分成一個(gè)名字,姓氏,電話號碼和地址。 因?yàn)榈刂分杏锌崭?為了不把地址分隔開,我們使用split()的maxsplit參數(shù),:
>>> [re.split(":? ", entry, 3) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155 Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436 Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662 South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919 Park Place']]
我們可以將最多的房屋號碼與街道名稱分開:
>>> [re.split(":? ", entry, 4) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155', 'Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436', 'Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662', 'South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919', 'Park Place']]
Text Munging
Mung或munge是計(jì)算機(jī)術(shù)語,用于對一段數(shù)據(jù)或文件進(jìn)行一系列潛在的破壞性或不可撤銷的更改。它有時(shí)用于說話人尚不清楚的模糊數(shù)據(jù)轉(zhuǎn)換步驟。常見的搜索操作包括刪除標(biāo)點(diǎn)或html標(biāo)簽,數(shù)據(jù)解析,過濾和轉(zhuǎn)換。 [wiki]
sub()用一個(gè)字符串或一個(gè)函數(shù)的結(jié)果替換每個(gè)匹配規(guī)則的子部分。 這個(gè)例子演示了如何使用sub()和函數(shù)來"munge"文本,或者隨機(jī)化除了第一個(gè)和最后一個(gè)字符之外的每個(gè)單詞中所有字符的順序:
>>> def repl(m):
... inner_word = list(m.group(2))
... random.shuffle(inner_word)
... return m.group(1) + "".join(inner_word) + m.group(3)
>>> text = "Professor Abdolmalek, please report your absences promptly."
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Poefsrosr Aealmlobdk, pslaee reorpt your abnseces plmrptoy.'
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Pofsroser Aodlambelk, plasee reoprt yuor asnebces potlmrpy.'
尋找所有的動詞
findall()匹配所有匹配的子部分,而不僅僅是search()所做的第一個(gè)。 例如,如果一個(gè)人是作家,并且想要在某些文本中找到所有副詞,他或她可以按以下方式使用findall():
>>> text = "He was carefully disguised but captured quickly by police."
>>> re.findall(r"\w+ly", text)
['carefully', 'quickly']
尋找所有的動詞和對應(yīng)位置
如果想要獲得關(guān)于匹配文本的所有匹配的更多信息,finditer()很有用,因?yàn)樗峁┝似ヅ鋵ο蠖皇亲址?繼續(xù)前面的例子,如果一個(gè)作家想要在某些文本中找到所有副詞及其位置,他或她會按以下方式使用finditer():
>>> text = "He was carefully disguised but captured quickly by police."
>>> for m in re.finditer(r"\w+ly", text):
... print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))
07-16: carefully
40-47: quickly
原始字符串符號
原始字符串符號(r"text")使正則表達(dá)式保持正常。 沒有它,正則表達(dá)式中的每個(gè)反斜杠( '' )必須以另一個(gè)反斜杠作為前綴。 例如,以下兩行代碼在功能上是相同的:
>>> re.match(r"\W(.)\1\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>
>>> re.match("\\W(.)\\1\\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>
當(dāng)想要匹配文字反斜杠時(shí),它必須在正則表達(dá)式中轉(zhuǎn)義。 用原始字符串表示法,這意味著r"\"。 沒有原始字符串表示法,必須使用"\\",使以下代碼行功能相同:
>>> re.match(r"\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>
>>> re.match("\\\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>
總而言之,最好使用raw字符串
寫一個(gè)分詞器
分詞器或掃描器分析字符串以對字符組進(jìn)行分類。這是編寫編譯器或解釋器的第一步。所以,理論上來說,學(xué)完re模塊就可以寫一個(gè)屬于自己的計(jì)算機(jī)語言,努力吧,說不定你的語言會成為下一個(gè)python,成為廣受歡迎的語言。
下面就是告訴大家如何寫一個(gè)分詞器,讓你的編譯器識別代碼中的關(guān)鍵字,識別符,值等等元素。
編譯器首先需要把代碼中的一個(gè)個(gè)詞拆開分類為一個(gè)個(gè)token
一個(gè)token有'type' 屬性,'value'屬性,'line'屬性,'column'屬性
一條代碼 'if a > 1:' 就會被分為5個(gè)token
第一個(gè)token, 'type' 是關(guān)鍵字 , 'value'是IF, 'line'是該語句所在的行數(shù), 'column'是 if在該行從左往右數(shù)的位置
第二個(gè)token, 'type'是 標(biāo)識符(ID) 'value'是 a,
第三個(gè)token, 'type'是 操作符(Operator) 'value'是 '>'
第四個(gè)token, 'type'是 數(shù)量(Number) 'value'是 1
第五個(gè)token, 'type'是 判斷結(jié)束符(EndOfIf) 'value'是 :
就這樣把代碼拆開,準(zhǔn)確地識別各個(gè)token,是每一個(gè)編譯器工作的第一步。
token的結(jié)構(gòu)可以由你自己定義,但一般來說都要都有type屬性和value屬性,你還可以自己添加其他的屬性來方便編譯器工作,這取決于你的編程水平。
import collections
import re
# 這是一個(gè)簡單的定義一個(gè)class的方法,我們定義一個(gè)token類,我們把代碼拆開然后根據(jù)正則表達(dá)式的識別將其實(shí)例化為一個(gè)個(gè)token
Token = collections.namedtuple('Token', ['type', 'value', 'line', 'column'])
def tokenize(code):
# 定義我們的編譯器有哪些關(guān)鍵字,不同的語言關(guān)鍵字是不一樣的,需要語言開發(fā)者自行定義
keywords = {'IF', 'THEN', 'ENDIF', 'FOR', 'NEXT', 'GOSUB', 'RETURN'}
# 語言除了關(guān)鍵字,還有其他的類型
token_specification = [
# 定義我們語言能使用的數(shù)值
('NUMBER', r'\d+(\.\d*)?'), # Integer or decimal number
# 定義語言的賦值符號
('ASSIGN', r':='), # Assignment operator
#定義語句的結(jié)束標(biāo)識,一般來說都是分號,我們也用分號來標(biāo)識結(jié)尾吧
('END', r';'), # Statement terminator
#標(biāo)識符,比如變量的名字,就是一個(gè)標(biāo)識符,我們這里規(guī)則變量只能用字母連下劃線都不能用
('ID', r'[A-Za-z]+'), # Identifiers
#定義運(yùn)算符,我們的語言能進(jìn)行+-*/
('OP', r'[+\-*/]'), # Arithmetic operators
#要能夠識別新的一行代碼
('NEWLINE', r'\n'), # Line endings
#要能夠識別各種空白
('SKIP', r'[ \t]+'), # Skip over spaces and tabs
#其他任何的東西我們都是別錯(cuò)誤匹配,相當(dāng)于寫了錯(cuò)誤的語句,我們的語言要報(bào)錯(cuò),比如你在python里面寫prinf("hello")肯定是錯(cuò)誤的,這是C里面的語句
('MISMATCH',r'.'), # Any other character
]
#我們把上面的正則表達(dá)式用或串起來
tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification)
line_num = 1
line_start = 0
# 我們開始對代碼進(jìn)行識別
for mo in re.finditer(tok_regex, code):
kind = mo.lastgroup
value = mo.group(kind)
if kind == 'NEWLINE':
line_start = mo.end()
line_num += 1
elif kind == 'SKIP':
pass
elif kind == 'MISMATCH':
raise RuntimeError(f'{value!r} unexpected on line {line_num}')
else:
if kind == 'ID' and value in keywords:
kind = value
column = mo.start() - line_start
yield Token(kind, value, line_num, column)
statements = '''
IF quantity THEN
total := total + price * quantity;
tax := price * 0.05;
ENDIF;
'''
# 把我們實(shí)例化的token打印出來
for token in tokenize(statements):
print(token)
分詞器產(chǎn)生以下輸出:
Token(type='IF', value='IF', line=2, column=4)
Token(type='ID', value='quantity', line=2, column=7)
Token(type='THEN', value='THEN', line=2, column=16)
Token(type='ID', value='total', line=3, column=8)
Token(type='ASSIGN', value=':=', line=3, column=14)
Token(type='ID', value='total', line=3, column=17)
Token(type='OP', value='+', line=3, column=23)
Token(type='ID', value='price', line=3, column=25)
Token(type='OP', value='*', line=3, column=31)
Token(type='ID', value='quantity', line=3, column=33)
Token(type='END', value=';', line=3, column=41)
Token(type='ID', value='tax', line=4, column=8)
Token(type='ASSIGN', value=':=', line=4, column=12)
Token(type='ID', value='price', line=4, column=15)
Token(type='OP', value='*', line=4, column=21)
Token(type='NUMBER', value='0.05', line=4, column=23)
Token(type='END', value=';', line=4, column=27)
Token(type='ENDIF', value='ENDIF',line=5, column=4)
Token(type='END', value=';', line=5, column=9)
'