本章讨论正则表达式的量词,通过量词你可以匹配不同长度的重复字符类型,从而解决非定长的字符串的匹配。
4,量词
量词说白了就是如何表示重复,这里的重复不只是单个字符的重复,还包括不同字符的重复,例如数字0-9任意一个字符的重复。同时,还有贪婪和非贪婪的区别,接下来我们来一一说明。
4.1零次或者多次(*)
通过*跟在一个字符或者字符类后面,可以实现0次或者多次重复。我们看下下面的例子:
python
import re
a = "a12bca1234bcabc"
a1 = "abcbcbc"
b = re.findall(r'a[0-9]*bc', a)
c = re.findall(r'a[bc]*c', a1)
d = re.findall(r'a[bc]*?c', a1)
print("a= {}, b = {}, c = {}, d = {}".
format(a, b, c, d))
# 输出结果:a= a12bca1234bcabc, b = ['a12bc', 'a1234bc', 'abc'],
# c = ['abcbcbc'], d = ['abc']
a通过在0-9的后面跟一个*,表示这个0-9的任意数字出现0次或者多次,所以符合条件的有三个子串,第一个子串a12bc,数字出现了两次,第二个子串a1234bc,数字出现了4次,第三个是abc,数字出现了0次,因为*代表0次或者多次,所以abc也符合条件。
c通过bc*表示b或者c出现0次或者多次,我们发现匹配到了整个字符串abcbcbc。但是其实abc,abcbc这两个子串也符合条件,但是为什么匹配的是最多的字符的呢?这涉及到正则表达式的一个默认原则,贪婪匹配,就是尽量匹配更多的字符,按照这个原则,整个字符串都被匹配进来了。
有时候,我们不想匹配那么多,只想匹配满足条件的最少字符的字符串,那么这就用到了非贪婪匹配的语法。就是*后面加上一个?,这就告诉正则表达式,尽量在满足条件的情况下少匹配字符。就像d这样,我们只匹配了一个abc。
4.1.1贪婪与非贪婪
根据上面的代码,我们引出了贪婪和非贪婪的概念。在正在表达式中如果应用了不确定次数的匹配,比如*,那么正则表达式会尽量匹配多的字符,这就是贪婪匹配。如果你不想做贪婪匹配,就需要在后面加上?,表示尽量少的匹配字符。除了*,还有其他几个字符后面可以根?表示非贪婪,后面我们会一一谈到。因为第一个讲到的就是*,所以我们就先引入贪婪和非贪婪的概念,让你一开始就不带着疑问去看,虽然排版上看上去比较凌乱,但是不会让你带着疑问往下看,这是值得的。后面遇到可以根非贪婪字符?的情况,我们都会讲到。
4.2一次或者多次(+)
这个+和*唯一的区别就是+表示至少出现一次,而*可以出现0次。我们看下下面的代码:
python
import re
a = "a12bca1234bcabc"
a1 = "abcbcbcac"
b = re.findall(r'a[0-9]+bc', a)
b1 = re.findall(r'a[0-9]*bc', a)
c = re.findall(r'a[bc]+c', a1)
c1 = re.findall(r'a[bc]*c', a1)
d = re.findall(r'a[bc]+?c', a1)
d1 = re.findall(r'a[bc]*?c', a1)
print("a= {}, b = {}, b1={}, c = {}, c1 = {}, d = {}, d1 = {}".
format(a, b, b1, c, c1, d, d1))
# 输出结果:a= a12bca1234bcabc, b = ['a12bc', 'a1234bc'],
# b1=['a12bc', 'a1234bc', 'abc'],c = ['abcbcbc'],
# c1 = ['abcbcbc', 'ac'], d = ['abc'], d1 = ['abc', 'ac']
a1和之前代码不同的地方上我在后面又加了一个ac。
b通过0-9+表示从0到9至少出现一次,所以满足条件的只有a12bc和a1234bc这两个子串。对比b1,多了个abc,因为*允许数字出现0次。
c和d的结果一样,因为+也是默认贪婪,都是尽量的多匹配字符。
d和d1分别表示了*和+的非贪婪匹配,都是在后面加了?号。因为*允许出现0次,所以最后的ac也会被匹配。因为非贪婪,所以和c,c1的匹配结果完全不同。
4.3 0次或1次(?)
这个?和上面我们讲的在*或者+后跟的?是不一样的。这个?代表前面的字符只出现0次或者1次。当然,它也有对应的非贪婪的写法,就是后面跟?,也就是两个??。我们看一下代码:a
python
import re
a = "a2bca1234bcabc"
a1 = "abcbcbcacc"
b = re.findall(r'a[0-9]?bc', a)
c = re.findall(r'a[bc]?c', a1)
d = re.findall(r'a[bc]??c', a1)
print("a= {}, b = {}, c = {}, d = {}".
format(a, b, c, d))
# 输出结果:a= a2bca1234bcabc, b = ['a2bc', 'abc'],
# c = ['abc', 'acc'], d = ['abc', 'ac']
a可以看到正则表达式中0-9?表示在a和c之间出现0个或者一个数字,满足条件的字符串子串有a2bc以及abc。
c验证了?的默认的贪婪模式,我们可以看到匹配了acc子串,d我们用了??来非贪婪匹配,结果匹配了ac而不是acc。
4.4 确定次数({n})
上面我们学习了*,+和?都是出现不确定的次数。假如我们需要出现准确的次数,我们可以通过花括号中间填需要出现的次数来进行约束。例如我想字符a出现五次,那么可以直接在a后面跟{5}。我们看下面的例子:
python
import re
a = "a2bca1234bcabc"
a1 = "abcbcbcacc"
b = re.findall(r'a[0-9]{4}bc', a)
c = re.findall(r'ac{2}', a1)
print("a= {}, b = {}, c = {}".
format(a, b, c))
# 输出结果:a= a2bca1234bcabc, b = ['a1234bc'], c = ['acc']
b中设定了数字出现4次,所以只匹配到了a1234bc,c中设定字符c出现两次,所以匹配到了acc。这种经常用到明确知道固定字符数的场景,就好比我们之前的电话号码:r"13-9\d{9}"。
4.5从m到n次({m,n})
{m,n}代表一个范围,表示至少出现m次,至多出现n次。对于不确定的次数,所以我们很自然而然的想到贪婪和非贪婪。我想经过上面的学习,大家很容易想出来,非贪婪的写法就是在后面加?。我们看看下面的代码:
python
import re
a = "a2bca1234bcabc"
a1 = "abcbcbcacc"
b = re.findall(r'a[0-9]{1,4}bc', a)
c = re.findall(r'a.{0,9}c', a1)
d = re.findall(r'a.{0,9}?c', a1)
print("a= {}, a1= {}, b = {}, c = {}, d = {}".
format(a, a1, b, c, d))
# 输出结果:a= a2bca1234bcabc, a1= abcbcbcacc,
# b = ['a2bc', 'a1234bc'], c = ['abcbcbcacc'],
# d = ['abc', 'ac']
b中,a和bc之间的数字需要出现至少1次,最多4次,所以匹配到了a2bc和a1234bc。
c是贪婪式匹配,所以匹配到了整个字符串。d因为花括号后面加了?,所以进行非贪婪的匹配,所以匹配到了两个子串。注意这次我们用了通配符逗号,其实也可以用我们之前学到的\w来匹配单词字符,至少对这个例子来讲效果是一样的。
4.6 至少({m,})和最多{,n}
这个和上面学到的从m次到n次是一样的,只是一个限定了最少次数,一个限定了最多次数。同样,既然有不确定的次数,所以必然会有贪婪和非贪婪的区别,只要在后面加?即可。我们看下面的代码:
python
import re
a = "a2bca1234bcabc"
a1 = "abcbcbcacc"
b = re.findall(r'a[0-9]{1,}bc', a)
c = re.findall(r'a.{,9}c', a1)
d = re.findall(r'a.{,9}?c', a1)
print("a= {}, a1= {}, b = {}, c = {}, d = {}".
format(a, a1, b, c, d))
# 输出结果:a= a2bca1234bcabc, a1= abcbcbcacc,
# b = ['a2bc', 'a1234bc'], c = ['abcbcbcacc'],
# d = ['abc', 'ac']
上面的代码只是稍微的做了修改,和4.5的结果是一样的。我们将{1,4}改为{1,},将{0,9}改为{,9},注意这些数字,逗号,以及花括号之间不能有空格,要不然会不是你想要的结果,有兴趣的同学可以试一下。
到这里,我们量词就讲完了,由于正则表达式太过于复杂,所以我们每章的内容都不会太长。如果你读到这里,我强烈建议你复习一下前面学到的章节,以免遗忘。下一章我们讲位置断言,到时候你会再次碰到我们熟悉的\b。