非捕获组命名组
精心设计的正则表达式可能会划分不少组,这些组不只能够匹配相关的子串,还可以对正则表达式自己进行分组和结构化。在复杂的正则表达式中,因为有太多的组,所以经过组的序号来跟踪和使用会变得困难。有两个新的功能能够帮你解决这个问题——非捕获组和命名组——它们都使用了一个公共的正则表达式扩展语法。咱们先来看看这个表达式扩展语法是什么。
正则表达式的扩展语法
众所周知,Perl 5 为标准的正则表达式增长了许多强大的功能。Perl 的开发者们并不能选择一个新的元字符或者经过反斜杠构造一个新的特殊序列来实现扩展的功能。由于这样会和标准的正则表达式发生冲突。好比你想选择 & 做为扩展功能的元字符(在标准正则表达式中,& 没有特殊意义),但这样的话,已经按照标准语法写出来的正则表达式就不得不修改,由于它们中包含的 '&' 意愿上只是把它当作普通字符来匹配而已。
小甲鱼解释:看起来非常头疼的兼容性问题,Perl 的开发者们是如何解决的呢?请接着看......
最终,Perl 的开发者们决定使用 (?...) 做为扩展语法。问号 ? 紧跟在左小括号 ( 后边,自己是一个语法错误的写法,由于 ?前边没有东西能够重复,因此这样就解决了兼容性的问题(理由是语法正确的正则表达式确定不会这么写嘛~)。而后,紧跟在 ? 后边的字符则表示哪些扩展语法会被使用。例如 (?=foo) 表示一种新的扩展功能(前向断言),(?:foo) 则表示另外一种扩展功能(一个包含子串 foo 的非捕获组)。
Python 支持 Perl 的一些扩展语法,而且在此基础上还增长了一个扩展语法。若是紧跟在问号 ? 后边的是 P,那么能够确定这是一个 Python 的扩展语法。
好,既然咱们已经知道了如何对正则表达式的标准语法进行扩展,那咱们回来看看这些扩展语法在复杂的正则表达式中是如何应用的。
非捕获组
第一个咱们要讲的是非捕获组。有时候你知识须要用一个组来表示部分正则表达式,你并不须要这个组去匹配任何东西,这时你能够经过非捕获组来明确表示你的意图。非捕获组的语法是 (?:...),这个 ... 你能够替换为任何正则表达式。
正则表达式
小甲鱼解释:“捕获”就是匹配的意思啦,普通的子组都是捕获组,由于它们能从字符串中匹配到数据。
除了你不能从非捕获组得到匹配的内容以外,其余的非捕获组跟普通子组没有什么区别了。你能够在里边听任何东西,使用重复功能的元字符,或者跟其余子组进行嵌套(捕获的或者非捕获的子组均可以)。
当你须要修改一个现有的模式的时候,(?:...) 是很是有用的。原始是添加一个非捕获组并不会影响到其余(捕获)组的序号。值得一提的是,在搜索的速度上,捕获组和非捕获组的速度是没有任何区别的。
命名组
咱们再来看另一个重要功能:命名组。普通子组咱们使用序列来访问它们,命名组则可使用一个有意义的名字来进行访问。
命名组的语法是 Python 特有的扩展语法:(?P<name>)。很明显,< > 里边的 name 就是命名组的名字啦。命名组除了有一个名字标识以外,跟其余捕获组是同样的。
匹配对象的全部方法不只能够处理那些由数字引用的捕获组,还能够处理经过字符串引用的命名组。除了使用名字访问,命名组仍然可使用数字序号进行访问:
spring
命名组很是好用,由于它让你可使用一个好记的名字代替一些毫无心义的数字。下边是来自 imaplib 模块的例子:
spa
很明显,使用 m.group('zonem') 访问匹配内容要比使用数字 9 更简单明了。
正则表达式中,反向引用的语法像 (...)\1 是使用序号的方式来访问子组;在命名组里,显然也是有对应的变体:使用名字来代替序号。其扩展语法是 (?P=name),含义是该 name 指向的组须要在当前位置再次引用。那么搜索两个单词的正则表达式能够写成 (\b\w+)\s+\1,也能够写成 (?P<word>\b\w+)\s+(?P=word):
设计
前向断言
咱们要讲解的另外一个零宽断言是前向断言,前向断言能够分为前向确定断言和前向否认断言两种形式。
(?=...)code
前向确定断言。若是当前包含的正则表达式(这里以 ... 表示)在当前位置成功匹配,则表明成功,不然失败。一旦该部分正则表达式被匹配引擎尝试过,就不会继续进行匹配了;剩下的模式在此断言开始的地方继续尝试。对象
(?!...)开发
前向否认断言。这跟前向确定断言相反(不匹配则表示成功,匹配表示失败)。字符串
为了使你们更易懂,咱们举个例子来证实这玩意是真的颇有用。你们考虑一个简单的正则表达式模式,这个模式的做用是匹配一个文件名。咱们都知道,文件名是用 . 将名字和扩展名分隔开的。例如在 fishc.txt 中,fishc 是文件的名字,.txt 是扩展名。
这个正则表达式其实挺简单的:
.*[.].*$
注意,这里用于分隔的 . 是一个元字符,因此咱们使用 [.] 剥夺了它的特殊功能。还有 $,咱们使用 $ 确保字符串剩余的部分都包含在扩展名中。因此这个正则表达式能够匹配 fishc.txt,foo.bar,autoexec.bat,sendmail.cf,printers.conf 等。
如今咱们来考虑一种复杂一点的状况,若是你想匹配扩展名不是 bat 的文件,你的正则表达式应该怎么写呢?
咱们先来看下你有可能写错的尝试:
.*[.][^b].*$
这里为了排除 bat,咱们先尝试排除扩展名的第一个字符为非 b。但这是错误的开始,由于 foo.bar 后缀名的第一个字符也是 b。
为了弥补刚刚的错误,咱们试了这一招:
.*[.]([^b]..|.[^a].|..[^t])$
咱们不得不认可,这个正则表达式变得很难看......但这样第一个字符不是 b,第二个字符不是 a,第三个字符不是 t......这样正好能够接受 foo.bar,排除 autoexec.bat。但问题又来了,这样的正则表达式要求扩展名必须是三个字符,好比sendmail.cf 就会被排除掉。
好吧,咱们接着修复问题:
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
在第三次尝试中,咱们让第二个和第三个字符变成可选的。这样就能够匹配稍短的扩展名,好比 sendmail.cf。
不得不认可,咱们把事情搞砸了,如今的正则表达式变得艰涩难懂外加奇丑无比!!
更惨的是若是需求改变了,例如你想同时排除 bat 和 exe 扩展名,这个正则表达式模式就变得更加复杂了......
当当当当!主角登场,其实,一个前向否认断言就能够解决你的难题:
.*[.](?!bat$).*$
咱们来解释一下这个前向否认断言的含义:若是正则表达式 bat 在当前位置不匹配,尝试剩下的部分正则表达式;若是 bat匹配成功,整个正则表达式将会失败(由于是前向否认断言嘛^_^)。(?!bat$) 末尾的 $ 是为了确保能够正常匹配像sample.batch 这种以 bat 开始的扩展名。
一样,有了前向否认断言,要同时排除 bat 和 exe 扩展名,也变得至关容易:
.*[.](?!bat$|exe$).*$
io