前向否认界定符 python正则表达式不匹配某个字符串 以及无捕获组和命名组(转)

[编辑] 无捕获组和命名组php

精心设计的 REs 也许会用不少组,既能够捕获感兴趣的子串,又能够分组和结构化 RE 自己。在复杂的 REs 里,追踪组号变得困难。有两个功能能够对这个问题有所帮助。它们也都使用正则表达式扩展的通用语法,所以咱们来看看第一个。python


Perl 5 对标准正则表达式增长了几个附加功能,Python 的 re 模块也支持其中的大部分。选择一个新的单按键元字符或一个以 "\" 开始的特殊序列来表示新的功能,而又不会使 Perl 正则表达式与标准正则表达式产生混乱是有难度的。若是你选择 "&" 作为新的元字符,举个例子,老的表达式认为 "&" 是一个正常的字符,而不会在使用 \& 或 [&] 时也不会转义。正则表达式


Perl 开发人员的解决方法是使用 (?...) 来作为扩展语法。"?" 在括号後面会直接致使一个语法错误,由于 "?" 没有任何字符能够重复,所以它不会产生任何兼容问题。紧随 "?" 之後的字符指出扩展的用途,所以 (?=foo)spring


Python 新增了一个扩展语法到 Perl 扩展语法中。若是在问号後的第一个字符是 "P",你就能够知道它是针对 Python 的扩展。目前有两个这样的扩展: (?P<name>...) 定义一个命名组,(?P=name) 则是对命名组的逆向引用。若是 Perl 5 的将来版本使用不一样的语法增长了相同的功能,那幺 re 模块也将改变以支持新的语法,这是为了兼容性的目的而保持的 Python 专用语法。ubuntu


如今咱们看一下普通的扩展语法,咱们回过头来简化在复杂 REs 中使用组运行的特性。由于组是从左到右编号的,并且一个复杂的表达式也许会使用许多组,它可使跟踪当前组号变得困难,而修改如此复杂的 RE 是十分麻烦的。在开始时插入一个新组,你能够改变它之後的每一个组号。设计


首先,有时你想用一个组去收集正则表达式的一部分,但又对组的内容不感兴趣。你能够用一个无捕获组: (?:...) 来实现这项功能,这样你能够在括号中发送任何其余正则表达式。开发

#!python
>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")
>>> m.groups()
()字符串

除了捕获匹配组的内容以外,无捕获组与捕获组表现彻底同样;你能够在其中放置任何字符,能够用重复元字符如 "*" 来重复它,能够在其余组(无捕获组与捕获组)中嵌套它。(?:...) 对于修改已有组尤为有用,由于你能够不用改变全部其余组号的状况下添加一个新组。捕获组和无捕获组在搜索效率方面也没什么不一样,没有哪个比另外一个更快。get


其次,更重要和强大的是命名组;与用数字指定组不一样的是,它能够用名字来指定。it


命令组的语法是 Python 专用扩展之一: (?P<name>...)。名字很明显是组的名字。除了该组有个名字以外,命名组也同捕获组是相同的。`MatchObject` 的方法处理捕获组时接受的要么是表示组号的整数,要么是包含组名的字符串。命名组也能够是数字,因此你能够经过两种方式来获得一个组的信息:

#!python
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'

命名组是便于使用的,由于它可让你使用容易记住的名字来代替不得不记住的数字。这里有一个来自 imaplib 模块的 RE 示例:

#!python
InternalDate = re.compile(r'INTERNALDATE "'
r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
r'(?P<year>[0-9][0-9][0-9][0-9])'
r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
r'"')

很明显,获得 m.group('zonem') 要比记住获得组 9 要容易得多。


由于逆向引用的语法,象 (...)\1 这样的表达式所表示的是组号,这时用组名代替组号天然会有差异。还有一个 Python 扩展:(?P=name) ,它可使叫 name 的组内容再次在当前位置发现。正则表达式为了找到重复的单词,(\b\w+)\s+\1 也能够被写成 (?P<word>\b\w+)\s+(?P=word):

#!python
>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
>>> p.search('Paris in the the spring').group()
'the the'[编辑] 前向界定符

另外一个零宽界定符(zero-width assertion)是前向界定符。前向界定符包括前向确定界定符和後向确定界定符,所下所示:

(?=...)

前向确定界定符。若是所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,不然失败。但一旦所含表达式已经尝试,匹配引擎根本没有提升;模式的剩馀部分还要尝试界定符的右边。

(?!...)

前向否认界定符。与确定界定符相反;当所含表达式不能在字符串当前位置匹配时成功


经过示范在哪前向能够成功有助于具体实现。考虑一个简单的模式用于匹配一个文件名,并将其经过 "." 分红基本名和扩展名两部分。如在 "news.rc" 中,"news" 是基本名,"rc" 是文件的扩展名。


匹配模式很是简单:

.*[.].*$

注意 "." 须要特殊对待,由于它是一个元字符;我把它放在一个字符类中。另外注意後面的 $; 添加这个是为了确保字符串全部的剩馀部分必须被包含在扩展名中。这个正则表达式匹配 "foo.bar"、"autoexec.bat"、 "sendmail.cf" 和 "printers.conf"。


如今,考虑把问题变得复杂点;若是你想匹配的扩展名不是 "bat" 的文件名?一些不正确的尝试:

.*[.][^b].*$

上面的第一次去除 "bat" 的尝试是要求扩展名的第一个字符不是 "b"。这是错误的,由于该模式也不能匹配 "foo.bar"。

.*[.]([<sup>b]..|.[^a].|..[</sup>t])$

当你试着修补第一个解决方法而要求匹配下列状况之一时表达式更乱了:扩展名的第一个字符不是 "b"; 第二个字符不是 "a";或第三个字符不是 "t"。这样能够接受 "foo.bar" 而拒绝 "autoexec.bat",但这要求只能是三个字符的扩展名而不接受两个字符的扩展名如 "sendmail.cf"。咱们将在努力修补它时再次把该模式变得复杂。

.*[.]([<sup>b].?.?|.[^a]?.?|..?[</sup>t]?)$

在第三次尝试中,第二和第三个字母都变成可选,为的是容许匹配比三个字符更短的扩展名,如 "sendmail.cf"。


该模式如今变得很是复杂,这使它很难读懂。更糟的是,若是问题变化了,你想扩展名不是 "bat" 和 "exe",该模式甚至会变得更复杂和混乱。


前向否认把全部这些裁剪成:

.*[.](?!bat$).*$

前向的意思:若是表达式 bat 在这里没有匹配,尝试模式的其馀部分;若是 bat$ 匹配,整个模式将失败。後面的 $ 被要求是为了确保象 "sample.batch" 这样扩展名以 "bat" 开头的会被容许。


将另外一个文件扩展名排除在外如今也容易;简单地将其作为可选项放在界定符中。下面的这个模式将以 "bat" 或 "exe" 结尾的文件名排除在外。

.*[.](?!bat$|exe$).*$