本文译自官方文档:
Regular Expression HOWTO
参考文章:
Python——正则表达式(1)
Python——正则表达式(2)
全文下载:
Python正则表达式基础
======================================================================================
4.更强大的功能
到目前为止,我们已经学习了正则表达式的一部分内容。在这一节,我们将介绍一些新的元字符,介绍如何使用组(groups)来检索被匹配的部分文本。
-----------------------------------------------------------------------------------------------------------------------------------------------------
4.1.更多的元字符
一些元字符被称为“零宽断言”,它们不匹配任何字符,只是简单地表示成功或失败。例如,\b表示当前位置位于一个单词的边界,但 \b 并不能改变位置。
这意味着零宽断言不能被重复使用,因为如果它们在一个给定的位置匹配一次,它们显然能够匹配无限次。
I
或操作符,如果A和B都是正则表达式,A|B将匹配任何能够匹配A或者B的字符串。为了让或操作符 | 在多字符的字符串匹配中工作地更加合理,
它的优先级很低,例如 Crow | Servo 将会匹配Crow或者Servo,而不是Cro 然后[w] or [S],再跟着ervo。
如果需要匹配字符‘|’,使用 \|,或者将它放在一个字符类中,[|]。
^
匹配一行的开头位置。如果没有设置MULTILINE标志,它只匹配字符串的开头位置。
如果设置了MULTILINE标志,它将匹配字符串中每一行(根据换行符)的开头。
举个例子,如果你只想在一行的开头位置匹配From这个单词,这个正则表达式应该是^From。
>>> print(re.search('^From','From Here to Eternity')) <_sre.SRE_Match object; span=(0, 4), match='From'> >>> print(re.search('^From','Reciting From Memory')) None
$
匹配一行的结尾,它每当遇到换行符也会进行匹配。看下面例子:
>>> print(re.search('}$','{block}')) <_sre.SRE_Match object; span=(6, 7), match='}'> >>> print(re.search('}$','{block} ')) None >>> print(re.search('}$','{block}\n')) <_sre.SRE_Match object; span=(6, 7), match='}'>如果要匹配字符‘$’,需要使用 \$,或者将它放进一个字符类中 [$]。
下述例子只有当class作为单独的一个完整单词的时候才会被匹配,当class是其他单词的一部分时不会被匹配。
>>> p = re.compile(r'\bclass\b') >>> print(p.search('no class at all')) <_sre.SRE_Match object; span=(3, 8), match='class '> >>> print(p.search('the declassified algorithm')) None >>> print(p.search('one subclass is')) None >>> print(p.search('test:#class$')) <_sre.SRE_Match object; span=(6, 11), match='class'>使用这个特殊的字符,有两点需要注意。
第一,Python字符串跟正则表达式有些字符存在冲突。在Python字符串中,\b 表示退格符(ASCII码值是8),如果你不使用原始字符串,Python会将 \b 解释为退格符,所以这个正则表达式就不会按照你所想的方式去匹配。下例使用与上例相同的正则表达式但省去字符‘r’:
>>> p = re.compile('\bclass\b') >>> print(p.search('no class at all')) None >>> print(p.search('\b'+'class'+'\b')) <_sre.SRE_Match object; span=(0, 7), match='\x08class\x08'>第二,在字符类中这个断言也没有用,\b 在字符类中就相当于Python中的退格符。
\B
这个零宽断言与\b的意义相反,它匹配非单词边界。
------------------------------------------------------------------------------------------------------------------------------------------------------
4.2.分组
实际上,除了正则表达式是否匹配外,你需要知道更多的信息。对于比较复杂的内容,正则表达式通常使用分组的方式分别对不同内容进行匹配。
比如,一个RFC-822头部的每一行使用分号‘:’使之分为名字和值:
From: [email protected] User-Agent: Thunderbird 1.5.0.9 (X11/20061227) MIME-Version: 1.0 To: [email protected]这种情况下,可以写一个正则表达式匹配整个头部,然后利用分组功能,使一个组匹配头的名字,另一个组匹配名字对应的值。
>>> p = re.compile('(ab)*') >>> print(p.match('ababababab').span()) (0, 10)使用小括号‘(’和‘)’表示的子组我们也可以对它们按照层次索引,可以将索引值按照层次传递给group()、start()、end()和span()等这些方法。序号0表示第一个分组,它总是存在的,即整个正则表达式本身,所以匹配对象的这些方法将序号0作为默认参数。
>>> p = re.compile('(a)b') >>> m = p.match('ab') >>> m.group() 'ab' >>> m.group(0) 'ab'子分组是从1开始,从左至右编号的。另外,分组也可以嵌套,因此我们可以从左到右统计左括号来统计子组的序号。
>>> p = re.compile('(a(b)c)d') >>> m = p.match('abcd') >>> m.group(0) 'abcd' >>> m.group(1) 'abc' >>> m.group(2) 'b'group()函数也允许多个参数,在这种情况下,它将返回一个元祖,包含指定分组内容:
>>> m.group(2,1,2) ('b', 'abc', 'b')groups()方法返回一个元祖,包含序号从1开始的所有子分组的内容:
>>> m.groups() ('abc', 'b')反向引用指的是你可以在后面的位置引用先前匹配过的内容,比如,\1 引用第一个分组的内容,如果当前位置的内容和分组1的内容一样,则匹配成功。要注意Python中的字符串使用反斜杠加上数字来表示字符串中的任意字符,所以在正则表达式中使用反向引用的时候记得使用原始字符串。
>>> p = re.compile(r'(\b\w+)\s+\1') >>> p.search('Paris in the the spring').group() 'the the'像这样搜索字符串的时候并不会经常用到反向引用,因为很少有文本格式会这样来重复字符。但是你很快就会发现在字符串替换的时候它们是非常有用的。
-----------------------------------------------------------------------------------------------------------------------------------------------------
4.3.非捕获组和命名组
精心设计的正则表达式可能会划分很多组,这些组不仅可以匹配相关的子字符串,还可以对正则表达式本身进行分组和结构化。在复杂的正则表达式中,我们很难去跟踪分组的序号,这里有两种方式可以帮助解决这个问题,这两种方式都用了同一种正则表达式扩展语法,所以我们先来看看这个表达式扩展语法。
众所周知,Perl5为标准的正则表达式增加了很多强大的扩展功能,对于这些扩展功能,Perl开发者并不能选择一个新的元字符或者通过反斜杠构造一个新的特殊序列来实现扩展功能,因为这会和标准正则表达式冲突。比如,如果他们选择 & 作为一个新的元字符,那么旧的表达式将会把 & 作为一个特殊的正则字符,但是却没有通过写成 \& 或者 [&] 来去掉它的特殊含义。
最后的解决方法是Perl开发者选择 (?...) 作为扩展语法,问号 ? 紧跟在小括号后面本身是一个语法错误因为前面没有字符可以重复,所以这就解决了兼容问题。紧跟在问号之后的字符指定了哪种扩展功能将被使用,例如,(?=foo) 是一种扩展功能(前向断言),(?:foo)是另一种扩展功能(一个包含子字符串 foo 的非捕获组)。
Python支持Perl的一些扩展语法,并且还增加了一个扩展语法。如果在问号 ? 之后的字符是 P 的话,可以肯定这是一个Python的扩展语法。
现在我们已经知道了扩展语法,所以让我再回过头来看看这些扩展语法在复杂的正则表达式中是如何工作的。
有时你想要用一个分组去指定正则表达式的一部分,但是并不关心分组匹配的内容。你可以通过非捕获组来实现这个功能:(?:…),这里省略号 … 可以换成任何正则表达式:
>>> m = re.match('([abc])+','abc') >>> m.groups() ('c',) >>> m = re.match('(?:[abc])+','abc') >>> m.groups() ()非捕获组除了不能获取任何内容外,在其他方面和捕获组一样,你可以在里面放任何正则表达式,使用重复功能的元字符,或者将它嵌套到其他分组(捕获的或者非捕获的)。(?:…)非捕获组在修改已存在的模式的时候是非常有用的,因为添加非捕获组并不影响其他捕获分组的序号。值得一提的是,非捕获分组和捕获分组在搜索速度上没有任何区别。
>>> p = re.compile(r'(?P<word>\b\w+\b)') >>> m = p.search('((((Lots of punctuation)))') >>> m.group('word') 'Lots' >>> m.group(1) 'Lots'
命名组通过名称访问分组可以让你不用去记住分组的序号,从而使处理变得更加简单。下面看一个imaplib模块中的正则表达式的例子:
>>> 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。
>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)') >>> p.search('Paris in the the spring').group() 'the the'
------------------------------------------------------------------------------------------------------------------------------------------------------
.*[.].*$注意点号‘.’是一个元字符,所以文件名中间的点需要将它放入一个字符类以去掉它的特殊功能。同时注意末尾的结束符$,它用来保证字符串的剩余部分都包含在扩展名内。这个正则表达式会匹配foo.bar、autoexec.bat、sendmail.cf和printers.conf。
.*[.][^b].*$这种尝试想要匹配扩展名的第一个字母不是b的文件名,从而将扩展名为bat的文件排除。但是要知道,foo.bar的扩展名bar的第一个字母也是b,这种方法会将它一并排除。
.*[.]([^b]..|.[^a].|..[^t])$扩展名的第一个字母不是b,或者扩展名的第二个字母不是a,或者扩展名的第三个字母不是t,符合这些规则的文件名将会被匹配。这样的话就会匹配foo.bar,而只将autoexec.bat排除。但是它只能匹配扩展名包含3个字母的文件名,而扩展名包含2个字母的,如sendmail.cf将会被排除。所以我们继续修复这个正则表达式:
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$在这个正则表达式中,扩展名的第二个和第三个字母都用问号 ? 变成可选的,这样就可以匹配少于3个字母的扩展名了,比如sendmail.cf。
.*[.](?!bat$).*$前向否定断言的含义是这样的:如果表达式bat在当前位置不匹配,则尝试剩余的模式;而如果bat$匹配了,则整个正则表达式将失败。(?!bat$)末尾的结束符‘$’保证可以正常匹配像sample.batch这样的扩展名是以bat开头的文件名。
所以,现在将另一个文件的扩展名排除在外就变得很简单,只要将它以或选的方式加入到断言中即可。比如,下面的正则表达式可以将扩展名为bat和exe的文件名排除:
.*[.](?!bat$|exe$).*$