文:郑元春
人生苦短,我用Python!
接上篇《编程学习--正则表达式(入门一)》,今天把剩下的基础部分完成,然后开始讲解Python的re模块,有时间的话就讲解下这个模块的源码。分析源码会让理解更加的透彻
5.逻辑和分组
经过这两天的学习和写码测试,终于明白了分组和《入门一》讲的正则表达式不同了。前面讲的正则表达式都是全匹配的,结果会把所有的符合情况的字符串全部返回(输出),但是分组的话,他只会输出符合分组模式的字符串,组外的字符串(如果有的话)只是用来做匹配辅助的。
刚开始一直在纠结为啥组外的字符串没有输出来,今天突然想通了,Python的分组就是为了过滤符合分组模式的字符串的,所以Python的分组模式虽然是将整个的模式匹配了一遍,但是最后只会输出分组内的内容,在文中我会告诉你们怎么输出所有的匹配字符串。(那个在线的正则匹配验证的网站中还是会输出辅助字符的)。
- 5.1
|
这个就是‘或’功能了,很简单不解释。|
的优先级比较低,所以ab|cd
能够匹配的模式是ab
或者是cd
.如果|
是在分组内的话,那么分组就会改变他的优先级,它的范围就只能在分组内了。
#case 1: 没有分组
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函数,不是finall函数
result.groups()
>>>('c',)
result.span()
>>>(15, 18)
result.group(1)
>>>'c'
具体的分组下面会讲解,可以看到,虽然输出的仅仅是组内的匹配内容,但是通过span()还是可以看到其实是匹配了整个的acd
的。
- 5.2
()
括号括起来的表达式将作为分组.为什么要引入分组呢,主要的原因还是在数量词的组合运用上。
有的时候模式后面会跟着数量词,而数量词只是匹配前一个字符,也就是现在只能重复单个字符,如果我们重复一个字符串怎么办呢,使用()
分组可以将多个字符组合成一个字符串,之后就可以指定这个字符串的重复规则了。
比如 (ab){2,3}
就表示的是abab
或者是ababab
。分组是可以嵌套使用的,将正则表达式从左边到右边扫描,每个分组就是找到的左括号(
的顺序,以为后面还有使用分组索引、分组别名来替换分组的使用方法,这里只是顺便一提。
在这里先说下re模块中的两个函数:group和groups。具体的使用将会在re模块那一章节里面进行讲解。这里只是为了下面需要使用才进行简单的说明。
group和groups是两个不同的函数。
分组与不分组的情况区别是一个带着括号,另一个不带着括号,有的时候我们需要的是返回的字符串只匹配分组内的规则,有的时候我们需要返回的字符串是匹配整个正则表达式的规则,这两种需求都可以使用re.search()
和re.match()
函数测试。两种函数都会返回一个matchobject
对象,这个对象里面有如下的属性或者是方法:
属性:
- string:
匹配时使用的文本。
- re:
匹配时使用的Pattern对象。
- pos:
文本中正则表达式开始搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
- endpos:
文本中正则表达式结束搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
- lastindex:
最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None。
- lastgroup:
最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None。
方法:
- group([group1, …]):
获得一个或多个分组截获的字符串;指定多个参数时将以元组形式返回。group1可以使用编号也可以使用别名;编号0代表整个匹配的子串;不填写参数时,返回group(0);没有截获字符串的组返回None;截获了多次的组返回最后一次截获的子串。
- groups([default]):
以元组形式返回全部分组截获的字符串。相当于调用group(1,2,…last)。default表示没有截获字符串的组以这个值替代,默认为None。
- groupdict([default]):
返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上。
- start([group]):
返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0。
- end([group]):
返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0。
- span([group]):
返回(start(group), end(group))。
- expand(template):
将匹配到的分组代入template中然后返回。template中可以使用\id或\g<id>、\g<name>引用分组,但不能使用编号0。\id与\g<id>是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符'0',只能使用\g<1>0。
由于我们的分组是可以嵌套使用的,所以每个分组从左到右的索引就是扫描顺序,通过group(n)的方式来访问匹配的分组的字符。
而group() == group(0) == 所有匹配的字符,与括号无关,这个是API规定的。
所以带有分组的会返回两种格式,一种是和不带分组()
一样,返回的是匹配整个正则表达式的字符串,一种是只返回分组内的字符串。下面用代码说话。
#case 1:最简单的分组
reStr="a(bc)"
targetStr="abcabc"
# 使用findall方式
re.findall(reStr,targetStr)
>>>['bc', 'bc'] #返回所有的匹配到的组内字符串
# 使用search方式
result=re.search(reStr,targetStr)
result.group()
>>>'abc' #分组外的字符a也输出了
result.group(0)
>>>'abc'
result.group(1)
>>>'bc' #只输出分组中匹配的内容
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
通过case 1和case 2的比较就可以明白分组的索引是怎么回事了吧。同时也能够明白group()``group(0)
以及group(n)
还有groups()
的关系了吧。
#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: 与数量模式并用
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:与`|`的联合使用
reStr="(ab|cd)"
targetStr="abcd"
result=re.search(reStr,targetStr)
result.group()
>>>'ab'
result.group(1)
>>>'ab'
result.groups()
>>>('ab',)
- 5.3
(\\<number>)
分组的时候,每一对括号代表着一个分组,从左边到右边依次编号为1,2,3......。match或者是search函数返回的是有group信息,通过访问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的时候一定要在前面先写好分组模式,后面才能引用
- 5.4
(?P<name>)
上面说的时候有没有点匿名函数的意思啊,其实还可以使用命名的方式来引用前面的分组信息。
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'
当使用name的时候就可以使用group(name)的方式来访问了
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中已经说明了name的使用方法了。
总结
分组还是比较好的一个功能的,做过
Django
项目的同学一定在url配置的时候使用过正则表达式,所以url在Django叫做url pattern
。
同时需要注意group()和group(n)以及groups()的用法!另外,可以使用name来标识分组也是很人性化的。
网友评论