13 KiB
Python:正则表达式简介
原文:https://www.blog.pythonlibrary.org/2016/06/08/python-an-intro-to-regular-expressions/
正则表达式基本上是一种微小的语言,你可以在 Python 和许多其他编程语言中使用它。您经常会听到正则表达式被称为“regex”、“regexp”或只是“RE”。有些语言,比如 Perl 和 Ruby,实际上直接在语言本身中支持正则表达式语法。Python 只通过需要导入的库来支持它们。正则表达式的主要用途是匹配字符串。使用正则表达式创建字符串匹配规则,然后将其应用于字符串,以查看是否有匹配。
正则表达式“language”实际上很小,所以您不能用它来满足所有的字符串匹配需求。除此之外,虽然有一些任务可以使用正则表达式,但它可能会变得非常复杂,以至于难以调试。在这种情况下,你应该使用 Python。应该注意的是,Python 本身就是一种优秀的文本解析语言,可以用于正则表达式中的任何操作。然而,这样做可能需要更多的代码,并且比正则表达式慢,因为正则表达式是用 c 编译和执行的。
匹配的字符
当你想匹配一个字符串中的一个字符时,在大多数情况下你可以只使用那个字符或者那个子字符串。因此,如果我们想要匹配“狗”,那么我们将使用字母 dog 。当然,有一些字符是为正则表达式保留的。这些被称为元字符。以下是 Python 正则表达式实现支持的元字符的完整列表:
. ^ $ * + ? { } [ ] \ | ( )
让我们花点时间看看其中一些是如何工作的。您将遇到的最常见的元字符对之一是方括号:[和]。有用于创建一个“字符类”,这是一组你想匹配的字符。你可以这样单独列出角色:【XYZ】。这将匹配大括号之间列出的任何字符。也可以用破折号来表示要匹配的一系列字符:【a-g】。在这个例子中,我们将匹配字母 a 到 g 中的任何一个。
但是,要真正进行搜索,我们需要添加一个要查找的开始字符和一个结束字符。为了使这更容易,我们可以使用允许重复的星号。*不是匹配*,而是告诉正则表达式前一个字符可能匹配零次或多次。
看一个简单的例子总是有帮助的:
'a[b-f]*f
这个正则表达式模式意味着我们将寻找字母 a ,我们类中的零个或多个字母,[b-f]并且它需要以一个 f 结尾。让我们尝试在 Python 中使用这个表达式:
>>> import re
>>> text = 'abcdfghijk'
>>> parser = re.search('a[b-f]*f', text)
<_sre.sre_match object="" span="(0," match="abcdf">
>>> parser.group()
'abcdf'
基本上这个表达式会查看我们传递给它的整个字符串,在这个例子中是 abcdfghijk 。它会在一开始的比赛中找到一只和一只。然后,因为它有一个末尾带星号的字符类,所以它实际上会读入字符串的其余部分,看看是否匹配。如果不匹配,它会一次回溯一个字符,试图找到匹配。
当我们调用 re 模块的搜索函数时,所有这些神奇的事情就发生了。如果我们找不到匹配,那么返回 None 。否则,我们会得到一个匹配的对象,你可以在上面看到。要真正看到匹配的样子,需要调用组方法。
还有另一个类似于*的重复元字符。就是 + ,会匹配一次或多次。这与匹配0 次或更多次的*有细微的区别。 + 要求它所寻找的字符至少出现一次。
最后两个重复元字符的工作方式略有不同。有个问号,?,它将匹配一次或零次。它将前面的字符标记为可选。一个简单的例子是“co-?op”。这将与“coop”和“co-op”匹配。
最后一个重复的元字符是{a,b},其中 a 和 b 是十进制整数。这意味着必须至少有 a 次重复,最多有 b 次重复。您可能想尝试这样的方法:
xb{1,4}z
这是一个相当愚蠢的例子,但它说的是我们将匹配像 xbz 、 xbbz 、xbbz和xbbz这样的东西,而不是 xz ,因为它没有“b”。
我们要学习的下一个元字符是 ^ 。这个字符将允许我们匹配没有在我们的类中列出的字符。换句话说,它将补充我们的课程。只有当我们真正把^放入我们的类中时,这才会起作用。如果是在课堂之外,那么我们将尝试与^进行真正的比赛。一个很好的例子是这样的:[^a].这将匹配除字母“a”以外的任何字符。
^ 也用作锚,因为它通常用于字符串开头的匹配。弦的末端有对应的锚,是 $ 。
我们已经花了很多时间介绍正则表达式的各种概念。在接下来的几节中,我们将深入研究一些更真实的代码示例!
使用搜索的模式匹配
让我们花点时间来学习一些模式匹配的基础知识。当使用 Python 在字符串中寻找模式时,可以像本章前面的例子一样使用 search 函数。方法如下:
import re
text = "The ants go marching one by one"
strings = ['the', 'one']
for string 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 not find "{}"'.format(string))
对于这个例子,我们导入了 re 模块并创建了一个简单的字符串。然后我们创建一个两个字符串的列表,我们将在主字符串中搜索。接下来,我们循环遍历我们计划搜索的字符串,并实际运行对它们的搜索。如果有匹配的,我们就打印出来。否则我们告诉用户没有找到字符串。
在这个例子中,还有几个其他的函数值得解释。你会注意到我们称为跨度。这给了我们匹配的字符串的开始和结束位置。如果您打印出我们分配给 span 的 text_pos ,您将得到这样一个元组:(21,24)。或者,你可以调用一些匹配方法,这就是我们接下来要做的。我们用 start 和 end 来抓取比赛的开始和结束位置,这也应该是我们从 span 得到的两个数字。
转义码
在 Python 中,还可以使用特殊的转义码来搜索一些序列。这里有一个简短的列表,对每个问题都有简要的解释:
\d
Matches digit
\D
Matches non-digit
\s
Matches whitespace
\S
Matches non-whitespace
\w
Matches alphanumeric
\W
Matches non-alphanumeric
您可以在字符类中使用这些转义码,比如: [\d] 。这将允许我们找到任何数字,相当于**【0-9】**。我强烈推荐你自己尝试一下其他的。
收集
re 模块允许你“编译”你经常搜索的表达式。这将基本上允许你把你的表达式变成一个 SRE 模式对象。然后,您可以在搜索功能中使用该对象。让我们使用前面的代码,并将其修改为使用 compile:
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))
您会注意到,这里我们通过对列表中的每个字符串调用 compile 来创建模式对象,并将结果赋给变量 regex 。然后,我们将这个正则表达式传递给我们的搜索函数。代码的其余部分是相同的。使用编译的主要原因是保存它,以便稍后在您的代码中重用。然而,compile 也有一些标志,可以用来启用各种特殊的功能。接下来我们将对此进行研究。
特别注意:当你编译模式时,它们会被自动缓存,所以如果你没有在代码中使用很多正则表达式,那么你可能不需要将编译后的对象保存到变量中。
编译标志
Python 3 中包含了 7 个编译标志,可以改变编译模式的行为。让我们来看看它们各自的功能,然后我们来看看如何使用编译标志。
关于。答/答美国信息交换标准代码
ASCII 标志告诉 Python,当与以下转义码结合使用时,只根据 ASCII 进行匹配,而不使用完全 Unicode 匹配:\w,\b,\B,\d,\D,\s 和\S。U / re。UNICODE 标志也是为了向后兼容;然而,这些标志是多余的,因为 Python 3 在缺省情况下已经在 Unicode 中匹配了。
关于。调试
这将显示关于已编译表达式的调试信息。
关于。我/ re。IGNORECASE
如果您想要执行不区分大小写的匹配,那么这就是适合您的标志。如果您的表达式是 [a-z]``并且您用这个标志编译它,您的模式也将匹配大写字母!这也适用于 Unicode,并且不受当前区域设置的影响。
关于。L / re。现场
使转义码:\w,\W,\b,\d,\s 和\S 取决于当前的区域设置。然而,文档说你不应该依赖这个标志,因为本地机制本身是非常不可靠的。相反,只需使用 Unicode 匹配。文档继续指出,这个标志实际上只对字节模式有意义。
关于。M / re。多线
当您使用这个标志时,您是在告诉 Python 让 ^ 模式字符在字符串的开头和每一行的开头都匹配。它还告诉 Python**$**应该在字符串的末尾和每一行的末尾匹配,这与它们的缺省值有微妙的不同。有关其他信息,请参见文档。
关于。S / re。DOTALL
这面有趣的旗帜将会成为。(句点)元字符匹配任何字符。如果没有标志,它将匹配除换行符之外的任何内容。
关于。X / re。冗长的
如果您发现您的正则表达式难以阅读,那么这个标志可能正是您所需要的。它将允许可视化地分离正则表达式的逻辑部分,甚至添加注释!模式中的空白将被忽略,除非是在字符类中,或者空白前面有未转义的反斜杠。
使用编译标志
让我们花点时间看看一个使用详细编译标志的简单例子!一个很好的例子是用一个常见的电子邮件查找正则表达式,如 r'[\w\。-]+@[\w-]+'并使用 VERBOSE 标志添加一些注释。让我们来看看:
re.compile('''
[\w\.-]+ # the user name
@
[\w\.-]+' # the domain
''',
re.VERBOSE)
让我们继续学习如何找到多个匹配。
查找多个实例
到目前为止,我们所看到的是如何在一个字符串中找到第一个匹配。但是如果一个字符串中有多个匹配呢?让我们回顾一下如何找到单个匹配:
>>> import re
>>> silly_string = "the cat in the hat"
>>> pattern = "the"
>>> match = re.search(pattern, text)
>>> match.group()
'the'
现在您可以看到单词“the”有两个实例,但我们只找到一个。有两种方法可以找到这两者。我们要看的第一个是 findall 函数:
>>> import re
>>> silly_string = "the cat in the hat"
>>> pattern = "the"
>>> re.findall(pattern, silly_string)
['the', 'the']
函数会搜索你传递给它的字符串,并将每个匹配添加到一个列表中。一旦搜索完你的字符串,它将返回一个匹配列表。寻找多个匹配的另一种方法是使用 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 获得的字符串。所以我们需要对结果进行一些格式化,然后才能打印出来。试一下这段代码,看看它是如何工作的。
反斜杠很复杂
在 Python 的正则表达式中,反斜杠有点复杂。原因是正则表达式使用反斜杠来表示特殊形式或允许搜索一个特殊字符,而不是调用它,例如当我们想要搜索美元符号:$时。如果我们不反斜杠,我们只是创建一个锚。这个问题的出现是因为 Python 在文字字符串中使用反斜杠字符做同样的事情。假设您想要搜索这样的字符串(去掉引号):“\python”。
要在正则表达式中进行搜索,您需要对反斜杠进行转义,但是因为 Python 也使用反斜杠,所以反斜杠也必须进行转义,所以您将得到以下搜索模式:“\\python”。幸运的是,Python 通过在字符串前加上字母“r”来支持原始字符串。因此,我们可以通过执行以下操作来提高可读性:r"\python "。
所以如果你需要用反斜杠搜索某些东西,一定要使用原始字符串,否则你可能会得到一些意想不到的结果!
包扎
本文仅仅触及了正则表达式的皮毛。事实上,模块本身有更多的内容。有整本书都是关于正则表达式的,但是这应该给了你入门的基础。您将需要搜索示例和阅读文档,当您使用正则表达式时可能需要多次,但是当您需要时它们是一个方便的工具。