Python正则表达式:如何使用正则表达式

正则表达式(简称RE)本质上可以看作一个小的、高度专业化的编程语言,在Python中可以通过re模块使用它。使用正则表达式,你需要为想要匹配的字符串集合指定一套规则,字符串集合可以包含英文句子、e-mail地址、TeX命令或者其它任何你希望的字符串。然后您能提这样的问题:“这个字符串匹配这个模式吗?”,或者“在这个字符串中存在这个模式的匹配吗?”。你也能使用正则表达式修改一个字符串或者分离它。
正则表达式被编译到一系列的字节码,然后被C语言实现的匹配引擎执行。在一些高级应用场景,必须关注引擎怎么执行一个RE,以根据引擎的特征编写RE提高字节码的处理效率。这篇文章不包含优化,优化需要对匹配引擎的内部实现有好的理解。

正则表达式相对小并且存在限制,所以不是所有的字符串处理任务都能用正则表达式解决。也存在有些任务可以用正则表达式做,但表达式非常复杂。在这些情况下,更好的选择是使用Python代码处理,但Python代码相对正则表达式会更慢,但却可能更好理解。

正则表达式简述

我们将从最简单的正则表达式开始,由于正则表达式被用于字符串的操作,我们将从最常用的任务开始:匹配字符。

匹配字符串

大部分字母和字符将匹配他们自身,例如,正则表达式test将匹配字符串test(你可以开始大小写不敏感模式,这样RE可以匹配Test或者TEST)。
这个规则存在例外,一些字符是特殊元字符,不匹配他们自身。他们暗示一些不寻常的事将被匹配,或者他们影响RE的其它部分,例如重复他们或者改变他们的含义。文章的其余部分都主要讨论各种元字符和他们的含义。
下面是元字符的列表,后面将介绍他们的含义:

. ^ $ * + ? { } [ ] \ | ( )

首先我们看[和],他们被用于指定一个字符类,表示你希望匹配的一套字符集。字符能被单独列出,或者使用'-'来指示字符的范围,例如:[abc]将匹配字符a、b或c的任意一个;[a-c]也是匹配a、b或c中的任意一个。如果你想匹配仅小写字母,那么RE应该为[a-z]。
在类中([ ]内)的元字符不是激活的,例如:[akm$]将匹配'a'、'k'、'm'或'$'中的任意一个,'$'是一个元字符,但是在字符类中它作为普通字符使用。
你也能排除类中列出的字符集,通过将'^'作为类的第一个字符,注意在类之外的'^'将仅仅匹配'^'字符,例如:[^5]将匹配除了5之外的任何字符。
或许最重要的元字符是反斜杠\。在Python中,反斜杠能被各种字符跟随作为各种特殊序列使用。它也能被用于取出元字符的特殊性将其作为本身匹配,例如:如果你需要匹配一个[或者\,你能在它们之前带上一个反斜杠移除它们的特殊含义,即\[或者\\。
以'\'开始的特殊序列中的一些表示了经常被使用的预定义字符集,例如数字集合、字符集合、或者非空白的任意字符集合。
让我们看一个例子:\w匹配任何字母数字。如果正则表达式模式以字节为单位,这等价于类[a-zA-Z0-9_]。如果正则表达式模式是字符串,则\w将匹配所有被unicodedata模块提供的在Unicode数据库总的字符。当编译正则表达式时,你能添加re.ASCII标志给\w更严格的限制。
下面提供了部分特殊序列供参考:
\d:匹配任意数字,等价于[0-9];
\D:匹配任意非数据字符,等价于[^0-9];
\s:匹配任意空白字符,等价于[ \t\n\r\f\v];
\S:匹配任意非空白字符,等价于[^ \t\n\r\f\v];
\w:匹配任意字母数字,等价于[a-zA-Z0-9_];
\W:匹配任意非字母和数字,等价于[^a-zA-Z0-9_]。
这些序列可以被包含到字符类中,例如:[\s,.]是一个字符类,将匹配任意的空白字符,或者',',或者'.'。
在这节中最后的元字符是'.',它匹配除了新行字符之外的任何字符,使用交替模式(re.DOTALL)它将匹配包括新行的所有字符,'.'通常被用于需要匹配“任意字符”的场景。

处理重复

正则表达式的首要功能是匹配字符集,而正则表达式的另一个能力则是指定RE中特定部分必须被重复多少次。
处理重复的第一个元字符是'*','*'不会匹配字符'*',它表示先前的字符能被匹配0次或者多次。
例如:ca*t将匹配ct(0个a)、cat(1个a)、caaat(3个a)、等等。RE引擎内部会限制a的匹配的数量,但通常足够了。
重复(例如*)算法是贪婪的,对于重复的RE,匹配引擎将尝试尽可能多的重复次数,如果模式的后面部分不匹配,则匹配引擎将回退并再次尝试更少的重复次数。
例如,考虑表达式a[bcd]*b,这匹配单词'a',0个或者多个来自类[bcd]的字母,最后以'b'结束。下面是RE匹配abcbd的过程:
1、匹配a:RE匹配a成功;
2、匹配abcbd:引擎匹配[bcd]*,由于尽可能的匹配更多,所以匹配了整个字符串;
3、匹配失败:引擎试着匹配b,但是已经到达字符串结尾,因此失败;
4、匹配abcb:回退,[bcd]*匹配减少一个字符;
5、匹配失败:再次尝试b,但当前位置的字符为d;
6、匹配abc:继续回退,以至于[bcd]*仅匹配bc;
7、匹配abcb:再次尝试b,这次当前位置的字符为b,匹配成功,结束。
RE最终匹配abcb,整个过程演示了匹配引擎的匹配过程,首先匹配尽可能多的字符,如果不匹配,则不断回退再次尝试。它将回退直到[bcd]*匹配0个字符,如果任然失败,则引擎得出结论“字符串不匹配RE”。
另一个重复的元字符是+,匹配一次或者多次。小心*和+之间的不同,*匹配0次或者多次,即可以匹配空;+则需要至少出现一次。例如:ca+t将匹配cat(1个a),caaat(3个a),但不匹配ct。
另外还有两个重复限定符,其一是问号'?',表示匹配一次或者0次,例如: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表示无限制。
你可能已经注意到,使用最后一个限定符可以取代前面3个限定符:{0,}等价于*;{1,}等价于+;{0,1}等价于?。为什么使用*、+或者?呢?主要在于,更简短的表达式更利于阅读和理解。

使用正则表达式

现在我们已经了解了正则表达式的基本语法,下面看在Python中怎么使用正则表达式。re模块提供了使用正则表达式的接口,允许你编译RE到对象,然后使用它们。

编译正则表达式

正则表达式被编译到模式对象,提供了各种操作的方法,例如模式匹配或者替换。

>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')

re.compile()也提供了一个可选的flags参数,用于激活各种特征,后面将详细介绍,下面是一个简单的例子:

>>> p = re.compile('ab*', re.IGNORECASE)

RE作为一个字符串传给re.compile()。RE被作为字符串处理是因为正则表达式不是Python语言核心的一部分,没有特定的语言用于创建它们。re模块仅仅是Python包含的一个C语言扩展模块,就像socket和zlib模块一样。
将RE作为字符串保持了Python语言的简单,但也存在不利,例如下一节将讲述的内容。

反斜杠问题

如前所述,正则表达式使用反斜杠来表示一些特殊组合或者允许特殊字符作为普通字符使用。这一点和Python对于发斜杠的使用冲突。
你如果想写一个RE匹配字符串\section,我们看看怎么构造一个正则表达式对象:首先,我们使用整个字符串作为正则表达式;其次,找出反斜杠和其它元字符,在它们前面添加反斜杠,变为\\section;最后,字符串被传入到re.compile(),由于传入的必须为\\section,结合Python语法,每个\的前面必须再次添加一个\,因此,最终在Python中传入的字符串为"\\\\section"。
简而言之,为了匹配一个反斜杠,在Python中你需要写'\\\\'作为RE字符串。这导致了很多重复的反斜杠,使语法很难于理解。
解决方案是为正则表达式使用Python的原生字符串注释。当字符串带有前缀'r'时,反斜杠将不以特殊字符处理,于是r"\n"是包含'\'和'n'的两个字符的字符串,而"\n"是包含换行符的一个字符的字符串。在Python中正则表达式将经常采用这种方式编写。
 

执行匹配

一旦你有一个已编译的正则表达式对象,你就可以使用该对象的方法和属性,下面做一个简单的介绍。
1)match()
确定RE是否匹配字符串的开头。
2)search()
扫描字符串,查找和RE匹配的任何位置。
3)findall()
找到所有RE匹配的子字符串,并作为一个列表返回。
4)finditer()
发现所有RE匹配的子字符串,并作为一个iterator返回。
如果找到匹配,match()和search()返回None;如果匹配成功,则返回一个匹配对象实例,包含匹配的信息:开始和结束点、匹配的子字符串、等等。
下面来看看Python中怎么使用正则表达式。
首先,运行Python解释器,导入re模块,并且编译一个RE:

>>> import re
>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')

现在,你能尝试匹配各种字符串,一个空字符串将根本不匹配,由于+意味着‘一个或者更多’,match()将返回None,你能直接打印结果:

>>> p.match("")
>>> print(p.match(""))
None

接下来,我们尝试一个匹配的字符串,这时,match()将返回一个匹配对象,因此你应该存储结果在一个变量中以供后面使用:

>>> m = p.match('tempo')
>>> m 
<_sre.SRE_Match object; span=(0, 5), match='tempo'>

现在你能询问匹配对象关于匹配字符串的信息。匹配对象也有几个方法和属性,最重要的几个是:
1)group()
返回被RE匹配的字符串
2)start()
返回匹配的开始位置
3)end()
返回匹配的结束位置
4)span()
返回包含匹配位置的元组(开始,结束)
下面是一些使用这些方法的例子:

>>> m.group()
'tempo'
>>> m.start(), m.end()
(0, 5)
>>> m.span()
(0, 5)

由于match()仅检查RE是否匹配字符串的开始,start()将总是返回0。然而,search()方法扫描整个字符串,因此开始位置不一定为0:

>>> print(p.match('::: message'))
None
>>> m = p.search('::: message'); print(m) 
<_sre.SRE_Match object; span=(4, 11), match='message'>
>>> m.group()
'message'
>>> m.span()
(4, 11)

在实际编程汇总,通常将匹配对象存入一个变量中,然后检查它是否为None,例如:

findall()返回匹配字符串的列表:

>>> p = re.compile('\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']

findall()在返回结果前必须创建完整的列表,而finditer()则返回匹配对象实例作为一个iterator:

>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
>>> iterator 
<callable_iterator object at 0x...>
>>> for match in iterator:
...    print(match.span())
...
(0, 2)
(22, 24)
(29, 31)

模块级函数

你不是一定需要创建一个模式对象然后调用它的方法,re模块也提供了模块级的函数match()、search()、findall()、sub()、等等。这些函数采用和对应的模式方法同样的参数,也同样返回None或者匹配对象实例:

>>> print(re.match(r'From\s+', 'Fromage amk'))
None
>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998') 
<_sre.SRE_Match object; span=(0, 5), match='From '>

这些函数创建一个模式对象,并调用它上面的方法,它们也存储编译后的对象到缓存中,以至于未来使用同样的RE将不需要重新编译。
你应该用这些模块及的函数,还是应该通过模块对象来调用呢?如果你正在做一个正则表达式的循环,则预编译将节省许多函数调用,否则,两个方式没有太大区别。

猜你喜欢

转载自www.linuxidc.com/Linux/2015-09/123290.htm