作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明。谢谢!

1 模块简介

正则表达式是一门小语言,你可以在Python中或者其他编程语言中使用。你经常可以看到正则表达式可以写为“regex”,“regexp”或者就是“RE”。一些语言,例如Perl或者Ruby,语言本身直接支持正则表达式。Python通过一个库来支持正则表达式,因此你需要引入这个库。正则表达式的主要用途就是匹配字符串。你先通过正则表达式来创建字符串匹配规则,然后将其应用到字符串,观察是否存在匹配。

正则表达式作为一门小语言,你不可能使用它来满足所有的字符串匹配需求。另外,尽管有些任务你可以使用正则表达式来完成,但是它可能非常复杂,以至于很难去调试。这种情况下,你应该只使用Python。值得注意的是,Python在文本分析方面是一门优秀的语言,它可以完成你通过正则表达式所做的任何事情。但是,它或许需要写很多代码来完成,并且会比正则表达式的速度慢,因为正则表达式是在C语言下编译和执行的。

2 模块使用

2.1 匹配字符

当你想匹配字符串中的一个字符,大部分情况下,你可以仅仅使用那个字符或者子字符串。如果我们想匹配“dog”,我们就使用字母“dog”。当然,正则表达式保留了一些字符。这些就是元字符。下面就是Python中正则表达式所支持的元字符,

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

下面我们将会花费一些时间来了解它们是如何工作的。你将会遇到的一个最常见的元字符对就是方括号:[和]。它们经常被用于创建字符类,就是你想匹配的一个字符集合。你或许单独列出每个字符,如[xyz]。这个就会匹配括号中任意一个字符。你也可以使用破折号来表示一个字符范围,例如[a-g]。在这个例子中,我们将会匹配从a到g中任意一个字符。

为了执行一个实际的搜索过程,我们需要增加一个起始字符来进行查找,另外还需要一个结束字符。为了让这个过程更容易,我们使用星号,它允许重复,而不是匹配*, *将会告诉正则表达式,先前的字符可以匹配0次或者多次。

一个简单的例子如下所示,

a[b-f]*f

这个正则表达式意味着,我们首先查找字符a,0个或者多个[b-f]中的字符,需要以字符f结束。让我们在Python中使用这段代码,

>>> import re
>>> text = "abcdfghijk"
>>> parser = re.search('a[b-f]*f',text)
>>> parser
<_sre.SRE_Match object at 0x106b30b90>
>>> parser.group()
'abcdf'

这段代码中,表达式会首先查找我们传入的字符串,在这里,字符串就是“abcdfghijk”。它将会发现开始位置的字母a会被匹配上。有一个末尾是星号的字符类,它将会读取剩余的字符串,观看是否匹配。如果不匹配,它就会回退一个字符来尝试找到一个匹配。

最神奇的地方就是我们调用re模块的search函数。如果我们没有找到一个匹配,就会返回None。否则,我们就获得一个Match对象,你可以在上述代码中看到。为了看到实际匹配上什么,你需要调用group方法。

还有另外一个类似于 * 的重复元字符。它就是 + ,将会匹配一次或者多次。这与 * 有所不同, * 只匹配0次或者多次。 + 需要查找的字符至少出现一次。

最后两个重复的元字符也有所不同。?,只匹配一次或者0次,意味着它前面的字符是可选的。一个简单的例子就是“co-op”,它就会匹配“coop”和“co-op”。

最后一个重复元字符就是{a,b},这里a和b都是十进制整数。这就意味着,它必须至少重复a次,最多重复b次。你可以按照如下方式来书写,

xb{1,4}z

这是一个很简单的例子,但是它将会匹配xbz,xbbz,xbbbz和xbbbbz,xz不会匹配上,因为它没有字符b。

下一个元字符就是。这个字符允许我们匹配没有出现在字符类中罗列出的字符,它是字符类的补集。只有我们将放在字符类里面,它才会生效。如果它在字符类的外面,我们将会匹配字符。一个很好的例子就是:[a]。这个将会匹配除字符a之外的任意字符。

字符^作为锚,另外一个作用是用于匹配字符串的起始位置,相对应的字符串结尾的锚就是$。

我们已经花费了相当多的时间来介绍很多正则表达式的概念。下面几节,我们将会深入几个实际的代码例子。

2.2 使用搜索进行模式匹配

让我们先学习一下基础的模式匹配。当你使用Python来查找一个字符串中的模式,你可以像上一节那样,使用search函数,代码如下:

import re

text = "Teh ants go marching one by one"

strings = ["the","one"]

for strig in strings:
    match = re.search(string,text)
    if match:
        print('Found "{}" in "{}"'.format(string,text))
        text_pos = match.span()
        print(text[match.start(),match.end()])
    else:
        print('Did ot find "{}"'.format(string))

这个例子中,我们首先引入re模块,并创建一个简单的字符串。然后我们创建包含两个要在主字符串进行查找的字符串。下一步,我们在要查找的字符串上进行遍历,并进行搜索。如果匹配上,我们将其打印出来。否则,我们告诉用户,字符串没有找到。

还有其他的函数需要解释一下。你或许注意到我们调用了span函数,这个会告诉我们待匹配的字符串的起始位置和结束位置。如果你将我们赋值的范围给text_pos打印出来,你将会得到一个元组,类似于(21,24)。另外你还可以调用一些其它方法,就是我们接下来要做的。我们使用start和end来获取匹配的起始位置和结束位置,它们就是我们所获取范围的两个数字。

2.3 转义字符

还有一些你在Python中搜索时需要的转义字符,下面就是一个转义字符列表以及相应的解释。

d 匹配数字
D 匹配非数字
s 匹配空格
S 匹配非空格
w 匹配数字字母
W 匹配非数字字母
你可以在字符类中使用转义字符,例如[\d],这就允许我们查找任何一个数字,等价于[0-9]。我非常推荐你使用它们。

2.4 编译

re模块允许你编译频繁搜索的表达式。这就允许你将表达式转换为SRE_Pattern对象。你可以在搜索函数中使用这个对象。让我们使用之前的代码,并将其修改一下用于编译,

import re

text = "The ants go marching one by one"

strings = ['the','one']

for string in strings:
    regex = re.compile(string)
    match = re.search(regex,text)
    if match:
        print('Found "{}" in "{}" '.format(string,text))
        text_pos = match.span()
        print(text[match.start():match.end()])
    else:
        print('Did not find "{}"'.format(string))

你将会注意到在这里,我们通过在列表中每个字符串调用编译来创建我们的模式对象,并将结果赋值给变量regex。我们将regex传递给搜索函数。剩余的代码都是相同的。主要的原因就是使用编译用于保存以便在后续代码中继续使用。当然,编译也可以使用标志位,这样可以使能不同特殊的功能。

当你编译模式时,他们将会自动获得缓存,你就不需要在代码中大量使用正则表达式,你不需要将已编译的对象保存到变量中。

2.5 编译标志位

Python 3中有7个编译标志位,可以用于改变编译模式行为。让我们看看这些标志位,并观察如何使用这些标志位。

2.5.1 re.A/re.ASCII

当遇到如下的编码:w、W、b、B、d、D、s和S,ASCII标志位告诉Python只匹配ASCII,而不是全部的Unicode。re.U/re.UNICODE标志位主要是为了后向兼容性,现在这些标志位已经废弃,因为Python 3默认匹配Unicode。

2.5.2 re.DEBUG

这个将会显示关于编译表达式的调试信息。

2.5.3 re.I/re.IGNORECASE

如果你想执行大小写不敏感匹配,那么这个标志位就可以起到这个作用。如果你的表达式是[a-z],如果你用这个标志位进行编译,你的模式也会匹配大写字符。这个也会对Unicode编码有效,并不会受到当前区域的影响。

2.5.4 re.L/re,LOCALE

w、W、b、B、d、D、s和S依赖当前区域。但是,Python官方文档说明你不应该依赖这个标志位,因为区域机制本身不可靠。取而代之,仅仅使用Unicode匹配。官方文档表示这个标志位仅仅对字节匹配有意义。

2.5.5 re.M/re.MULTILINE

当你使用这个标志位,你在告诉Python使得^模式字符匹配字符串的开始以及每行的开始。它也告诉Python使得$模式字符匹配字符串的末尾和每行的末尾,这个与默认的有些区别,具体可以查看官方文档。

2.5.6 re.S/re.DOTALL

这个标志位使得.元字符匹配任何字符。没有这个标志位,它将会匹配除了换行符之外的任何字符。

2.5.7 re.X/re.VERBOSE

如果你发现正则表达式很难阅读,这个标志位就会满足你的需要。它允许你将正则表达式中逻辑独立部分可视化出来,甚至添加注释。模式中的空格将会被忽略,除了在字符类中或者当空格在未绑定的反斜杠的前面。

2.5.8 使用编译标志位

让我们来看一个使用VERBOSE编译标志位的简单例子。一个例子就是邮箱地址查找正则表达式,例如,r'[w.-]+@[w.-]+',然后使用VERBOSE标志位来添加一些注视。代码如下所示,

re.compile('''
            [\w\.-]+    # the user name
            @
            [\w\.-]+    # the domain
            ''',
            re.VERBOSE
            )

让我们来学习如何查找多个匹配。

2.6 查找多个实例

我们之前所看到是如何查找字符串中的第一个匹配。但是如果你有一个多个匹配的字符串,该如何处理?让我们先回顾一下如何查找一个单独的匹配:

import re
silly_string = "the cat in the hat"
pattern = "the"
match = re.search(pattern,silly_string)
print match.group()

现在,你能够看到单词“word”有两个实例,但是我们仅仅找出一个。找出所有实例共有两个方法。首先,我们看一下findall函数:

import re
silly_string = "the cat in the hat"
pattern = "the"
print re.findall(pattern,silly_string)

findall函数将会搜索整个字符串,并将每个匹配添加到列表中。一旦它结束在字符串中的搜索,它就会返回匹配列表。另一种查找多匹配的方法是使用finditer函数。

import re
silly_string = "the cat in the hat"
pattern = "the"
for match in re.finditer(pattern,silly_string):
    s = "Found '{group}' at {begin}:{end}".format(
    group = match.group(),begin=match.start(),
    end = match.end())
    print(s)

正如你所猜测,finditer方法返回匹配实例的迭代器而非findall函数得到的字符串。我们需要做的就是将结果格式化,然后再打印出来。

2.7 反斜杠

反斜杠在Python中正则表达式有些复杂。原因就是正则表达式使用反斜杠来表示特殊形式或者允许其成为可搜索的特殊字符而非直接调用,例如,我们想搜索美元符号:$。如果我们不使用反斜杠,我们仅仅是创建一个锚。原因就是Python将反斜杠字符作为字面字符使用。让我们看看类似于“python”字符串的搜索结果。

为了在正则表达式中进行搜索,你需要去除反斜杠,但是由于Python也使用反斜杠,反斜杠也需要被去除,于是你的搜索模式就是“\python”。幸运的事,Python通过在字符串开始处添加字母‘r’就可以支持原始字符串。所以我们可以以r“\python”格式使得更加可读。

如果你需要使用反斜杠进行搜索,请确认使用了原始字符串,否则将会遇到未知的结果。

2.8 总结

这篇博文大致总结了正则表达式可以做的任务。实际上,还有很多超出模块本身的任务。有一本关于正则表达式的书,这里主要告诉你基本的知识。当你正在使用正则表达式时,可能需要你查找更多的例子并阅读官方文档。当你需要它时,它确实是一个好用的工具。

3 Reference

Python 201

posted on 2017-05-05 16:51  老顽童2007  阅读(2516)  评论(1编辑  收藏  举报