Python正则表达式的re模块使用详解

发布时间:2020-02-23编辑:脚本学堂
本文详细介绍了python中的正则表达式模块:re模块的使用方法,re模块用处很广的,有需要的朋友,一起研究学习下。

更多的元字符

粗体文字链接标题还有一些我们还没展示的元字符,其中的大部分将在本节展示。

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

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

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

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

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

复制代码 代码示例:
#!python
>>> print re.search('^From', 'From Here to Eternity')
<re.MatchObject instance at 80c1520>
>>> print re.search('^From', 'Reciting From Memory')
None

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

复制代码 代码示例:
#!python
>>> print re.search('}$', '{block}')
<re.MatchObject instance at 80adfa8>
>>> print re.search('}$', '{block} ')
None
>>> print re.search('}$', '{block}n')
<re.MatchObject instance at 80adfa8>
 

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

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

Z Matches only at the end of the string. 只匹配字符串尾。

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

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

复制代码 代码示例:
#!python
>>> p = re.compile(r'bclassb')
>>> print p.search('no class at all')
<re.MatchObject instance at 80c8f28>
>>> 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('bclassb')
>>> print p.search('no class at all')
None
>>> print p.search('b' + 'class' + 'b')
<re.MatchObject instance at 80c3ee0>

第二个在字符类中,这个限定符(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')

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

复制代码 代码示例:
#!python
>>> m.groups()
('abc', 'b')

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

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

复制代码 代码示例:
#!python
>>> p = re.compile(r'(bw+)s+1')
>>> p.search('Paris in the the spring').group()
'the the'
 

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

无捕获组和命名组

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

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

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

Python 新增了一个扩展语法到 Perl 扩展语法中。如果在问号后的第一个字符是 "P",你就可以知道它是针对 Python 的扩展。目前有两个这样的扩展: (?P<name>...) 定义一个命名组,(?P=name) 则是对命名组的逆向引用。如果 Perl 5 的未来版本使用不同的语法增加了相同的功能,那么 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>bw+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 的组内容再次在当前位置发现。

正则表达式为了找到重复的单词,(bw+)s+1 也可以被写成 (?P<word>bw+)s+(?P=word):
 

复制代码 代码示例:
#!python
>>> p = re.compile(r'(?P<word>bw+)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"。

.*[.]([^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 中相应的组匹配而被子串替换。这使你可以在替换后的字符串中插入原始文本的一部分。

这个例子匹配被 "{" 和 "}" 括起来的单词 "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
>>> 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, 'Call 65490 for printing, 49152 for user code.')
'Call 0xffd2 for printing, 0xc000 for user code.'
 

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

常见问题

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

使用字符串方式

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

举个 用一个固定字符串替换另一个 的例子,如:你可以把 "deed" 替换成 "word"。re.sub() 似乎正是胜任这个工作的函数,但还是考虑考虑 replace() 方法吧。注意 replace() 也可以在单词里面进行替换,可以把 "swordfish" 变成 "sdeedfish"。不过 RE 也是可以做到的。(为了避免替换单词的一部分,模式将写成 bwordb,这是为了要求 "word" 两边有一个单词边界。这是个超出 replace 能力的工作)。

另一个常见任务是从一个字符串中删除单个字符或用另一个字符来替代它。你也许可以用 re.sub('n',' ', s) 这样来实现,但 translate() 能够实现这两个任务,而且比任何正则表达式操作起来更快。 (translate 需要配合 string.maketrans 使用。例如:import string 后 'a1b3'.translate(string.maketrans('ab', 'cd')) )

总之,在使用 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 = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print re.match('<.*>', s).span()
(0, 32)
>>> print re.match('<.*>', s).group()
<html><head><title>Title</title>

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

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

复制代码 代码示例:
#!python
>>> print re.match('<.*?>', s).group()
<html>

注意用正则表达式分析 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<header>[^:]+)   # Header name
s* :               # Whitespace, and a colon
(?P<value>.*?)      # The header's value -- *? used to
# lose the following trailing whitespace
s*$                # Trailing whitespace to end-of-line
""", re.VERBOSE)
这个要难读得多:
#!python
pat = re.compile(r"s*(?P<header>[^:]+)s*:(?P<value>.*?)s*$")