文:鄭元春
人生苦短,我用Python!
接上篇《編程學(xué)習(xí)--正則表達(dá)式(入門一)》,今天把剩下的基礎(chǔ)部分完成,然后開(kāi)始講解Python的re模塊,有時(shí)間的話就講解下這個(gè)模塊的源碼。分析源碼會(huì)讓理解更加的透徹
5.邏輯和分組
經(jīng)過(guò)這兩天的學(xué)習(xí)和寫碼測(cè)試,終于明白了分組和《入門一》講的正則表達(dá)式不同了。前面講的正則表達(dá)式都是全匹配的,結(jié)果會(huì)把所有的符合情況的字符串全部返回(輸出),但是分組的話,他只會(huì)輸出符合分組模式的字符串,組外的字符串(如果有的話)只是用來(lái)做匹配輔助的。
剛開(kāi)始一直在糾結(jié)為啥組外的字符串沒(méi)有輸出來(lái),今天突然想通了,Python的分組就是為了過(guò)濾符合分組模式的字符串的,所以Python的分組模式雖然是將整個(gè)的模式匹配了一遍,但是最后只會(huì)輸出分組內(nèi)的內(nèi)容,在文中我會(huì)告訴你們?cè)趺摧敵鏊械钠ヅ渥址?。(那個(gè)在線的正則匹配驗(yàn)證的網(wǎng)站中還是會(huì)輸出輔助字符的)。
- 5.1
|
這個(gè)就是‘或’功能了,很簡(jiǎn)單不解釋。|的優(yōu)先級(jí)比較低,所以ab|cd能夠匹配的模式是ab或者是cd.如果|是在分組內(nèi)的話,那么分組就會(huì)改變他的優(yōu)先級(jí),它的范圍就只能在分組內(nèi)了。
#case 1: 沒(méi)有分組
reStr="ab|cd"
targetStr="ab cd abcd abc acd"
re.findall(reStr,targetStr)
>>>['ab','cd','ab','cd','ab','cd']
#case 2:使用分組(下部分講分組)
reStr="a(b|c)d"
targetStr="ab cd abcd abc acd"
re.findall(reStr,targetStr)
>>>['c']
#先看下分組
result=re.search(reStr,targetStr) #注意這里使用的是search函數(shù),不是finall函數(shù)
result.groups()
>>>('c',)
result.span()
>>>(15, 18)
result.group(1)
>>>'c'
具體的分組下面會(huì)講解,可以看到,雖然輸出的僅僅是組內(nèi)的匹配內(nèi)容,但是通過(guò)span()還是可以看到其實(shí)是匹配了整個(gè)的acd的。
- 5.2
()
括號(hào)括起來(lái)的表達(dá)式將作為分組.為什么要引入分組呢,主要的原因還是在數(shù)量詞的組合運(yùn)用上。
有的時(shí)候模式后面會(huì)跟著數(shù)量詞,而數(shù)量詞只是匹配前一個(gè)字符,也就是現(xiàn)在只能重復(fù)單個(gè)字符,如果我們重復(fù)一個(gè)字符串怎么辦呢,使用()分組可以將多個(gè)字符組合成一個(gè)字符串,之后就可以指定這個(gè)字符串的重復(fù)規(guī)則了。
比如 (ab){2,3}就表示的是abab或者是ababab。分組是可以嵌套使用的,將正則表達(dá)式從左邊到右邊掃描,每個(gè)分組就是找到的左括號(hào)(的順序,以為后面還有使用分組索引、分組別名來(lái)替換分組的使用方法,這里只是順便一提。
在這里先說(shuō)下re模塊中的兩個(gè)函數(shù):group和groups。具體的使用將會(huì)在re模塊那一章節(jié)里面進(jìn)行講解。這里只是為了下面需要使用才進(jìn)行簡(jiǎn)單的說(shuō)明。
group和groups是兩個(gè)不同的函數(shù)。
分組與不分組的情況區(qū)別是一個(gè)帶著括號(hào),另一個(gè)不帶著括號(hào),有的時(shí)候我們需要的是返回的字符串只匹配分組內(nèi)的規(guī)則,有的時(shí)候我們需要返回的字符串是匹配整個(gè)正則表達(dá)式的規(guī)則,這兩種需求都可以使用re.search()和re.match()函數(shù)測(cè)試。兩種函數(shù)都會(huì)返回一個(gè)matchobject對(duì)象,這個(gè)對(duì)象里面有如下的屬性或者是方法:
屬性:
- string:
匹配時(shí)使用的文本。
- re:
匹配時(shí)使用的Pattern對(duì)象。
- pos:
文本中正則表達(dá)式開(kāi)始搜索的索引。值與Pattern.match()和Pattern.seach()方法的同名參數(shù)相同。
- endpos:
文本中正則表達(dá)式結(jié)束搜索的索引。值與Pattern.match()和Pattern.seach()方法的同名參數(shù)相同。
- lastindex:
最后一個(gè)被捕獲的分組在文本中的索引。如果沒(méi)有被捕獲的分組,將為None。
- lastgroup:
最后一個(gè)被捕獲的分組的別名。如果這個(gè)分組沒(méi)有別名或者沒(méi)有被捕獲的分組,將為None。
方法:
- group([group1, …]):
獲得一個(gè)或多個(gè)分組截獲的字符串;指定多個(gè)參數(shù)時(shí)將以元組形式返回。group1可以使用編號(hào)也可以使用別名;編號(hào)0代表整個(gè)匹配的子串;不填寫參數(shù)時(shí),返回group(0);沒(méi)有截獲字符串的組返回None;截獲了多次的組返回最后一次截獲的子串。
- groups([default]):
以元組形式返回全部分組截獲的字符串。相當(dāng)于調(diào)用group(1,2,…last)。default表示沒(méi)有截獲字符串的組以這個(gè)值替代,默認(rèn)為None。
- groupdict([default]):
返回以有別名的組的別名為鍵、以該組截獲的子串為值的字典,沒(méi)有別名的組不包含在內(nèi)。default含義同上。
- start([group]):
返回指定的組截獲的子串在string中的起始索引(子串第一個(gè)字符的索引)。group默認(rèn)值為0。
- end([group]):
返回指定的組截獲的子串在string中的結(jié)束索引(子串最后一個(gè)字符的索引+1)。group默認(rèn)值為0。
- span([group]):
返回(start(group), end(group))。
- expand(template):
將匹配到的分組代入template中然后返回。template中可以使用\id或\g<id>、\g<name>引用分組,但不能使用編號(hào)0。\id與\g<id>是等價(jià)的;但\10將被認(rèn)為是第10個(gè)分組,如果你想表達(dá)\1之后是字符'0',只能使用\g<1>0。
由于我們的分組是可以嵌套使用的,所以每個(gè)分組從左到右的索引就是掃描順序,通過(guò)group(n)的方式來(lái)訪問(wèn)匹配的分組的字符。
而group() == group(0) == 所有匹配的字符,與括號(hào)無(wú)關(guān),這個(gè)是API規(guī)定的。
所以帶有分組的會(huì)返回兩種格式,一種是和不帶分組()一樣,返回的是匹配整個(gè)正則表達(dá)式的字符串,一種是只返回分組內(nèi)的字符串。下面用代碼說(shuō)話。
#case 1:最簡(jiǎn)單的分組
reStr="a(bc)"
targetStr="abcabc"
# 使用findall方式
re.findall(reStr,targetStr)
>>>['bc', 'bc'] #返回所有的匹配到的組內(nèi)字符串
# 使用search方式
result=re.search(reStr,targetStr)
result.group()
>>>'abc' #分組外的字符a也輸出了
result.group(0)
>>>'abc'
result.group(1)
>>>'bc' #只輸出分組中匹配的內(nèi)容
result.groups()
>>>('bc',) #輸出的是tuple
#case 2:分組索引
reStr="a(bc)(de)"
targetStr="abcde bc de"
re.findall(reStr,targetStr)
>>>[('bc', 'de')]
result=re.search(reStr,targetStr)
result.group()
>>>'abcde'
result.group(0)
>>>'abcde'
result.group(1)
>>>'bc'
result.group(2)
>>>'de'
result.groups()
>>>('bc', 'de')
result.group(3)
>>>IndexError: no such group
result.group
通過(guò)case 1和case 2的比較就可以明白分組的索引是怎么回事了吧。同時(shí)也能夠明白group()``group(0)以及group(n)還有groups()的關(guān)系了吧。
#case 3:分組嵌套
reStr="a(b(cd))"
targetStr="cd bcd abcd"
re.findall(reStr,targetStr)
>>>[('bcd', 'cd')]
result=re.search(reStr,targetStr)
result.group()
>>>'abcd'
result.group(0)
>>>'abcd'
result.group(1)
>>>'bcd'
result.group(2)
>>>'cd'
result.groups()
>>>('bcd', 'cd')
#case 4: 與數(shù)量模式并用
reStr="(abc){1,2}"
targetStr="abcabc"
re.findall(reStr,targetStr)
>>>['abc']
result=re.search(reStr,targetStr)
result.group()
>>>'abcabc'
result.group(0)
>>>'abcabc'
result.group(1)
>>>'abc'
result.groups()
>>>('abc',)
#case 5:與`|`的聯(lián)合使用
reStr="(ab|cd)"
targetStr="abcd"
result=re.search(reStr,targetStr)
result.group()
>>>'ab'
result.group(1)
>>>'ab'
result.groups()
>>>('ab',)
- 5.3
(\\<number>)
分組的時(shí)候,每一對(duì)括號(hào)代表著一個(gè)分組,從左邊到右邊依次編號(hào)為1,2,3......。match或者是search函數(shù)返回的是有g(shù)roup信息,通過(guò)訪問(wèn)group(1),group(2)可以直接取得分組索引為1,2的分組。
reStr=r"(ab)(cd)\\1" #注意前面的r,以后還是全部帶r吧
targetStr="abcdab"
re.findall(reStr,targetStr)
>>>[('ab', 'cd')]
result=re.search(reStr,targetStr)
result.group()
>>>'ababab'
result.group(1)
>>>'ab'
#如果將正則模式換成
reStr=r"(ab)(cd)\\1\\2"
targetStr="abcdabcd"
result=re.search(reStr,targetStr)
result.group()
>>>'abcdabcd'
result.group(1)
>>>'ab'
result.group(2)
>>>'cd'
注意:使用index的時(shí)候一定要在前面先寫好分組模式,后面才能引用
- 5.4
(?P<name>)
上面說(shuō)的時(shí)候有沒(méi)有點(diǎn)匿名函數(shù)的意思啊,其實(shí)還可以使用命名的方式來(lái)引用前面的分組信息。
reStr=r"(?P<g1>ab)(?P=g1)"
targetStr="abab"
result=re.search(reStr,targetStr)
result.group()
>>>'abab'
result.group(1)
>>>'ab'
result.groupdict()
>>>{'g1': 'ab'}
result.group('g1')
>>>'ab'
當(dāng)使用name的時(shí)候就可以使用group(name)的方式來(lái)訪問(wèn)了
reStr=r"(?P<g1>ab)c(?P<g2>de)(?P=g1)(?P=g2)"
targetStr="abcdeabde"
result=re.search(reStr,targetStr)
result.group()
>>>'abcdeabde'
- 5.5
(?P=name)
上面在5.4中已經(jīng)說(shuō)明了name的使用方法了。
總結(jié)
分組還是比較好的一個(gè)功能的,做過(guò)
Django項(xiàng)目的同學(xué)一定在url配置的時(shí)候使用過(guò)正則表達(dá)式,所以u(píng)rl在Django叫做url pattern。
同時(shí)需要注意group()和group(n)以及groups()的用法!另外,可以使用name來(lái)標(biāo)識(shí)分組也是很人性化的。