小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
之前遇到一个极具挑战性的项目,它需要很多的技能,分别是正则表达式、正则表达式、以及正则表达式(我一个搞ai算法的为什么被送去搞软件开发啊!!!!呃呃呃~啊!!!!!)
那么我是遇到了什么样的问题从而促成了本文的诞生呢?Let's go!
0. re模块的match\search\findall
正式进入本文的核心知识点之前,简单介绍一下python re模块的findall(相信大部分读者都知道它们,请直接跳转至下一节)。
本节标题中出现的三个方法都是匹配字符串的方法,它们的具体功能与区别如下:
-
re.match: 尝试从
string的起始位置匹配一个pattern,匹配成功则返回匹配到的值,若在起始位置没有匹配成功的话,match()的返回值就为none(flags代表标志位,非本文重点故不展开)re.match(pattern, string, flags=0) -
re.search: 扫描整个
string并返回第一个成功的匹配re.search(pattern, string, flags=0) -
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地址了。