Python 正则表达式,“小括号”里有大坑

3,426 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

之前遇到一个极具挑战性的项目,它需要很多的技能,分别是正则表达式、正则表达式、以及正则表达式(我一个搞ai算法的为什么被送去搞软件开发啊!!!!呃呃~啊!!!!!)

那么我是遇到了什么样的问题从而促成了本文的诞生呢?Let's go!

0. re模块的match\search\findall

正式进入本文的核心知识点之前,简单介绍一下python re模块的findall(相信大部分读者都知道它们,请直接跳转至下一节)。

本节标题中出现的三个方法都是匹配字符串的方法,它们的具体功能与区别如下:

  1. re.match: 尝试从string起始位置匹配一个pattern,匹配成功则返回匹配到的值,若在起始位置没有匹配成功的话,match()的返回值就为none(flags代表标志位,非本文重点故不展开)

    re.match(pattern, string, flags=0)
    
  2. re.search: 扫描整个string返回第一个成功的匹配

    re.search(pattern, string, flags=0)
    
  3. re.findall():string中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表(pos:起始位置偏移量,endpos:结束位置)

    findall(string[, pos[, endpos]])
    

1. 我踩到了什么坑

首先阐明需求,我需要在输入的string中匹配出所有的ipv4地址,即类似于192.168.1.53这样的一组子串。显然,因为是需要匹配出所有子串,于是便使用到了本文标题中所出现re.findall()。

那么基于上述需求,我的正则表达式是怎么写的呢?

既然匹配的都是数字,使用\d肯定是一个好的选择,多个数字则是\d+,但此时出现了一头拦路虎——点,easy,我反手就是一个‘\.’成功匹配。

其实我们可以注意到,在匹配到了最前面的三个数字(比如此处的192)后,剩下的内容都是“点+几个数字”的形式,通过".\d+ "的这一表达式则可成功匹配。但ipv4可有三组“点+几个数字”这样的内容(比如此处的[.168,.1 ,.53]),这应该怎么解决呢?当然我们可以耿直的将“.\d+ ”直接键入三次,那样也就没有这篇文章了(括弧笑)。

因此我们顺理成章的可以想到,将“.\d+ ”给括起来,再接一个加号,变成“ (.\d+)+ ”的形式,这就表示我们想要匹配多个“点+几个数字”,此时再在其前面加上我们最开始匹配192的\d+,pattern变成“\d+(.\d+)+ ”的形式,就可以成功的匹配到ipv4形式的string了....吗???

注意!!!注意!!!

坑出现了!!!

在正则表达式中(不仅限于python,而是所有的编程语言),如果你的pattern中出现了小括号,那么编程语言会将小括号视作匹配边界,换句话说,正则表达式会把小括号中的内容视为一个group,这个group中的内容才是编程语言眼里的pattern,而不是你键入的那一长串东西。此时正则表达式将匹配该小括号中的内容,并且只返回最后一个匹配的项

比如在本节的例子中,findall()将把小括号中的”.\d+ “视作它需要匹配的pattern,此时满足条件的项有[.168,.1,.53]三个,正则表达式会为你返回最后一个满足条件的“.53”,而不是我想要的192.168.1.53!!!

2. 如何解决这个大坑

我们直接一点:在括号内的最前面,加上一个" ?: ",问题得解。示例如下:

r'\d+(.\d+)+' #试图匹配192.168.1.53类型的字符串,然而因为其将小括号内视为一个group,因此只会返回.53(只匹配小括号内且只返回最后一个符合的项)
r'\d+(?:.\d+)+' #正确写法,?:即声明这不是一个group

此时我们便能成功匹配到想要的ipv4地址了。