zl程序教程

您现在的位置是:首页 >  后端

当前栏目

PYTHON正则表达式re模块使用说明

Python正则表达式模块 使用 说明 re
2023-06-13 09:14:28 时间

首先,运行Python解释器,导入re模块并编译一个RE:

#!python
Python2.2.2(#1,Feb102003,12:57:01)
>>>importre
>>>p=re.compile("[a-z]+")
>>>p
<_sre.SRE_Patternobjectat80c3c28>

现在,你可以试着用RE的[a-z]+去匹配不同的字符串。一个空字符串将根本不能匹配,因为+的意思是“一个或更多的重复次数”。在这种情况下match()将返回None,因为它使解释器没有输出。你可以明确地打印出match()的结果来弄清这一点。

#!python
>>>p.match("")
>>>printp.match("")
None

现在,让我们试着用它来匹配一个字符串,如"tempo"。这时,match()将返回一个MatchObject。因此你可以将结果保存在变量里以便后面使用。

#!python
>>>m=p.match("tempo")
>>>printm
<_sre.SRE_Matchobjectat80c4f68>

现在你可以查询`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
>>>printp.match(":::message")
None
>>>m=p.search(":::message");printm
<re.MatchObjectinstanceat80c9650>
>>>m.group()
"message"
>>>m.span()
(4,11)

在实际程序中,最常见的作法是将`MatchObject`保存在一个变量里,然后检查它是否为None,通常如下所示:

#!python
p=re.compile(...)
m=p.match("stringgoeshere")
ifm:
print"Matchfound:",m.group()
else:
print"Nomatch"

两个`RegexObject`方法返回所有匹配模式的子串。findall()返回一个匹配字符串行表:

#!python
>>>p=re.compile("\d+")
>>>p.findall("12drummersdrumming,11piperspiping,10lordsa-leaping")
["12","11","10"]

findall()在它返回结果时不得不创建一个列表。在Python2.2中,也可以用finditer()方法。

#!python
>>>iterator=p.finditer("12drummersdrumming,11...10...")
>>>iterator
<callable-iteratorobjectat0x401833ac>
>>>formatchiniterator:
...    printmatch.span()
...
(0,2)
(22,24)
(29,31)

 

模块级函数

你不一定要产生一个`RegexObject`对象然后再调用它的方法;re模块也提供了顶级函数调用如match()、search()、sub()等等。这些函数使用RE字符串作为第一个参数,而后面的参数则与相应`RegexObject`的方法参数相同,返回则要么是None要么就是一个`MatchObject`的实例。

#!python
>>>printre.match(r"From\s+","Fromageamk")
None
>>>re.match(r"From\s+","FromamkThuMay1419:12:101998")
<re.MatchObjectinstanceat80c5978>

Underthehood,这些函数简单地产生一个RegexOject并在其上调用相应的方法。它们也在缓存里保存编译后的对象,因此在将来调用用到相同RE时就会更快。


你将使用这些模块级函数,还是先得到一个`RegexObject`再调用它的方法呢?如何选择依赖于怎样用RE更有效率以及你个人编码风格。如果一个RE在代码中只做用一次的话,那么模块级函数也许更方便。如果程序包含很多的正则表达式,或在多处复用同一个的话,那么将全部定义放在一起,在一段代码中提前编译所有的REs更有用。从标准库中看一个例子,这是从xmllib.py文件中提取出来的:

#!python
ref=re.compile(...)
entityref=re.compile(...)
charref=re.compile(...)
starttagopen=re.compile(...)

我通常更喜欢使用编译对象,甚至它只用一次,butfewpeoplewillbeasmuchofapuristaboutthisasIam。

 

编译标志

编译标志让你可以修改正则表达式的一些运行方式。在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","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"""
&[[]]      #Startofanumericentityreference
(
[0-9]+[^0-9]      #Decimalform
|0[0-7]+[^0-7]  #Octalform
|x[0-9a-fA-F]+[^0-9a-fA-F]#Hexadecimalform
)
""",re.VERBOSE)

没有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-widthassertions)。它们并不会使引擎在处理字符串时更快;相反,它们根本就没有对应任何字符,只是简单的成功或失败。举个例子,\b是一个在单词边界定位当前位置的界定符(assertions),这个位置根本就不会被\b改变。这意味着零宽界定符(zero-widthassertions)将永远不会被重复,因为如果它们在给定位置匹配一次,那么它们很明显可以被匹配无数次。

|


可选项,或者"or"操作符。如果A和B是正则表达式,A|B将匹配任何匹配了"A"或"B"的字符串。|的优先级非常低,是为了当你有多字符串要选择时能适当地运行。Crow|Servo将匹配"Crow"或"Servo",而不是"Cro",一个"w"或一个"S",和"ervo"。


为了匹配字母"|",可以用\|,或将其包含在字符类中,如[|]。

^


匹配行首。除非设置MULTILINE标志,它只是匹配字符串的开始。在MULTILINE模式里,它也可以直接匹配字符串中的每个换行。


例如,如果你只希望匹配在行首单词"From",那么RE将用^From。

#!python
>>>printre.search("^From","FromHeretoEternity")
<re.MatchObjectinstanceat80c1520>
>>>printre.search("^From","RecitingFromMemory")
None

$


匹配行尾,行尾被定义为要么是字符串尾,要么是一个换行字符后面的任何位置。

#!python
>>>printre.search("}$","{block}")
<re.MatchObjectinstanceat80adfa8>
>>>printre.search("}$","{block}")
None
>>>printre.search("}$","{block}\n")
<re.MatchObjectinstanceat80adfa8>

匹配一个"$",使用\$或将其包含在字符类中,如[$]。

\A


只匹配字符串首。当不在MULTILINE模式,\A和^实际上是一样的。然而,在MULTILINE模式里它们是不同的;\A只是匹配字符串首,而^还可以匹配在换行符之后字符串的任何位置。

\Z

Matchesonlyattheendofthestring.
只匹配字符串尾。

\b

单词边界。这是个零宽界定符(zero-widthassertions)只用以匹配单词的词首和词尾。单词被定义为一个字母数字序列,因此词尾就是用空白符或非字母数字符来标示的。


下面的例子只匹配"class"整个单词;而当它被包含在其他单词中时不匹配。

#!python
>>>p=re.compile(r"\bclass\b")
>>>printp.search("noclassatall")
<re.MatchObjectinstanceat80c8f28>
>>>printp.search("thedeclassifiedalgorithm")
None
>>>printp.search("onesubclassis")
None

当用这个特殊序列时你应该记住这里有两个微妙之处。第一个是Python字符串和正则表达式之间最糟的冲突。在Python字符串里,"\b"是反斜杠字符,ASCII值是8。如果你没有使用raw字符串时,那么Python将会把"\b"转换成一个回退符,你的RE将无法象你希望的那样匹配它了。下面的例子看起来和我们前面的RE一样,但在RE字符串前少了一个"r"。

#!python
>>>p=re.compile("\bclass\b")
>>>printp.search("noclassatall")
None
>>>printp.search("\b"+"class"+"\b")
<re.MatchObjectinstanceat80c3ee0>

第二个在字符类中,这个限定符(assertion)不起作用,\b表示回退符,以便与Python字符串兼容。

\B


另一个零宽界定符(zero-widthassertions),它正好同\b相反,只在当前位置不在单词边界时匹配。

 

分组

你经常需要得到比RE是否匹配还要多的信息。正则表达式常常用来分析字符串,编写一个RE匹配感兴趣的部分并将其分成几个小组。举个例子,一个RFC-822的头部用":"隔成一个头部名和一个值,这就可以通过编写一个正则表达式匹配整个头部,用一组匹配头部名,另一组匹配头部值的方式来处理。


组是通过"("和")"元字符来标识的。"("和")"有很多在数学表达式中相同的意思;它们一起把在它们里面的表达式组成一组。举个例子,你可以用重复限制符,象*,+,?,和{m,n},来重复组里的内容,比如说(ab)*将匹配零或更多个重复的"ab"。

#!python
>>>p=re.compile("(ab)*")
>>>printp.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")

Thegroups()方法返回一个包含所有小组字符串的元组,从1到所含的小组号。

#!python
>>>m.groups()
("abc","b")

模式中的逆向引用允许你指定先前捕获组的内容,该组也必须在字符串当前位置被找到。举个例子,如果组1的内容能够在当前位置找到的话,\1就成功否则失败。记住Python字符串也是用反斜杠加数据来允许字符串中包含任意字符的,所以当在RE中使用逆向引用时确保使用raw字符串。


例如,下面的RE在一个字符串中找到成双的词。

#!python
>>>p=re.compile(r"(\b\w+)\s+\1")
>>>p.search("Parisinthethespring").group()
"thethe"

象这样只是搜索一个字符串的逆向引用并不常见--用这种方式重复数据的文本格式并不多见--但你不久就可以发现它们用在字符串替换上非常有用。

 

无捕获组和命名组

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


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


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


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


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


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

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

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


其次,更重要和强大的是命名组;与用数字指定组不同的是,它可以用名字来指定。


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

#!python
>>>p=re.compile(r"(?P<word>\b\w+\b)")
>>>m=p.search("((((Lotsofpunctuation)))")
>>>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("Parisinthethespring").group()
"thethe"

 

前向界定符

另一个零宽界定符(zero-widthassertion)是前向界定符。前向界定符包括前向肯定界定符和后向肯定界定符,所下所示:

(?=...)

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

(?!...)

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


通过示范在哪前向可以成功有助于具体实现。考虑一个简单的模式用于匹配一个文件名,并将其通过"."分成基本名和扩展名两部分。如在"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("Thisisatest,shortandsweet,ofsplit().")
["This","is","a","test","short","and","sweet","of","split",""]
>>>p.split("Thisisatest,shortandsweet,ofsplit().",3)
["This","is","a","test,shortandsweet,ofsplit()."]

有时,你不仅对定界符之间的文本感兴趣,也需要知道定界符是什么。如果捕获括号在RE中使用,那么它们的值也会当作列表的一部分返回。比较下面的调用:

#!python
>>>p=re.compile(r"\W+")
>>>p2=re.compile(r"(\W+)")
>>>p.split("This...isatest.")
["This","is","a","test",""]
>>>p2.split("This...isatest.")
["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","bluesocksandredshoes")
"coloursocksandcolourshoes"
>>>p.sub("colour","bluesocksandredshoes",count=1)
"coloursocksandredshoes"

subn()方法作用一样,但返回的是包含新字符串和替换执行次数的两元组。

#!python
>>>p=re.compile("(blue|white|red)")
>>>p.subn("colour","bluesocksandredshoes")
("coloursocksandcolourshoes",2)
>>>p.subn("colour","nocoloursatall")
("nocoloursatall",0)

空匹配只有在它们没有紧挨着前一个匹配时才会被替换掉。

#!python
>>>p=re.compile("x*")
>>>p.sub("-","abxd")
"-a-b-"

如果替换的是一个字符串,任何在其中的反斜杠都会被处理。"\n"将会被转换成一个换行符,"\r"转换成回车等等。未知的转义如"\j"则保持原样。逆向引用,如"\6",被RE中相应的组匹配而被子串替换。这使你可以在替换后的字符串中插入原始文本的一部分。


这个例子匹配被"{"和"}"括起来的单词"section",并将"section"替换成"subsection"。

#!python
>>>p=re.compile("section{([^}]*)}",re.VERBOSE)
>>>p.sub(r"subsection{\1}","section{First}section{second}")
"subsection{First}subsection{second}"

还可以指定用(?P<name>...)语法定义的命名组。"\g<name>"将通过组名"name"用子串来匹配,并且"\g<number>"使用相应的组号。所以"\g<2>"等于"\2",但能在替换字符串里含义不清,如"\g<2>0"。("\20"被解释成对组20的引用,而不是对后面跟着一个字母"0"的组2的引用。)

#!python
>>>p=re.compile("section{(?P<name>[^}]*)}",re.VERBOSE)
>>>p.sub(r"subsection{\1}","section{First}")
"subsection{First}"
>>>p.sub(r"subsection{\g<1>}","section{First}")
"subsection{First}"
>>>p.sub(r"subsection{\g<name>}","section{First}")
"subsection{First}"

替换也可以是一个甚至给你更多控制的函数。如果替换是个函数,该函数将会被模式中每一个不重复的匹配所调用。在每个调用时,函数被作为`MatchObject`的匹配函属,并可以使用这个信息去计算预期的字符串并返回它。


在下面的例子里,替换函数将十进制翻译成十六进制:

#!python
>>>defhexrepl(match):
...    "Returnthehexstringforadecimalnumber"
...    value=int(match.group())
...    returnhex(value)
...
>>>p=re.compile(r"\d+")
>>>p.sub(hexrepl,"Call65490forprinting,49152forusercode.")
"Call0xffd2forprinting,0xc000forusercode."

当使用模块级的re.sub()函数时,模式作为第一个参数。模式也许是一个字符串或一个`RegexObject`;如果你需要指定正则表达式标志,你必须要么使用`RegexObject`做第一个参数,或用使用模式内嵌修正器,如sub("(?i)b+","x","bbbbBBBB")returns"xx"。

 

常见问题

正则表达式对一些应用程序来说是一个强大的工具,但在有些时候它并不直观而且有时它们不按你期望的运行。本节将指出一些最容易犯的常见错误。

 

使用字符串方式

有时使用re模块是个错误。如果你匹配一个固定的字符串或单个的字符类,并且你没有使用re的任何象IGNORECASE标志的功能,那么就没有必要使用正则表达式了。字符串有一些方法是对固定字符串进行操作的,它们通常快很多,因为都是一个个经过优化的C小循环,用以代替大的、更具通用性的正则表达式引擎。


举个用一个固定字符串替换另一个的例子;如,你可以把"deed"替换成"word"。re.sub()seemslikethefunctiontouseforthis,butconsiderthereplace()method.注意replace()也可以在单词里面进行替换,可以把"swordfish"变成"sdeedfish",不过RE也是可以做到的。(为了避免替换单词的一部分,模式将写成\bword\b,这是为了要求"word"两边有一个单词边界。这是个超出替换能力的工作)。


另一个常见任务是从一个字符串中删除单个字符或用另一个字符来替代它。你也许可以用象re.sub("\n","",S)这样来实现,但translate()能够实现这两个任务,而且比任何正则表达式操作起来更快。


总之,在使用re模块之前,先考虑一下你的问题是否可以用更快、更简单的字符串方法来解决。

 

match()vssearch()

match()函数只检查RE是否在字符串开始处匹配,而search()则是扫描整个字符串。记住这一区别是重要的。记住,match()只报告一次成功的匹配,它将从0处开始;如果匹配不是从0开始的,match()将不会报告它。

#!python
>>>printre.match("super","superstition").span()
(0,5)
>>>printre.match("super","insuperable")
None

另一方面,search()将扫描整个字符串,并报告它找到的第一个匹配。

#!python
>>>printre.search("super","superstition").span()
(0,5)
>>>printre.search("super","insuperable").span()
(2,7)

有时你可能倾向于使用re.match(),只在RE的前面部分添加.*。请尽量不要这么做,最好采用re.search()代替之。正则表达式编译器会对REs做一些分析以便可以在查找匹配时提高处理速度。一个那样的分析机会指出匹配的第一个字符是什么;举个例子,模式Crow必须从"C"开始匹配。分析机可以让引擎快速扫描字符串以找到开始字符,并只在"C"被发现后才开始全部匹配。

添加.*会使这个优化失败,这就要扫描到字符串尾部,然后回溯以找到RE剩馀部分的匹配。使用re.search()代替。

 

贪婪vs不贪婪

当重复一个正则表达式时,如用a*,操作结果是尽可能多地匹配模式。当你试着匹配一对对称的定界符,如HTML标志中的尖括号时这个事实经常困扰你。匹配单个HTML标志的模式不能正常工作,因为.*的本质是“贪婪”的

#!python
>>>s="<html><head><title>Title</title>"
>>>len(s)
32
>>>printre.match("<.*>",s).span()
(0,32)
>>>printre.match("<.*>",s).group()
<html><head><title>Title</title>

RE匹配在"<html>"中的"<",.*消耗掉子符串的剩馀部分。在RE中保持更多的左,虽然>不能匹配在字符串结尾,因此正则表达式必须一个字符一个字符地回溯,直到它找到>的匹配。最终的匹配从"<html"中的"<"到"</title>"中的">",这并不是你所想要的结果。


在这种情况下,解决方案是使用不贪婪的限定符*?、+?、??或{m,n}?,尽可能匹配小的文本。在上面的例子里,">"在第一个"<"之后被立即尝试,当它失败时,引擎一次增加一个字符,并在每步重试">"。这个处理将得到正确的结果:

#!python
>>>printre.match("<.*?>",s).group()
<html>

注意用正则表达式分析HTML或XML是痛苦的。变化混乱的模式将处理常见情况,但HTML和XML则是明显会打破正则表达式的特殊情况;当你编写一个正则表达式去处理所有可能的情况时,模式将变得非常复杂。象这样的任务用HTML或XML解析器。

 

不用re.VERBOSE

现在你可能注意到正则表达式的表示是十分紧凑,但它们非常不好读。中度复杂的REs可以变成反斜杠、圆括号和元字符的长长集合,以致于使它们很难读懂。


在这些REs中,当编译正则表达式时指定re.VERBOSE标志是有帮助的,因为它允许你可以编辑正则表达式的格式使之更清楚。


re.VERBOSE标志有这么几个作用。在正则表达式中不在字符类中的空白符被忽略。这就意味着象dog|cat这样的表达式和可读性差的dog|cat相同,但[ab]将匹配字符"a"、"b"或空格。另外,你也可以把注释放到RE中;注释是从"#"到下一行。当使用三引号字符串时,可以使REs格式更加干净:

#!python
pat=re.compile(r"""
\s*                #Skipleadingwhitespace
(?P<header>[^:]+)  #Headername
\s*:              #Whitespace,andacolon
(?P<value>.*?)      #Theheader"svalue--*?usedto
#losethefollowingtrailingwhitespace
\s*$                #Trailingwhitespacetoend-of-line
""",re.VERBOSE)

这个要难读得多:

#!python
pat=re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")

反馈

正则表达式是一个复杂的主题。本文能否有助于你理解呢?那些部分是否不清晰,或在这儿没有找到你所遇到的问题?如果是那样的话,请将建议发给作者以便改进。