
Python自1.5版本起增加了re模块,它提供Perl风格的正则表达式模式。
Python1.5之前版本则是通过regex模块提供Emecs风格的模式。Emacs风
格模式可读性稍差些,而且功能也不强,因此编写新代码时尽量不要再使用
regex模块,当然偶尔你还是可能在老代码里发现其踪影。
就其本质而言,正则表达式(或RE)是一种小型的、高度专业化的编程语言,
(在Python中)它内嵌在Python中,并通过re模块实现。使用这个小型语
言,你可以为想要匹配的相应字符串集指定规则;该字符串集可能包含英文语
句、e-mail地址、TeX命令或任何你想搞定的东西。然后你可以问诸如“这个
字符串匹配该模式吗?”或“在这个字符串中是否有部分匹配该模式呢?”。
你也可以使用RE以各种方式来修改或分割字符串。
正则表达式模式被编译成一系列的字节码,然后由用C编写的匹配引擎执行。
在高级用法中,也许还要仔细留意引擎是如何执行给定RE,如何以特定方式
编写RE以令生产的字节码运行速度更快。本文并不涉及优化,因为那要求你
已充分掌握了匹配引擎的内部机制。哈哈
正则表达式语言相对小型和受限(功能有限),因此并非所有字符串处理都能
用正则表达式完成。当然也有些任务可以用正则表达式完成,不过最终表达式
会变得异常复杂。碰到这些情形时,编写Python代码进行处理可能反而更
好;尽管Python代码比一个精巧的正则表达式要慢些,但它更易理解。
[编辑]简单模式我们将从最简单的正则表达式学习开始。由于正则表达式常用于字符串操作,
那我们就从最常见的任务:字符匹配下手。
有关正则表达式底层的计算机科学上的详细解释(确定性和非确定性有限自动
机),你可以查阅编写编译器相关的任何教科书。
字符匹配
大多数字母和字符一般都会和自身匹配。例如,正则表达式test会和字符串
“test”完全匹配。(你也可以使用大小写不敏感模式,它还能让这个RE匹
配“Test”或“TEST”;稍后会有更多解释。)
这个规则当然会有例外;有些字符比较特殊,它们和自身并不匹配,而是会表
明应和一些特殊的东西匹配,或者它们会影响到RE其它部分的重复次数。本
文很大篇幅专门讨论了各种元字符及其作用。
这里有一个元字符的完整列表;其含义会在本指南馀下部分进行讨论。
.^$*+?{[]\\|()
我们首先考察的元字符是"["和"]"。它们常用来指定一个字符类别,所谓字
符类别就是你想匹配的一个字符集。字符可以单个列出,也可以用“-”号分隔
的两个给定字符来表示一个字符区间。例如,[abc]将匹配"a
中的任意一个字符;也可以用区间[a-c]来表示同一字符集,和前者效果一致。
如果你只想匹配小写字母,那么RE应写成[a-z].
元字符在类别里并不起作用。例如,[akm$]将匹配字符"a
中的任意一个;"$"通常用作元字符,但在字符类别里,其特性被除去,恢复成
普通字符。
你可以用补集来匹配不在区间范围内的字符。其做法是把"^"作为类别的首个字
符;其它地方的"^"只会简单匹配"^"字符本身。例如,[^5]将匹配除"5"之
外的任意字符。
也许最重要的元字符是反斜杠"\\"。做为Python中的字符串字母,反斜杠后
面可以加不同的字符以表示不同特殊意义。它也可以用于取消所有的元字符,
这样你就可以在模式中匹配它们了。举个例子,如果你需要匹配字符"["或
"\\",你可以在它们之前用反斜杠来取消它们的特殊意义:\\[或\\\\。
一些用"\\"开始的特殊字符所表示的预定义字符集通常是很有用的,象数字
集,字母集,或其它非空字符集。下列是可用的预设特殊字符:
\\d匹配任何十进制数;它相当于类[0-9]。
\\D匹配任何非数字字符;它相当于类[^0-9]。
\\s匹配任何空白字符;它相当于类[\\\n\\r\\f\\v]。\\S匹配任何非空白字符;它相当于类[^\\\n\\r\\f\\v]。
\\w匹配任何字母数字字符;它相当于类[a-zA-Z0-9_]。
\\W匹配任何非字母数字字符;它相当于类[^a-zA-Z0-9_]。
这样特殊字符都可以包含在一个字符类中。如,[\\s,.]字符类将匹配任何空白
字符或
本节最后一个元字符是.。它匹配除了换行字符外的任何字符,在alternate
模式(re.DOTALL)下它甚至可以匹配换行。"."通常被用于你想匹配“任何字
符”的地方。
[编辑]重复
正则表达式第一件能做的事是能够匹配不定长的字符集,而这是其它能作用在
字符串上的方法所不能做到的。不过,如果那是正则表达式唯一的附加功能的
话,那么它们也就不那么优秀了。它们的另一个功能就是你可以指定正则表达
式的一部分的重复次数。
我们讨论的第一个重复功能的元字符是*。*并不匹配字母字符"*";相反,
它指定前一个字符可以被匹配零次或更多次,而不是只有一次。
举个例子,ca*t将匹配"ct"(0个"a"字符),"cat"(1个"a"),"caaat"
(3个"a"字符)等等。RE引擎有各种来自C的整数类型大小的内部,以
防止它匹配超过2亿个"a"字符;你也许没有足够的内存去建造那么大的字符
串,所以将不会累计到那个。
象*这样地重复是“贪婪的”;当重复一个RE时,匹配引擎会试着重复尽可
能多的次数。如果模式的后面部分没有被匹配,匹配引擎将退回并再次尝试更
小的重复。
一步步的示例可以使它更加清晰。让我们考虑表达式a[bcd]*b。它匹配字母
"a",零个或更多个来自类[bcd]中的字母,最后以"b"结尾。现在想一想该
RE对字符串"abcbd"的匹配。
Step Matched Explanation
1a a匹配模式
2abcbd引擎匹配[bcd]*,并尽其所能匹配到字符串的结尾
3Failure引擎尝试匹配b,但当前位置已经是字符的最后了,所以失败
4abcb退回,[bcd]*尝试少匹配一个字符。
5Failure再次尝次b,但在当前最后一位字符是"d"。
6abc再次退回,[bcd]*只匹配"bc"。7abcb再次尝试b,这次当前位上的字符正好是"b"
RE的结尾部分现在可以到达了,它匹配"abcb"。这证明了匹配引擎一开始会
尽其所能进行匹配,如果没有匹配然后就逐步退回并反复尝试RE剩下来的部
分。直到它退回尝试匹配[bcd]到零次为止,如果随后还是失败,那么引擎就
会认为该字符串根本无法匹配RE。
另一个重复元字符是+,表示匹配一或更多次。请注意*和+之间的不同;
*匹配零或更多次,所以根本就可以不出现,而+则要求至少出现一次。用
同一个例子,ca+t就可以匹配"cat"(1个"a"),"caaat"(3个"a"),
但不能匹配"ct"。
还有更多的限定符。问号?匹配一次或零次;你可以认为它用于标识某事物是
可选的。例如:home-?brew匹配"homebrew"或"home-brew"。
最复杂的重复限定符是{m,n},其中m和n是十进制整数。该限定符的意思
是至少有m个重复,至多到n个重复。举个例子,a/{1,3}b将匹配"a/b",
"a//b"和"a///b"。它不能匹配"ab"因为没有斜杠,也不能匹配
"a////b",因为有四个。
你可以忽略m或n;因为会为缺失的值假设一个合理的值。忽略m会认为下
边界是0,而忽略n的结果将是上边界为无穷大--实际上是先前我们提到的
2兆,但这也许同无穷大一样。
细心的读者也许注意到其他三个限定符都可以用这样方式来表示。{0,}等同
于*,{1,}等同于+,而{0,1}则与?相同。如果可以的话,最好使用*,
+,或?。很简单因为它们更短也再容易懂。
[编辑]使用正则表达式
现在我们已经看了一些简单的正则表达式,那么我们实际在Python中是如何
使用它们的呢?re模块提供了一个正则表达式引擎的接口,可以让你将REs
编译成对象并用它们来进行匹配。
[编辑]编译正则表达式
正则表达式被编译成`RegexObject`实例,可以为不同的操作提供方法,如模
式匹配搜索或字符串替换。
#!python>>>import re
>>>p=re.compile('ab*')
>>>print p
re.compile()也接受可选的标志参数,常用来实现不同的特殊功能和语法变 更。我们稍后将查看所有可用的设置,但现在只举一个例子: #!python >>>p=re.compile('ab*',re.IGNORECASE) RE被做为一个字符串发送给re.compile()。REs被处理成字符串是因为正则表达式不是Python语言的核心部分,也没有为它创建特定的语法。(应用程序根本就不需要REs,因此没必要包含它们去使语言说明变得臃肿不堪。)而re模块则只是以一个C扩展模块的形式来被Python包含,就象socket或zlib模块一样。 将REs作为字符串以保证Python语言的简洁,但这样带来的一个麻烦就是象下节标题所讲的。 [编辑]反斜杠的麻烦 在早期规定中,正则表达式用反斜杠字符("\\")来表示特殊格式或允许使用特殊字符而不调用它的特殊用法。这就与Python在字符串中的那些起相同作用的相同字符产生了冲突。 让我们举例说明,你想写一个RE以匹配字符串"\\section",可能是在一个LATEX文件查找。为了要在程序代码中判断,首先要写出想要匹配的字符串。接下来你需要在所有反斜杠和元字符前加反斜杠来取消其特殊意义。 字符阶段 \\section要匹配的字符串 \\\\section为re.compile取消反斜杠的特殊意义 "\\\\\\\\section"为字符串取消反斜杠 简单地说,为了匹配一个反斜杠,不得不在RE字符串中写'\\\\\\\\',因为正则表达式中必须是"\\\\",而每个反斜杠按Python字符串字母表示的常规必须表示成"\\\\"。在REs中反斜杠的这个重复特性会导致大量重复的反斜杠,而且所生成的字符串也很难懂。 解决的办法就是为正则表达式使用Python的raw字符串表示;在字符串前加个"r"反斜杠就不会被任何特殊方式处理,所以r"\\n"就是包含"\\"和"n" 的两个字符,而"\\n"则是一个字符,表示一个换行。正则表达式通常在Python代码中都是用这种raw字符串表示。 常规字符串Raw字符串 "ab*"r"ab*" "\\\\\\\\section"r"\\\\section" "\\\\w+\\\\s+\\\\1"r"\\w+\\s+\\1" 执行匹配 一旦你有了已经编译了的正则表达式的对象,你要用它做什么呢? `RegexObject`实例有一些方法和属性。这里只显示了最重要的几个,如果要看完整的列表请查阅Python Library Reference 方法/属性作用 match()决定RE是否在字符串刚开始的位置匹配 search()扫描字符串,找到这个RE匹配的位置 findall()找到RE匹配的所有子串,并把它们作为一个列表返回 finditer()找到RE匹配的所有子串,并把它们作为一个迭代器返回 如果没有匹配到的话,match()和search()将返回None。如果成功的话,就会返回一个`MatchObject`实例,其中有这次匹配的信息:它是从哪里开始和结束,它所匹配的子串等等。 你可以用采用人机对话并用re模块实验的方式来学习它。如果你有Tkinter 的话,你也许可以考虑参考一下Tools/scripts/redemo.py,一个包含在Python发行版里的示范程序。 首先,运行Python解释器,导入re模块并编译一个RE: #!python Python2.2.2(#1,Feb102003,12:57:01) >>>import re >>>p=re.compile('[a-z]+') >>>p<_sre.SRE_Pattern object at80c3c28> 现在,你可以试着用RE的[a-z]+去匹配不同的字符串。一个空字符串将根本不能匹配,因为+的意思是“一个或更多的重复次数”。在这种情况下match()将返回None,因为它使解释器没有输出。你可以明确地打印出match()的结果来弄清这一点。 #!python >>>p.match("") >>>print p.match("") None 现在,让我们试着用它来匹配一个字符串,如"tempo"。这时,match()将返回一个MatchObject。因此你可以将结果保存在变量里以便后面使用。 #!python >>>m=p.match('tempo') >>>print m <_sre.SRE_Match object at80c4f68> 现在你可以查询`MatchObject`关于匹配字符串的相关信息了。MatchObject 实例也有几个方法和属性;最重要的那些如下所示: 方法/属性作用 group()返回被RE匹配的字符串 start()返回匹配开始的位置 end()返回匹配结束的位置 span()返回一个元组包含匹配(开始,结束)的位置 试试这些方法不久就会清楚它们的作用了: #!python >>>m.group() 'tempo' >>>m.start(),m.end() (0,5) >>>m.span() (0,5) group()返回RE匹配的子串。start()和end()返回匹配开始和结束时的索引。span()则用单个元组把开始和结束时的索引一起返回。因为匹配方法检查到如果RE在字符串开始处开始匹配,那么start()将总是为零。然而,`RegexObject`实例的search方法扫描下面的字符串的话,在这种情况下, 匹配开始的位置就也许不是零了。 #!python >>>print p.match(':::message') None >>>m=p.search(':::message');print m >>>m.group() 'message' >>>m.span() (4,11) 在实际程序中,最常见的作法是将`MatchObject`保存在一个变量里,然后检 查它是否为None,通常如下所示: #!python p=re.compile(...) m=p.match('string goes here') if m: print'Match found:',m.group() else: print'No match' 两个`RegexObject`方法返回所有匹配模式的子串。findall()返回一个匹配 字符串行表: #!python >>>p=re.compile('\\d+') >>>p.findall('12drummers drumming,11pipers piping,10lords a- leaping') ['12','11','10'] findall()在它返回结果时不得不创建一个列表。在Python2.2中,也可以 用finditer()方法。 #!python >>>iterator=p.finditer('12drummers drumming,11...10...') >>>iterator >>>for match in iterator: ...print match.span() ... (0,2) (22,24)(29,31) [编辑]模块级函数 你不一定要产生一个`RegexObject`对象然后再调用它的方法;re模块也提 供了顶级函数调用如match()、search()、sub()等等。这些函数使用RE字 符串作为第一个参数,而后面的参数则与相应`RegexObject`的方法参数相 同,返回则要么是None要么就是一个`MatchObject`的实例。 #!python >>>print re.match(r'From\\s+','Fromage amk') None >>>re.match(r'From\\s+','From amk Thu May1419:12:101998') Under the hood,这些函数简单地产生一个RegexOject并在其上调用相应的 方法。它们也在缓存里保存编译后的对象,因此在将来调用用到相同RE时就会更快。 你将使用这些模块级函数,还是先得到一个`RegexObject`再调用它的方法呢?如何选择依赖于怎样用RE更有效率以及你个人编码风格。如果一个RE 在代码中只做用一次的话,那么模块级函数也许更方便。如果程序包含很多的正则表达式,或在多处复用同一个的话,那么将全部定义放在一起,在一段代码中提前编译所有的REs更有用。从标准库中看一个例子,这是从xmllib.py 文件中提取出来的: #!python ref=re.compile(...) entityref=re.compile(...) charref=re.compile(...) starttagopen=re.compile(...) 我通常更喜欢使用编译对象,甚至它只用一次,but few people will be as much of a purist about this as I am。 [编辑]编译标志 编译标志让你可以修改正则表达式的一些运行方式。在re模块中标志可以使用两个名字,一个是全名如IGNORECASE,一个是缩写,一字母形式如I。(如果你熟悉Perl的模式修改,一字母形式使用同样的字母;例如re.VERBOSE 的缩写形式是re.X。)多个标志可以通过按位OR-ing它们来指定。如re.I |re.M被设置成I和M标志: 这有个可用标志表,对每个标志后面都有详细的说明。 标志含义 DOTALL,S使.匹配包括换行在内的所有字符 IGNORECASE,I使匹配对大小写不敏感 LOCALE,L做本地化识别(locale-aware)匹配 MULTILINE,M多行匹配,影响^和$ VERBOSE,X能够使用REs的verbose状态,使之被组织得更清晰易懂 I IGNORECASE 使匹配对大小写不敏感;字符类和字符串匹配字母时忽略大小写。举个例子,[A-Z]也可以匹配小写字母,Spam可以匹配"Spam L LOCALE 影响\\w,\\W,\\b,和\\B,这取决于当前的本地化设置。 locales是C语言库中的一项功能,是用来为需要考虑不同语言的编程提供帮助的。举个例子,如果你正在处理法文文本,你想用\\w+来匹配文字,但\\w 只匹配字符类[A-Za-z];它并不能匹配"é"或"ç"。如果你的系统配置适当 且本地化设置为法语,那么内部的C函数将告诉程序"é"也应该被认为是一 个字母。当在编译正则表达式时使用LOCALE标志会得到用这些C函数来处理 \\w后的编译对象;这会更慢,但也会象你希望的那样可以用\\w+来匹配法文 文本。 M MULTILINE (此时^和$不会被解释;它们将在4.1节被介绍.) 使用"^"只匹配字符串的开始,而$则只匹配字符串的结尾和直接在换行前 (如果有的话)的字符串结尾。当本标志指定后,"^"匹配字符串的开始和字 符串中每行的开始。同样的,$元字符匹配字符串结尾和字符串中每行的结尾 (直接在每个换行之前)。 S DOTALL使"."特殊字符完全匹配任何字符,包括换行;没有这个标志,"."匹配除了换行外的任何字符。 X VERBOSE 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。当该标 志被指定时,在RE字符串中的空白符被忽略,除非该空白符在字符类中或在 反斜杠之后;这可以让你更清晰地组织和缩进RE。它也可以允许你将注释写入RE,这些注释会被引擎忽略;注释用"#"号来标识,不过该符号不能在字符串 或反斜杠之后。 举个例子,这里有一个使用re.VERBOSE的RE;看看读它轻松了多少? #!python charref=re.compile(r""" &[[]]#Start of a numeric entity reference ( [0-9]+[^0-9]#Decimal form |0[0-7]+[^0-7]#Octal form |x[0-9a-fA-F]+[^0-9a-fA-F]#Hexadecimal form ) "" 没有verbose设置,RE会看起来象这样: #!python charref=re.compile("([0-9]+[^0-9]" "|0[0-7]+[^0-7]" "|x[0-9a-fA-F]+[^0-9a-fA-F])") 在上面的例子里,Python的字符串自动连接可以用来将RE分成更小的部分, 但它比用re.VERBOSE标志时更难懂。 [编辑]更多模式功能 到目前为止,我们只展示了正则表达式的一部分功能。在本节,我们将展示一 些新的元字符和如何使用组来检索被匹配的文本部分。 [编辑]更多的元字符还有一些我们还没展示的元字符,其中的大部分将在本节展示。 剩下来要讨论的一部分元字符是零宽界定符(zero-width assertions)。它们 并不会使引擎在处理字符串时更快;相反,它们根本就没有对应任何字符,只是 简单的成功或失败。举个例子,\\b是一个在单词边界定位当前位置的界定符(assertions),这个位置根本就不会被\\b改变。这意味着零宽界定符 (zero-width assertions)将永远不会被重复,因为如果它们在给定位置匹配 一次,那么它们很明显可以被匹配无数次。 | 可选项,或者"or"操作符。如果A和B是正则表达式,A|B将匹配任何匹 配了"A"或"B"的字符串。|的优先级非常低,是为了当你有多字符串要选 择时能适当地运行。Crow|Servo将匹配"Crow"或"Servo 一个"w"或一个"S 为了匹配字母"|",可以用\\|,或将其包含在字符类中,如[|]。 ^ 匹配行首。除非设置MULTILINE标志,它只是匹配字符串的开始。在 MULTILINE模式里,它也可以直接匹配字符串中的每个换行。 例如,如果你只希望匹配在行首单词"From",那么RE将用^From。 #!python >>>print re.search('^From','From Here to Eternity') >>>print re.search('^From','Reciting From Memory') None $ 匹配行尾,行尾被定义为要么是字符串尾,要么是一个换行字符后面的任何位 置。 #!python >>>print re.search('}$','{block}') None >>>print re.search('}$','{block}\\n') 匹配一个"$",使用\\$或将其包含在字符类中,如[$]。 \\A只匹配字符串首。当不在MULTILINE模式,\\A和^实际上是一样的。然而,在MULTILINE模式里它们是不同的;\\A只是匹配字符串首,而^还可以匹配在换行符之后字符串的任何位置。 \\Z Matches only at the end of the string. 只匹配字符串尾。 \\b 单词边界。这是个零宽界定符(zero-width assertions)只用以匹配单词的词 首和词尾。单词被定义为一个字母数字序列,因此词尾就是用空白符或非字母数字符来标示的。 下面的例子只匹配"class"整个单词;而当它被包含在其他单词中时不匹配。#!python >>>p=re.compile(r'\\bclass\\b') >>>print p.search('no class at all') >>>print p.search('the declassified algorithm') None >>>print p.search('one subclass is') None 当用这个特殊序列时你应该记住这里有两个微妙之处。第一个是Python字符串和正则表达式之间最糟的冲突。在Python字符串里,"\\b"是反斜杠字符,ASCII值是8。如果你没有使用raw字符串时,那么Python将会把"\\b"转换成一个回退符,你的RE将无法象你希望的那样匹配它了。下面的例子看起来和我们前面的RE一样,但在RE字符串前少了一个"r"。 #!python >>>p=re.compile('\\bclass\\b')>>>print p.search('no class at all') None >>>print p.search('\\b'+'class'+'\\b') 第二个在字符类中,这个限定符(assertion)不起作用,\\b表示回退符,以 便与Python字符串兼容。 \\B 另一个零宽界定符(zero-width assertions),它正好同\\b相反,只在当前 位置不在单词边界时匹配。 [编辑]分组 你经常需要得到比RE是否匹配还要多的信息。正则表达式常常用来分析字符串,编写一个RE匹配感兴趣的部分并将其分成几个小组。举个例子,一个RFC-822的头部用":"隔成一个头部名和一个值,这就可以通过编写一个正则表达式匹配整个头部,用一组匹配头部名,另一组匹配头部值的方式来处理。 组是通过"("和")"元字符来标识的。"("和")"有很多在数学表达式中 相同的意思;它们一起把在它们里面的表达式组成一组。举个例子,你可以用重复符,象*,+,?,和{m,n},来重复组里的内容,比如说(ab)*将匹 配零或更多个重复的"ab"。 #!python >>>p=re.compile('(ab)*') >>>print p.match('ababababab').span() (0,10) 组用"("和")"来指定,并且得到它们匹配文本的开始和结尾索引;这就可 以通过一个参数用group()、start()、end()和span()来进行检索。组是从 0开始计数的。组0总是存在;它就是整个RE,所以`MatchObject`的方法 都把组0作为它们缺省的参数。稍后我们将看到怎样表达不能得到它们所匹配 文本的span。 #!python >>>p=re.compile('(a)b') >>>m=p.match('ab') >>>m.group() 'ab' >>>m.group(0) 'ab'小组是从左向右计数的,从1开始。组可以被嵌套。计数的数值可以能过从左到右计算打开的括号数来确定。 #!python >>>p=re.compile('(a(b)c)d') >>>m=p.match('abcd') >>>m.group(0) 'abcd' >>>m.group(1) 'abc' >>>m.group(2) 'b' group()可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对 应值的元组。 #!python >>>m.group(2,1,2) ('b','abc','b') The groups()方法返回一个包含所有小组字符串的元组,从1到所含的小组 号。 #!python >>>m.groups() ('abc','b') 模式中的逆向引用允许你指定先前捕获组的内容,该组也必须在字符串当前位 置被找到。举个例子,如果组1的内容能够在当前位置找到的话,\\1就成功 否则失败。记住Python字符串也是用反斜杠加数据来允许字符串中包含任意 字符的,所以当在RE中使用逆向引用时确保使用raw字符串。 例如,下面的RE在一个字符串中找到成双的词。 #!python >>>p=re.compile(r'(\\b\\w+)\\s+\\1') >>>p.search('Paris in the the spring').group() 'the the' 象这样只是搜索一个字符串的逆向引用并不常见--用这种方式重复数据的文 本格式并不多见--但你不久就可以发现它们用在字符串替换上非常有用。 [编辑]无捕获组和命名组精心设计的REs也许会用很多组,既可以捕获感兴趣的子串,又可以分组和结 构化RE本身。在复杂的REs里,追踪组号变得困难。有两个功能可以对这个 问题有所帮助。它们也都使用正则表达式扩展的通用语法,因此我们来看看第 一个。 Perl5对标准正则表达式增加了几个附加功能,Python的re模块也支持其 中的大部分。选择一个新的单按键元字符或一个以"\\"开始的特殊序列来表示 新的功能,而又不会使Perl正则表达式与标准正则表达式产生混乱是有难度 的。如果你选择"&"做为新的元字符,举个例子,老的表达式认为"&"是一 个正常的字符,而不会在使用\\&或[&]时也不会转义。 Perl开发人员的解决方法是使用(?...)来做为扩展语法。"?"在括号后面会 直接导致一个语法错误,因为"?"没有任何字符可以重复,因此它不会产生任 何兼容问题。紧随"?"之后的字符指出扩展的用途,因此(?=foo) Python新增了一个扩展语法到Perl扩展语法中。如果在问号后的第一个字符 是"P",你就可以知道它是针对Python的扩展。目前有两个这样的扩展: (?P Perl5的未来版本使用不同的语法增加了相同的功能,那么re模块也将改变 以支持新的语法,这是为了兼容性的目的而保持的Python专用语法。 现在我们看一下普通的扩展语法,我们回过头来简化在复杂REs中使用组运行 的特性。因为组是从左到右编号的,而且一个复杂的表达式也许会使用许多 组,它可以使跟踪当前组号变得困难,而修改如此复杂的RE是十分麻烦的。 在开始时插入一个新组,你可以改变它之后的每个组号。 首先,有时你想用一个组去收集正则表达式的一部分,但又对组的内容不感兴 趣。你可以用一个无捕获组:(?:...)来实现这项功能,这样你可以在括号中 发送任何其他正则表达式。 #!python >>>m=re.match("([abc])+ >>>m.groups() ('c',) >>>m=re.match("(?:[abc])+ >>>m.groups() () 除了捕获匹配组的内容之外,无捕获组与捕获组表现完全一样;你可以在其中 放置任何字符,可以用重复元字符如"*"来重复它,可以在其他组(无捕获组与捕获组)中嵌套它。(?:...)对于修改已有组尤其有用,因为你可以不用改 变所有其他组号的情况下添加一个新组。捕获组和无捕获组在搜索效率方面也 没什么不同,没有哪一个比另一个更快。 其次,更重要和强大的是命名组;与用数字指定组不同的是,它可以用名字来 指定。 命令组的语法是Python专用扩展之一:(?P 名字。除了该组有个名字之外,命名组也同捕获组是相同的。`MatchObject` 的方法处理捕获组时接受的要么是表示组号的整数,要么是包含组名的字符 串。命名组也可以是数字,所以你可以通过两种方式来得到一个组的信息: #!python >>>p=re.compile(r'(?P >>>m=p.search('((((Lots of punctuation)))') >>>m.group('word') 'Lots' >>>m.group(1) 'Lots' 命名组是便于使用的,因为它可以让你使用容易记住的名字来代替不得不记住 的数字。这里有一个来自imaplib模块的RE示例: #!python InternalDate=re.compile(r'INTERNALDATE"' r'(?P r'(?P r'(?P r'(?P r'"') 很明显,得到m.group('zonem')要比记住得到组9要容易得多。 因为逆向引用的语法,象(...)\\1这样的表达式所表示的是组号,这时用组名 代替组号自然会有差别。还有一个Python扩展:(?P=name),它可以使叫 name的组内容再次在当前位置发现。正则表达式为了找到重复的单词, (\\b\\w+)\\s+\\1也可以被写成(?P #!python >>>p=re.compile(r'(?P >>>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"。 .*[.]([^b]..|.[^a].|..[^t])$ 当你试着修补第一个解决方法而要求匹配下列情况之一时表达式更乱了:扩展 名的第一个字符不是"b";第二个字符不是"a";或第三个字符不是"t"。这 样可以接受"foo.bar"而拒绝"autoexec.bat",但这要求只能是三个字符的扩展名而不接受两个字符的扩展名如"sendmail.cf"。我们将在努力修补它时 再次把该模式变得复杂。 .*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$ 在第三次尝试中,第二和第三个字母都变成可选,为的是允许匹配比三个字符 更短的扩展名,如"sendmail.cf"。 该模式现在变得非常复杂,这使它很难读懂。更糟的是,如果问题变化了,你 想扩展名不是"bat"和"exe",该模式甚至会变得更复杂和混乱。 前向否定把所有这些裁剪成: .*[.](?!bat$).*$ 前向的意思:如果表达式bat在这里没有匹配,尝试模式的其余部分;如果 bat$匹配,整个模式将失败。后面的$被要求是为了确保象"sample.batch" 这样扩展名以"bat"开头的会被允许。 将另一个文件扩展名排除在外现在也容易;简单地将其做为可选项放在界定符 中。下面的这个模式将以"bat"或"exe"结尾的文件名排除在外。 .*[.](?!bat$|exe$).*$ [编辑]修改字符串 到目前为止,我们简单地搜索了一个静态字符串。正则表达式通常也用不同的方式,通过下面的`RegexObject`方法,来修改字符串。 方法/属性作用 split()将字符串在RE匹配的地方分片并生成一个列表, sub()找到RE匹配的所有子串,并将其用一个不同的字符串替换 subn()与sub()相同,但返回新的字符串和替换次数 [编辑]将字符串分片 `RegexObject`的split()方法在RE匹配的地方将字符串分片,将返回列 表。它同字符串的split()方法相似但提供更多的定界符;split()只支持空 白符和固定字符串。就象你预料的那样,也有一个模块级的re.split()函 数。 split(string[,maxsplit=0])通过正则表达式将字符串分片。如果捕获括号在RE中使用,那么它们的内容 也会作为结果列表的一部分返回。如果maxsplit非零,那么最多只能分出 maxsplit个分片。 你可以通过设置maxsplit值来分片数。当maxsplit非零时,最多只能 有maxsplit个分片,字符串的其余部分被做为列表的最后部分返回。在下面 的例子中,定界符可以是非数字字母字符的任意序列。 #!python >>>p=re.compile(r'\\W+') >>>p.split('This is a test,short and sweet,of split().') ['This','is','a','test','short','and','sweet','of','split', ''] >>>p.split('This is a test,short and sweet,of split().',3) ['This','is','a','test,short and sweet,of split().'] 有时,你不仅对定界符之间的文本感兴趣,也需要知道定界符是什么。如果捕 获括号在RE中使用,那么它们的值也会当作列表的一部分返回。比较下面的 调用: #!python >>>p=re.compile(r'\\W+') >>>p2=re.compile(r'(\\W+)') >>>p.split('This...is a test.') ['This','is','a','test',''] >>>p2.split('This...is a test.') ['This','...','is','','a','','test','.',''] 模块级函数re.split()将RE作为第一个参数,其他一样。 #!python >>>re.split('[\\W]+','Words,words,words.') ['Words','words','words',''] >>>re.split('([\\W]+)','Words,words,words.') ['Words',',','words',',','words','.',''] >>>re.split('[\\W]+','Words,words,words.',1) ['Words','words,words.'] [编辑]搜索和替换其他常见的用途就是找到所有模式匹配的字符串并用不同的字符串来替换它们。sub()方法提供一个替换值,可以是字符串或一个函数,和一个要被处理的字符串。sub(replacement,string[,count=0]) 返回的字符串是在字符串中用RE最左边不重复的匹配来替换。如果模式没有发现,字符将被没有改变地返回。 可选参数count是模式匹配后替换的最大次数;count必须是非负整数。缺省值是0表示替换所有的匹配。 这里有个使用sub()方法的简单例子。它用单词"colour"替换颜色名。 #!python >>>p=re.compile('(blue|white|red)') >>>p.sub('colour','blue socks and red shoes') 'colour socks and colour shoes' >>>p.sub('colour','blue socks and red shoes',count=1) 'colour socks and red shoes' subn()方法作用一样,但返回的是包含新字符串和替换执行次数的两元组。#!python >>>p=re.compile('(blue|white|red)') >>>p.subn('colour','blue socks and red shoes') ('colour socks and colour shoes',2) >>>p.subn('colour','no colours at all') ('no colours at all',0) 空匹配只有在它们没有紧挨着前一个匹配时才会被替换掉。 #!python >>>p=re.compile('x*') >>>p.sub('-','abxd') '-a-b-d-' 如果替换的是一个字符串,任何在其中的反斜杠都会被处理。"\\n"将会被转换成一个换行符,"\\r"转换成回车等等。未知的转义如"\\j"则保持原样。逆向 引用,如"\\6",被RE中相应的组匹配而被子串替换。这使你可以在替换后的字符串中插入原始文本的一部分。 这个例子匹配被"{"和 #!python>>>p=re.compile('section{([^}]*)}',re.VERBOSE) >>>p.sub(r'subsection{\\1}','section{First}section{second}') 'subsection{First}subsection{second}' 还可以指定用(?P 对组20的引用,而不是对后面跟着一个字母"0"的组2的引用。) #!python >>>p=re.compile('section{(?P 'subsection{First}' >>>p.sub(r'subsection{\\g<1>}','section{First}') 'subsection{First}' >>>p.sub(r'subsection{\\g 'subsection{First}' 替换也可以是一个甚至给你更多控制的函数。如果替换是个函数,该函数将会 被模式中每一个不重复的匹配所调用。在每个调用时,函数被作为 `MatchObject`的匹配函属,并可以使用这个信息去计算预期的字符串并返回 它。 在下面的例子里,替换函数将十进制翻译成十六进制: #!python >>>def hexrepl(match): ..."Return the hex string for a decimal number" ...value=int(match.group()) ...return hex(value) ... >>>p=re.compile(r'\\d+') >>>p.sub(hexrepl,'Call65490for printing,49152for user code.') 'Call0xffd2for printing,0xc000for user code.' 当使用模块级的re.sub()函数时,模式作为第一个参数。模式也许是一个字 符串或一个`RegexObject`;如果你需要指定正则表达式标志,你必须要么使 用`RegexObject`做第一个参数,或用使用模式内嵌修正器,如 sub("(?i)b+ [编辑]常见问题 正则表达式对一些应用程序来说是一个强大的工具,但在有些时候它并不直观 而且有时它们不按你期望的运行。本节将指出一些最容易犯的常见错误。[编辑]使用字符串方式 有时使用re模块是个错误。如果你匹配一个固定的字符串或单个的字符类, 并且你没有使用re的任何象IGNORECASE标志的功能,那么就没有必要使用 正则表达式了。字符串有一些方法是对固定字符串进行操作的,它们通常快很 多,因为都是一个个经过优化的C小循环,用以代替大的、更具通用性的正则 表达式引擎。 举个用一个固定字符串替换另一个的例子;如,你可以把"deed"替换成 "word"。re.sub()seems like the function to use for this,but consider the replace()method.注意replace()也可以在单词里面进行替 换,可以把"swordfish"变成"sdeedfish 了避免替换单词的一部分,模式将写成\\bword\\b,这是为了要求"word"两边 有一个单词边界。这是个超出替换能力的工作)。 另一个常见任务是从一个字符串中删除单个字符或用另一个字符来替代它。你 也许可以用象re.sub('\\n','',S)这样来实现,但translate()能够实现这两个任务,而且比任何正则表达式操作起来更快。 总之,在使用re模块之前,先考虑一下你的问题是否可以用更快、更简单的 字符串方法来解决。 [编辑]match()vs search() match()函数只检查RE是否在字符串开始处匹配,而search()则是扫描整 个字符串。记住这一区别是重要的。记住,match()只报告一次成功的匹配, 它将从0处开始;如果匹配不是从0开始的,match()将不会报告它。 #!python >>>print re.match('super','superstition').span() (0,5) >>>print re.match('super','insuperable') None 另一方面,search()将扫描整个字符串,并报告它找到的第一个匹配。 #!python >>>print re.search('super','superstition').span() (0,5) >>>print re.search('super','insuperable').span() (2,7)有时你可能倾向于使用re.match(),只在RE的前面部分添加.*。请尽量不要这么做,最好采用re.search()代替之。正则表达式编译器会对REs做一 些分析以便可以在查找匹配时提高处理速度。一个那样的分析机会指出匹配的 第一个字符是什么;举个例子,模式Crow必须从"C"开始匹配。分析机可以 让引擎快速扫描字符串以找到开始字符,并只在"C"被发现后才开始全部匹 配。 添加.*会使这个优化失败,这就要扫描到字符串尾部,然后回溯以找到RE 剩余部分的匹配。使用re.search()代替。 [编辑]贪婪vs不贪婪 当重复一个正则表达式时,如用a*,操作结果是尽可能多地匹配模式。当你试 着匹配一对对称的定界符,如HTML标志中的尖括号时这个事实经常困扰你。 匹配单个HTML标志的模式不能正常工作,因为.*的本质是“贪婪”的 #!python >>>s=' >>>len(s) 32 >>>print re.match('<.*>',s).span() (0,32) >>>print re.match('<.*>',s).group() RE匹配在""中的"<",.*消耗掉子符串的剩余部分。在RE中保持 更多的左,虽然>不能匹配在字符串结尾,因此正则表达式必须一个字符一个 字符地回溯,直到它找到>的匹配。最终的匹配从"""中的"> 在这种情况下,解决方案是使用不贪婪的限定符*?、+?、或{m,n}?,尽可 能匹配小的文本。在上面的例子里,">"在第一个"<"之后被立即尝试,当 它失败时,引擎一次增加一个字符,并在每步重试">"。这个处理将得到正确 的结果: #!python >>>print re.match('<.*?>',s).group() 注意用正则表达式分析HTML或XML是痛苦的。变化混乱的模式将处理常见情况,但HTML和XML则是明显会打破正则表达式的特殊情况;当你编写一个正则表达式去处理所有可能的情况时,模式将变得非常复杂。象这样的任务用HTML或XML解析器。[编辑]不用re.VERBOSE 现在你可能注意到正则表达式的表示是十分紧凑,但它们非常不好读。中度复 杂的REs可以变成反斜杠、圆括号和元字符的长长集合,以致于使它们很难读懂。 在这些REs中,当编译正则表达式时指定re.VERBOSE标志是有帮助的,因为它允许你可以编辑正则表达式的格式使之更清楚。 re.VERBOSE标志有这么几个作用。在正则表达式中不在字符类中的空白符被忽略。这就意味着象dog|cat这样的表达式和可读性差的dog|cat相同,但 [a b]将匹配字符"a"、"b"或空格。另外,你也可以把注释放到RE中;注 释是从"#"到下一行。当使用三引号字符串时,可以使REs格式更加干净: #!python pat=re.compile(r""" \\s*#Skip leading whitespace (?P \\s*:#Whitespace,and a colon (?P #lose the following trailing whitespace \\s*$#Trailing whitespace to end-of-line "" 这个要难读得多: #!python pat=re.compile(r"\\s*(?P [编辑]反馈 正则表达式是一个复杂的主题。本文能否有助于你理解呢?那些部分是否不清晰,或在这儿没有找到你所遇到的问题?如果是那样的话,请将建议发给作者 以便改进。 描述正则表达式最全面的书非Jeffrey Friedl写的《精通正则表达式》莫 属,该书由O'Reilly出版。可惜该书只专注于Perl和Java风格的正则表 达式,不含任何Python材料,所以不足以用作Python编程时的参考。(第一版包含有Python现已过时的regex模块,自然用处不大)。 《精通正则表达式》第三版已经有部分正则表达式使用python说明,另外PHP 风格的更是一个章节说明。--why[编辑]关于本文档 本文档使用LaTeX2HTML转换器生成。 LaTeX2HTML is Copyright©1993,1994,1995,1996,1997,Nikos Drakos, Computer Based Learning Unit,University of Leeds,and Copyright©1997,1998,Ross Moore,Mathematics Department,Macquarie University, Sydney. The application of LaTeX2HTML to the Python documentation has been heavily tailored by Fred L.Drake,Jr.Original navigation icons were contributed by Christopher Petrilli.
