VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • PYTHON正则表达式 re模块使用说明

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

#!python
 Python 2.2.2 (#1, Feb 10 2003, 12:57:01)
 >>> import re
 >>> p = re.compile('[a-z]+')
 >>> p
 <_sre.SRE_Pattern object at 80c3c28>

现在,你可以试着用 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 at 80c4f68>

现在你可以查询 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
<re.MatchObject instance at 80c9650>
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('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']
findall() 在它返回结果时不得不创建一个列表。在 Python 2.2中,也可以用 finditer() 方法。

!python

iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
iterator
<callable-iterator object at 0x401833ac>
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 May 14 19:12:10 1998')
<re.MatchObject instance at 80c5978>


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", "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
)
""", 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-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'\bclass\b')
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('\bclass\b')
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')


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 里,追踪组号变得困难。有两个功能可以对这个问题有所帮助。它们也都使用正则表达式扩展的通用语法,因此我们来看看第一个。


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\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[ 123][0-9])-(?P[A-Z][a-z][a-z])-'
r'(?P[0-9][0-9][0-9][0-9])'
r' (?P[0-9][0-9])


相关教程