Python正则式还可以精确指定匹配的次数。指定的方式是
‘{m}’
‘{m,n}’
如果你只想指定一个最少次数或只指定一个最多次数,你可以把另外一个参数空起来。比如你想指定最少3次,可以写成 {3,} (注意那个逗号),同样如果只想指定最大为5次,可以写成{,5},也可以写成{0,5}。
例 寻找下面字符串中
a:3位数
b: 2位数到4位数
c: 5位数以上的数
d: 4位数以下的数
>>> s= ‘ 1 22 333 4444 55555 666666 ‘
>>> re.findall( r’\b\d{3}\b’ , s )
['333']
>>> re.findall( r’\b\d{2,4}\b’ , s )
['22', '333', '4444']
>>> re.findall( r’\b\d{5,}\b’, s )
['55555', '666666']
>>> re.findall( r’\b\d{1,4}\b’ , s )
['1', '22', '333', '4444']
‘*?’ ‘+?’ ‘??’ 最小匹配
‘*’ ‘+’ ‘?’通常都是尽可能多的匹配字符。有时候我们希望它尽可能少的匹配。比如一个c语言的注释 ‘ ’,如果使用最大规则:
>>> s =r ‘ code ’
>>> re.findall( r’/\*.*\*/’ , s )
[‘ code ’]
结果把整个字符串都包括进去了。如果把规则改写成
>>> re.findall( r’/\*.*?\*/’ , s )
['', '']
结果正确的匹配出了注释里的内容
1.3
有时候需要匹配一个跟在特定内容后面的或者在特定内容前面的字符串,Python提供一个简便的前向界定和后向界定功能,或者叫前导指定和跟从指定功能。它们是:
‘(?<=…)’ 前向界定
括号中’…’代表你希望匹配的字符串的前面应该出现的字符串。
‘(?=…)’
括号中的’…’代表你希望匹配的字符串后面应该出现的字符串。
例: 你希望找出c语言的注释中的内容,它们是包含在’’之间,不过你并不希望匹配的结果把’’也包括进来,那么你可以这样用:
>>> s=r’
>>> re.findall( r’(?<=/\*).+?(?=\*/)’ , s )
[' comment 1 ', ' comment 2 ']
注意这里我们仍然使用了最小匹配,以避免把整个字符串给匹配进去了。
要注意的是,前向界定括号中的表达式必须是常值,也即你不可以在前向界定的括号里写正则式。比如你如果在下面的字符串中想找到被字母夹在中间的数字,你不可以用前向界定:
例:
>>> s = ‘aaa111aaa , bbb222 , 333ccc ‘
>>> re.findall( r’(?<=[a-z]+)\d+(?=[a-z]+)' , s )
它会给出一个错误信息:
error: look-behind requires fixed-width pattern
不过如果你只要找出后面接着有字母的数字,你可以在后向界定写正则式:
>>> re.findall( r’\d+(?=[a-z]+)’, s )
['111', '333']
如果你一定要匹配包夹在字母中间的数字,你可以使用组(group)的方式
>>> re.findall (r'[a-z]+(\d+)[a-z]+' , s )
['111']
组的使用将在后面详细讲解。
除了前向界定前向界定和后向界定外,还有前向非界定和后向非界定,它的写法为:
‘(?<!...)’
前向非界定
只有当你希望的字符串前面不是’…’的内容时才匹配
‘(?!...)’
后向非界定
只有当你希望的字符串后面不跟着’…’内容时才匹配。
接上例,希望匹配后面不跟着字母的数字
>>> re.findall( r’\d+(?!\w+)’ , s )
['222']
注意这里我们使用了\w而不是像上面那样用[a-z],因为如果这样写的话,结果会是:
>>> re.findall( r’\d+(?![a-z]+)’ , s )
['11', '222', '33']
这和我们期望的似乎有点不一样。它的原因,是因为’111’和’222’中的前两个数字也是满足这个要求的。因此可看出,正则式的使用还是要相当小心的,因为我开始就是这样写的,看到结果后才明白过来。不过Python试验起来很方便,这也是脚本语言的一大优点,可以一步一步的试验,快速得到结果,而不用经过烦琐的编译、链接过程。也因此学习Python就要多试,跌跌撞撞的走过来,虽然曲折,却也很有乐趣。
1.4 组的基本知识
上面我们已经看过了Python的正则式的很多基本用法。不过如果仅仅是上面那些规则的话,还是有很多情况下会非常麻烦,比如上面在讲前向界定和后向界定时,取夹在字母中间的数字的例子。用前面讲过的规则都很难达到目的,但是用了组以后就很简单了。
‘(‘’)’
最基本的组是由一对圆括号括起来的正则式。比如上面匹配包夹在字母中间的数字的例子中使用的(\d+),我们再回顾一下这个例子:
>>> s = ‘aaa111aaa , bbb222 , 333ccc ‘
>>> re.findall (r'[a-z]+(\d+)[a-z]+' , s )
['111']
可以看到findall函数只返回了包含在’()’中的内容,而虽然前面和后面的内容都匹配成功了,却并不包含在结果中。
除了最基本的形式外,我们还可以给组起个名字,它的形式是
‘(?P<name>…)’ 命名组
‘(?P’代表这是一个Python的语法扩展’<…>’里面是你给这个组起的名字,比如你可以给一个全部由数字组成的组叫做’num’,它的形式就是’(?P<num>\d+)’。起了名字之后,我们就可以在后面的正则式中通过名字调用这个组,它的形式是
‘(?P=name)’ 调用已匹配的命名组
要注意,再次调用的这个组是已被匹配的组,也就是说它里面的内容是和前面命名组里的内容是一样的。
我们可以看更多的例子:请注意下面这个字符串各子串的特点。
>>> s='aaa111aaa,bbb222,333ccc,444ddd444,555eee666,fff777ggg'
我们看看下面的正则式会返回什么样的结果:
>>> re.findall( r'([a-z]+)\d+([a-z]+)' , s )
[('aaa', 'aaa'), ('fff', 'ggg')]
>>> re.findall( r '(?P<g1>[a-z]+)\d+(?P=g1)' , s ) #找出被中间夹有数字的前后同样的字母
['aaa']
>>> re.findall( r'[a-z]+(\d+)([a-z]+)' , s )
[('111', 'aaa'), ('777', 'ggg')]
我们可以通过命名组的名字在后面调用已匹配的命名组,不过名字也不是必需的。
‘\number’
正则式中的每个组都有一个序号,序号是按组从左到右,从1开始的数字,你可以通过下面的形式来调用已匹配的组
比如上面找出被中间夹有数字的前后同样的字母的例子,也可以写成:
>>> re.findall( r’([a-z]+)\d+\1’ , s )
['aaa']
结果是一样的。
我们再看一个例子
>>> s='111aaa222aaa111 , 333bbb444bb33'
>>> re.findall( r'(\d+)([a-z]+)(\d+)(\2)(\1)' , s )
[('111', 'aaa', '222', 'aaa', '111')]
Python2.4以后的re模块,还加入了一个新的条件匹配功能
‘(?(
id/name)yes-pattern|no-pattern)’
判断指定组是否已匹配,执行相应的规则
这个规则的含义是,如果id/name指定的组在前面匹配成功了,则执行yes-pattern的正则式,否则执行no-pattern的正则式。
举个例子,比如要匹配一些形如 usr@mail 的邮箱地址,不过有的写成< usr@mail >即用一对<>括起来,有点则没有,要匹配这两种情况,可以这样写
>>> s='<usr1@mail1>
>>> re.findall( r'(<)?\s*(\w+@\w+)\s*(?(1)>)' , s )
[('<', 'usr1@mail1'), ('', 'usr2@maill2')]
不过如果目标字符串如下
>>> s='<usr1@mail1>
而你想得到要么由一对<>包围起来的一个邮件地址,要么得到一个没有被<>包围起来的地址,但不想得到一对<>中间包围的多个地址或不完整的<>中的地址,那么使用这个式子并不能得到你想要的结果
>>> re.findall( r'(<)?\s*(\w+@\w+)\s*(?(1)>)' , s )
[('<', 'usr1@mail1'), ('', 'usr2@maill2'), ('', 'usr3@mail3'), ('', 'usr4@mail4'), ('', 'usr5@mail5')]
它仍然找到了所有的邮件地址。
想要实现这个功能,单纯的使用findall有点吃力,需要使用其它的一些函数,比如match或search函数,再配合一些控制功能。这部分的内容将在下面详细讲解。
小结:以上基本上讲述了Python正则式的语法规则。虽然大部分语法规则看上去都很简单,可是稍不注意,仍然会得到与期望大相径庭的结果,所以要写好正则式,需要仔细的体会正则式规则的含义后不同规则之间细微的差别。
详细的了解了规则后,再配合后面就要介绍的功能函数,就能最大的发挥正则式的威力了。