正则表达式:Python 中的正则表达式(第 1 部分)

举报
Yuchuan 发表于 2021/12/21 17:08:33 2021/12/21
【摘要】 您现在知道如何: 用于re.search()在 Python 中执行正则表达式匹配 使用正则表达式元字符创建复杂的模式匹配搜索 使用标志调整正则表达式解析行为 但是您仍然只看到模块中的一个函数:re.search()! 该re模块有更多有用的函数和对象可以添加到您的模式匹配工具包中。本系列的下一个教程将向您介绍 Python 中的 regex 模块必须提供的其他功能。

目录

在本教程中,您将探索正则表达式,也被称为正则表达式,Python编写的。正则表达式是一个特殊的字符序列,它定义了复杂字符串匹配功能的模式。

在本系列前面的教程字符串和字符数据 Python 中,您学习了如何定义和操作字符串对象。从那时起,您已经看到了一些确定两个字符串是否相互匹配的方法:

像这样的字符串匹配是编程中的一项常见任务,您可以使用字符串运算符和内置方法完成很多工作。但有时,您可能需要更复杂的模式匹配功能。

在本教程中,您将学习:

  • 如何访问在 Python 中实现正则表达式匹配的re模块
  • 如何使用re.search()模式匹配字符串
  • 如何使用正则表达式元字符创建复杂的匹配模式

正则表达式语法需要一点时间来适应。但是一旦你习惯了它,你就会发现正则表达式在你的 Python 编程中几乎是必不可少的。

Python 中的正则表达式及其用途

假设您有一个字符串对象s。现在假设您需要编写 Python 代码来确定是否s包含 substring '123'。至少有几种方法可以做到这一点。您可以使用in运算符:

>>>
>>> s = 'foo123bar'
>>> '123' in s
True

如果你想知道,不仅是否 '123'存在s,而且在那里存在,那么你可以使用.find().index()。其中每一个都返回s子字符串所在的字符位置:

>>>
>>> s = 'foo123bar'
>>> s.find('123')
3
>>> s.index('123')
3

在这些示例中,匹配是通过直接的逐字符比较来完成的。这将在许多情况下完成工作。但有时,问题比这更复杂。

例如,而不是寻找一个固定的串像'123',假设你想确定一个字符串是否包含任何三个连续的十进制数字字符,如琴弦'foo123bar''foo456bar''234baz',和'qux678'

严格的字符比较不会在这里解决。这就是 Python 中的正则表达式派上用场的地方。

正则表达式的(非常简短的)历史

1951 年,数学家 Stephen Cole Kleene 描述了正则语言的概念,这种语言可以被有限自动机识别并且可以使用正则表达式形式表达。在 1960 年代中期,计算机科学先驱Ken Thompson(Unix 的最初设计者之一)使用 Kleene 符号在QED 文本编辑器中实现了模式匹配。

从那时起,正则表达式出现在许多编程语言、编辑器和其他工具中,作为确定字符串是否与指定模式匹配的手段。Python、Java和 Perl 都支持正则表达式功能,大多数 Unix 工具和许多文本编辑器也是如此。

re模块

Python 中的正则表达式功能驻留在名为re. 该re模块包含许多有用的函数和方法,您将在本系列的下一个教程中了解其中的大部分。

现在,您将主要关注一项功能,re.search().

re.search(<regex>, <string>)

扫描正则表达式匹配的字符串。

re.search(<regex>, <string>)扫描<string>寻找模式<regex>匹配的第一个位置。如果找到匹配项,则re.search()返回匹配对象。否则,它返回None

re.search()接受可选的第三个<flags>参数,您将在本教程结束时了解该参数。

如何导入 re.search()

因为search()驻留在re模块中,所以需要先导入才可以使用。一种方法是导入整个模块,然后在调用函数时使用模块名称作为前缀:

import re
re.search(...)

或者,您可以按名称从模块导入函数,然后在没有模块名称前缀的情况下引用它:

from re import search
search(...)

re.search()在您能够使用它之前,您总是需要通过一种或另一种方式导入。

本教程剩余部分中的示例将假定显示的第一种方法 - 导入re模块,然后使用模块名称前缀引用函数:re.search()。为简洁起见,该import re语句通常会被省略,但请记住,它始终是必要的。

有关从模块和包导入的更多信息,请查看Python 模块和包—简介

第一个模式匹配示例

现在您知道如何访问re.search(),您可以尝试一下:

>>>
 1>>> s = 'foo123bar'
 2
 3>>> # One last reminder to import!
 4>>> import re
 5
 6>>> re.search('123', s)
 7<_sre.SRE_Match object; span=(3, 6), match='123'>

在这里,搜索模式<regex>123<string>s。返回的匹配对象出现在第 7 行。匹配对象包含大量有用的信息,您很快就会了解这些信息。

目前,重要的一点是re.search()实际上确实返回了一个匹配对象而不是None. 这告诉您它找到了匹配项。换句话说,指定的<regex>模式123存在于 中s

匹配对象是truthy,因此您可以在布尔上下文中使用它,例如条件语句:

>>>
>>> if re.search('123', s):
...     print('Found a match.')
... else:
...     print('No match.')
...
Found a match.

解释器将匹配对象显示为<_sre.SRE_Match object; span=(3, 6), match='123'>。这包含一些有用的信息。

span=(3, 6)表示<string>找到匹配的部分。这与切片表示法中的含义相同:

>>>
>>> s[3:6]
'123'

在此示例中,匹配从字符 position 开始,3一直延伸到但不包括 position 6

match='123'指示<string>匹配的字符。

这是一个好的开始。但在这种情况下,<regex>模式只是普通的 string '123'。这里的模式匹配仍然只是逐个字符的比较,in.find()前面显示的运算符和示例几乎相同。匹配对象有助于告诉您匹配的字符是'123',但这并不是什么启示,因为这些正是您搜索的字符。

你只是在热身。

Python 正则表达式元字符

<regex>包含称为metacharacters 的特殊字符时,Python 中正则表达式匹配的真正威力就显现出来了。这些对正则表达式匹配引擎具有独特的意义,极大地增强了搜索能力。

再考虑如何判断一个字符串是否包含任意三个连续的十进制数字字符的问题。

在正则表达式中,方括号 ( []) 中指定的一组字符组成了一个字符类。此元字符序列匹配类中的任何单个字符,如以下示例所示:

>>>
>>> s = 'foo123bar'
>>> re.search('[0-9][0-9][0-9]', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>

[0-9]匹配任何单个十进制数字字符 -'0'和之间的任何字符'9',包括。完整表达式[0-9][0-9][0-9]匹配三个十进制数字字符的任何序列。在这种情况下,s匹配是因为它包含三个连续的十进制数字字符'123'.

这些字符串也匹配:

>>>
>>> re.search('[0-9][0-9][0-9]', 'foo456bar')
<_sre.SRE_Match object; span=(3, 6), match='456'>

>>> re.search('[0-9][0-9][0-9]', '234baz')
<_sre.SRE_Match object; span=(0, 3), match='234'>

>>> re.search('[0-9][0-9][0-9]', 'qux678')
<_sre.SRE_Match object; span=(3, 6), match='678'>

另一方面,不包含三个连续数字的字符串将不匹配:

>>>
>>> print(re.search('[0-9][0-9][0-9]', '12foo34'))
None

使用 Python 中的正则表达式,您可以识别使用in运算符或字符串方法无法找到的字符串中的模式。

看看另一个正则表达式元字符。点 ( .) 元字符匹配除换行符以外的任何字符,因此它的功能类似于通配符:

>>>
>>> s = 'foo123bar'
>>> re.search('1.3', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>

>>> s = 'foo13bar'
>>> print(re.search('1.3', s))
None

在第一示例中,正则表达式1.3的匹配'123',因为'1''3'字面上匹配,并且.匹配的'2'。在这里,您实际上是在问,“是否s包含 a '1',然后是任何字符(换行符除外),然后是'3'?” 答案是肯定的,'foo123bar'但不是'foo13bar'

这些示例快速说明了正则表达式元字符的威力。字符类和点只是re模块支持的两个元字符。还有更多。接下来,您将全面探索它们。

re模块支持的元字符

下表简要总结了re模块支持的所有元字符。一些字符有多个用途:

Character(s) 意义
. 匹配除换行符以外的任何单个字符
^ ∙ 在字符串的开头锚定匹配项
∙ 补充字符类
$ 将匹配锚定在字符串的末尾
* 匹配零个或多个重复
+ 匹配一个或多个重复
? ∙匹配零个或一个重复
∙的指定非贪婪版本*+?
∙推出一个超前或向后断言
∙创建一个名为组
{} 匹配明确指定的重复次数
\ ∙ 转义具有特殊含义的元字符
∙ 引入特殊字符类
∙ 引入分组反向引用
[] 指定字符类
| 指定交替
() 创建一个组
:
#
=
!
指定专门小组
<> 创建命名组

这似乎是大量的信息,但不要惊慌!以下部分详细介绍了其中的每一个。

正则表达式解析器将上面未列出的任何字符视为仅匹配自身的普通字符。例如,在上面显示的第一个模式匹配示例中,您看到了:

>>>
>>> s = 'foo123bar'
>>> re.search('123', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>

在这种情况下,123技术上是一个正则表达式,但它不是一个非常有趣的,因为它不包含任何元字符。它只匹配字符串'123'

当您将元字符混入其中时,事情会变得更加令人兴奋。以下部分详细解释了如何使用每个元字符或元字符序列来增强模式匹配功能。

匹配单个字符的元字符

本节中的元字符序列尝试匹配搜索字符串中的单个字符。当正则表达式解析器遇到这些元字符序列之一时,如果当前解析位置的字符符合序列描述的描述,就会发生匹配。

[]

指定要匹配的特定字符集。

包含在方括号 ( []) 中的字符代表一个字符类——要匹配的枚举字符集。字符类元字符序列将匹配类中包含的任何单个字符。

您可以像这样单独枚举字符:

>>>
>>> re.search('ba[artz]', 'foobarqux')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> re.search('ba[artz]', 'foobazqux')
<_sre.SRE_Match object; span=(3, 6), match='baz'>

元字符序列[artz]匹配任何一个'a''r''t',或'z'字符。在示例中,正则表达式同时ba[artz]匹配'bar''baz'(并且也匹配'baa''bat')。

字符类还可以包含由连字符 ( -)分隔的一系列字符,在这种情况下,它匹配该范围内的任何单个字符。例如,[a-z]匹配'a'和之间的任何小写字母字符'z',包括:

>>>
>>> re.search('[a-z]', 'FOObar')
<_sre.SRE_Match object; span=(3, 4), match='b'>

[0-9] 匹配任何数字字符:

>>>
>>> re.search('[0-9][0-9]', 'foo123bar')
<_sre.SRE_Match object; span=(3, 5), match='12'>

在这种情况下,[0-9][0-9]匹配两个数字的序列。'foo123bar'匹配的字符串的第一部分是'12'

[0-9a-fA-F]匹配任何十六进制数字字符:

>>>
>>> re.search('[0-9a-fA-f]', '--- a0 ---')
<_sre.SRE_Match object; span=(4, 5), match='a'>

在这里,[0-9a-fA-F]匹配搜索字符串中的第一个十六进制数字字符'a'

注意:在上面的例子中,返回值总是最左边的可能匹配。re.search()从左到右扫描搜索字符串,一旦找到匹配项<regex>,它就会停止扫描并返回匹配项。

您可以通过指定^为第一个字符来补充字符类,在这种情况下,它匹配不在集合中的任何字符。在以下示例中,[^0-9]匹配任何不是数字的字符:

>>>
>>> re.search('[^0-9]', '12345foo')
<_sre.SRE_Match object; span=(5, 6), match='f'>

在这里,匹配对象表示字符串中第一个不是数字的字符是'f'

如果一个^字符出现在字符类中但不是第一个字符,那么它没有特殊含义并且匹配一个文字'^'字符:

>>>
>>> re.search('[#:^]', 'foo^bar:baz#qux')
<_sre.SRE_Match object; span=(3, 4), match='^'>

如您所见,您可以通过用连字符分隔字符来指定字符类中的字符范围。如果您希望字符类包含文字连字符怎么办?您可以将其作为第一个或最后一个字符放置或使用反斜杠 ( \) 将其转义:

>>>
>>> re.search('[-abc]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>
>>> re.search('[abc-]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>
>>> re.search('[ab\-c]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>

如果你想']'在字符类中包含一个文字,那么你可以把它作为第一个字符或者用反斜杠转义它:

>>>
>>> re.search('[]]', 'foo[1]')
<_sre.SRE_Match object; span=(5, 6), match=']'>
>>> re.search('[ab\]cd]', 'foo[1]')
<_sre.SRE_Match object; span=(5, 6), match=']'>

其他正则表达式元字符在字符类中失去了它们的特殊含义:

>>>
>>> re.search('[)*+|]', '123*456')
<_sre.SRE_Match object; span=(3, 4), match='*'>
>>> re.search('[)*+|]', '123+456')
<_sre.SRE_Match object; span=(3, 4), match='+'>

正如你在台锯上方,*+在Python中的正则表达式的特殊含义。它们表示重复,您很快就会了解更多。但是在这个例子中,它们在一个字符类中,所以它们从字面上匹配自己。

点 ( .)

指定通配符。

.元字符匹配除换行符之外的任何单个字符:

>>>
>>> re.search('foo.bar', 'fooxbar')
<_sre.SRE_Match object; span=(0, 7), match='fooxbar'>

>>> print(re.search('foo.bar', 'foobar'))
None
>>> print(re.search('foo.bar', 'foo\nbar'))
None

作为正则表达式,foo.bar本质上是指字符'foo',然后是除换行符以外的任何字符,然后是字符'bar'。上面显示的第一个字符串'fooxbar'符合要求,因为.元字符与'x'.

第二个和第三个字符串不匹配。在最后一种情况下,虽然'foo'和之间有一个字符'bar',但它是一个换行符,并且默认情况下,.元字符不匹配换行符。但是,有一种方法可以强制.匹配换行符,您将在本教程的末尾了解。

\w
\W

根据字符是否为单词字符进行匹配。

\w匹配任何字母数字单词字符。单词字符是大写和小写字母、数字和下划线 ( _) 字符,因此\w本质上是 的简写[a-zA-Z0-9_]

>>>
>>> re.search('\w', '#(.a$@&')
<_sre.SRE_Match object; span=(3, 4), match='a'>
>>> re.search('[a-zA-Z0-9_]', '#(.a$@&')
<_sre.SRE_Match object; span=(3, 4), match='a'>

在这种情况下,字符串中的第一个单词字符'#(.a$@&''a'

\W是相反的。它匹配任何非单词字符,相当于[^a-zA-Z0-9_]

>>>
>>> re.search('\W', 'a_1*3Qb')
<_sre.SRE_Match object; span=(3, 4), match='*'>
>>> re.search('[^a-zA-Z0-9_]', 'a_1*3Qb')
<_sre.SRE_Match object; span=(3, 4), match='*'>

这里,第一个非单词字符'a_1*3!b''*'

\d
\D

根据字符是否为十进制数字进行匹配。

\d匹配任何十进制数字字符。\D是相反的。它匹配任何不是十进制数字的字符:

>>>
>>> re.search('\d', 'abc4def')
<_sre.SRE_Match object; span=(3, 4), match='4'>

>>> re.search('\D', '234Q678')
<_sre.SRE_Match object; span=(3, 4), match='Q'>

\d本质上等价于[0-9],并且\D等价于[^0-9]

\s
\S

根据字符是否代表空格进行匹配。

\s 匹配任何空白字符:

>>>
>>> re.search('\s', 'foo\nbar baz')
<_sre.SRE_Match object; span=(3, 4), match='\n'>

请注意,与点通配符元字符不同,\s它匹配换行符。

\S是相反的\s。它匹配任何空白字符:

>>>
>>> re.search('\S', '  \n foo  \n  ')
<_sre.SRE_Match object; span=(4, 5), match='f'>

同样,\s\S考虑换行是空白。在上面的例子中,第一个非空白字符是'f'.

字符类序列\w\W\d\D\s, 和\S也可以出现在方括号字符类中:

>>>
>>> re.search('[\d\w\s]', '---3---')
<_sre.SRE_Match object; span=(3, 4), match='3'>
>>> re.search('[\d\w\s]', '---a---')
<_sre.SRE_Match object; span=(3, 4), match='a'>
>>> re.search('[\d\w\s]', '--- ---')
<_sre.SRE_Match object; span=(3, 4), match=' '>

在这种情况下,[\d\w\s]匹配任何数字、单词或空白字符。并且由于\w包含\d,相同的字符类也可以表示为[\w\s].

转义元字符

有时,您会希望在正则表达式中包含一个元字符,除非您不希望它带有其特殊含义。相反,您会希望它将自己表示为文字字符。

反斜杠 ( \)

删除元字符的特殊含义。

正如您刚刚看到的,反斜杠字符可以引入特殊字符类,如单词、数字和空格。还有一些特殊的元字符序列称为锚点,它们以反斜杠开头,您将在下面了解。

当它不用于这些目的时,反斜杠会转义元字符。以反斜杠开头的元字符失去了它的特殊含义,而是与文字字符匹配。考虑以下示例:

>>>
 1>>> re.search('.', 'foo.bar')
 2<_sre.SRE_Match object; span=(0, 1), match='f'>
 3
 4>>> re.search('\.', 'foo.bar')
 5<_sre.SRE_Match object; span=(3, 4), match='.'>

<regex>线1,点(.)函数作为通配符元字符,其中,所述第一字符匹配字符串中('f')。该.在人物<regex>线4由反斜线,所以它不是一个通配符。它按字面解释并匹配搜索字符串的'.'at 索引3

使用反斜杠进行转义可能会变得混乱。假设您有一个包含单个反斜杠的字符串:

>>>
>>> s = r'foo\bar'
>>> print(s)
foo\bar

现在假设你想创建一个<regex>将匹配之间的反斜线'foo''bar'。反斜杠本身是正则表达式中的特殊字符,因此要指定文字反斜杠,您需要使用另一个反斜杠对其进行转义。如果是这种情况,那么以下应该有效:

>>>
>>> re.search('\\', s)

不完全的。如果您尝试一下,您会得到以下结果:

>>>
>>> re.search('\\', s)
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    re.search('\\', s)
  File "C:\Python36\lib\re.py", line 182, in search
    return _compile(pattern, flags).search(string)
  File "C:\Python36\lib\re.py", line 301, in _compile
    p = sre_compile.compile(pattern, flags)
  File "C:\Python36\lib\sre_compile.py", line 562, in compile
    p = sre_parse.parse(p, flags)
  File "C:\Python36\lib\sre_parse.py", line 848, in parse
    source = Tokenizer(str)
  File "C:\Python36\lib\sre_parse.py", line 231, in __init__
    self.__next()
  File "C:\Python36\lib\sre_parse.py", line 245, in __next
    self.string, len(self.string) - 1) from None
sre_constants.error: bad escape (end of pattern) at position 0

哎呀。发生了什么?

这里的问题是反斜杠转义发生了两次,首先是字符串文字上的 Python 解释器,然后是它接收的正则表达式上的正则表达式解析器。

这是事件的顺序:

  1. Python 解释器是第一个处理字符串文字的'\\'。它将其解释为转义的反斜杠并仅将单个反斜杠传递给re.search().
  2. 正则表达式解析器只接收一个反斜杠,这不是一个有意义的正则表达式,因此会出现混乱的错误。

有两种方法可以解决这个问题。首先,您可以转义原始字符串文字中的两个反斜杠:

>>>
>>> re.search('\\\\', s)
<_sre.SRE_Match object; span=(3, 4), match='\\'>

这样做会导致以下情况发生:

  1. 解释器将其'\\\\'视为一对转义的反斜杠。它将每一对减少为一个反斜杠并传递'\\'给正则表达式解析器。
  2. 然后正则表达式解析器将其\\视为一个转义的反斜杠。作为<regex>, 匹配单个反斜杠字符。您可以从匹配的对象,它匹配在指数反斜杠看到3s是预期。这很麻烦,但它有效。

第二种可能更简洁的处理方法是指定<regex>使用原始字符串

>>>
>>> re.search(r'\\', s)
<_sre.SRE_Match object; span=(3, 4), match='\\'>

这抑制了解释器级别的转义。字符串'\\'被原封不动地传递给正则表达式解析器,它再次根据需要看到一个转义的反斜杠。

使用原始字符串在 Python 中指定包含反斜杠的正则表达式是一种很好的做法。

Anchors

Anchors 是零宽度匹配。它们不匹配搜索字符串中的任何实际字符,并且在解析过程中不消耗任何搜索字符串。相反,锚指示搜索字符串中必须发生匹配的特定位置。

^
\A

将比赛锚定到 的开头<string>

当正则表达式解析器遇到^or 时\A,解析器的当前位置必须位于搜索字符串的开头才能找到匹配项。

换句话说,正则表达式^foo规定不仅'foo'必须出现在搜索字符串中的任何旧位置,而且必须出现在开头:

>>>
>>> re.search('^foo', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('^foo', 'barfoo'))
None

\A 功能类似:

>>>
>>> re.search('\Afoo', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('\Afoo', 'barfoo'))
None

^并且\AMULTILINE模式上的行为略有不同。您将MULTILINE在以下有关标志的部分中了解有关模式的更多信息。

$
\Z

将比赛锚定到<string>.

当正则表达式解析器遇到$or 时\Z,解析器的当前位置必须位于搜索字符串的末尾才能找到匹配项。无论是前面$还是\Z必须构成搜索字符串的结尾:

>>>
>>> re.search('bar$', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> print(re.search('bar$', 'barfoo'))
None

>>> re.search('bar\Z', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>
>>> print(re.search('bar\Z', 'barfoo'))
None

作为一种特殊情况,$(但不是\Z)也匹配搜索字符串末尾的单个换行符之前:

>>>
>>> re.search('bar$', 'foobar\n')
<_sre.SRE_Match object; span=(3, 6), match='bar'>

在这个例子中,'bar'技术上不是在搜索字符串的末尾,因为它后面跟着一个额外的换行符。但是正则表达式解析器让它滑动并称其为匹配项。此例外不适用于\Z.

$并且\ZMULTILINE模式上的行为略有不同。有关模式的更多信息,请参阅下面有关标志的部分MULTILINE

\b

将匹配锚定到单词边界。

\b断言正则表达式解析器的当前位置必须在单词的开头或结尾。单词由一系列字母数字字符或下划线 ( [a-zA-Z0-9_]) 组成,与\w字符类相同:

>>>
 1>>> re.search(r'\bbar', 'foo bar')
 2<_sre.SRE_Match object; span=(4, 7), match='bar'>
 3>>> re.search(r'\bbar', 'foo.bar')
 4<_sre.SRE_Match object; span=(4, 7), match='bar'>
 5
 6>>> print(re.search(r'\bbar', 'foobar'))
 7None
 8
 9>>> re.search(r'foo\b', 'foo bar')
10<_sre.SRE_Match object; span=(0, 3), match='foo'>
11>>> re.search(r'foo\b', 'foo.bar')
12<_sre.SRE_Match object; span=(0, 3), match='foo'>
13
14>>> print(re.search(r'foo\b', 'foobar'))
15None

在上面的例子中,匹配发生在第 1 行和第 3 行,因为在'bar'第 6 行不是这种情况,因此匹配失败。

类似地,第 9 行和第 11 行存在匹配,因为单词边界存在于 末尾'foo',但不存在于第14 行

当它作为一个完整的词出现在搜索字符串中时,在 的\b两端使用锚<regex>会导致它匹配:

>>>
>>> re.search(r'\bbar\b', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search(r'\bbar\b', 'foo(bar)baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>

>>> print(re.search(r'\bbar\b', 'foobarbaz'))
None

这是另一个需要将 指定<regex>为原始字符串的实例,正如上面的示例所做的那样。

因为'\b'是 Python 中字符串文字和正则表达式的转义序列,所以上面的每次使用都需要双重转义,就'\\b'好像您没有使用原始字符串一样。那不会是世界末日,但原始字符串更整洁。

\B

将匹配锚定到不是单词边界的位置。

\B与 相反\b。它断言正则表达式解析器的当前位置不能在单词的开头或结尾:

>>>
 1>>> print(re.search(r'\Bfoo\B', 'foo'))
 2None
 3>>> print(re.search(r'\Bfoo\B', '.foo.'))
 4None
 5
 6>>> re.search(r'\Bfoo\B', 'barfoobaz')
 7<_sre.SRE_Match object; span=(3, 6), match='foo'>

在这种情况下,匹配发生在第 7 行,因为'foo'在搜索字符串的开头或结尾不存在单词边界'barfoobaz'

量词

一个量词元字符紧跟的一部分<regex>,并表示有多少次必须发生部分的匹配成功。

*

匹配前面正则表达式的零次或多次重复。

例如,a*匹配零个或多个'a'字符。这意味着它将匹配一个空字符串,'a''aa''aaa', 等等。

考虑以下示例:

>>>
 1>>> re.search('foo-*bar', 'foobar')                     # Zero dashes
 2<_sre.SRE_Match object; span=(0, 6), match='foobar'>
 3>>> re.search('foo-*bar', 'foo-bar')                    # One dash
 4<_sre.SRE_Match object; span=(0, 7), match='foo-bar'>
 5>>> re.search('foo-*bar', 'foo--bar')                   # Two dashes
 6<_sre.SRE_Match object; span=(0, 8), match='foo--bar'>

第 1 行,和'-'之间有零个字符。在第 3 行有一个,在第 5 行有两个。元字符序列在所有三种情况下都匹配。'foo''bar'-*

您可能会.*在某个时候在 Python 程序中遇到正则表达式。这匹配零次或多次出现的任何字符。换句话说,它本质上匹配任何字符序列直到换行符。(请记住,.通配符元字符与换行符不匹配。)

在此示例中,.*匹配'foo'和之间的所有内容'bar'

>>>
>>> re.search('foo.*bar', '# foo $qux@grault % bar #')
<_sre.SRE_Match object; span=(2, 23), match='foo $qux@grault % bar'>

你注意到匹配对象中包含的span=match=信息了吗?

到目前为止,您所看到的示例中的正则表达式都指定了可预测长度的匹配项。一旦开始使用诸如 之类*的量词,匹配的字符数就会变化很大,匹配对象中的信息就会变得更加有用。

您将在本系列的下一个教程中了解有关如何访问存储在匹配对象中的信息的更多信息。

+

匹配前面正则表达式的一个或多个重复。

这类似于*,但量化的正则表达式必须至少出现一次:

>>>
 1>>> print(re.search('foo-+bar', 'foobar'))              # Zero dashes
 2None
 3>>> re.search('foo-+bar', 'foo-bar')                    # One dash
 4<_sre.SRE_Match object; span=(0, 7), match='foo-bar'>
 5>>> re.search('foo-+bar', 'foo--bar')                   # Two dashes
 6<_sre.SRE_Match object; span=(0, 8), match='foo--bar'>

请记住上面的foo-*bar匹配字符串,'foobar'因为*元字符允许'-'. 的+元字符,而另一方面,需要中的至少一个发生'-'。这意味着在这种情况下,第 1 行没有匹配项。

?

匹配前面正则表达式的零次或一次重复。

同样,这类似于*and +,但在这种情况下,如果前面的正则表达式出现一次或根本不出现,则只有匹配:

>>>
 1>>> re.search('foo-?bar', 'foobar')                     # Zero dashes
 2<_sre.SRE_Match object; span=(0, 6), match='foobar'>
 3>>> re.search('foo-?bar', 'foo-bar')                    # One dash
 4<_sre.SRE_Match object; span=(0, 7), match='foo-bar'>
 5>>> print(re.search('foo-?bar', 'foo--bar'))            # Two dashes
 6None

在此示例中,第 1 行和第 3 行存在匹配项。但是在第 5 行,其中有两个'-'字符,匹配失败。

以下是更多示例,展示了所有三个量词元字符的使用:

>>>
>>> re.match('foo[1-9]*bar', 'foobar')
<_sre.SRE_Match object; span=(0, 6), match='foobar'>
>>> re.match('foo[1-9]*bar', 'foo42bar')
<_sre.SRE_Match object; span=(0, 8), match='foo42bar'>

>>> print(re.match('foo[1-9]+bar', 'foobar'))
None
>>> re.match('foo[1-9]+bar', 'foo42bar')
<_sre.SRE_Match object; span=(0, 8), match='foo42bar'>

>>> re.match('foo[1-9]?bar', 'foobar')
<_sre.SRE_Match object; span=(0, 6), match='foobar'>
>>> print(re.match('foo[1-9]?bar', 'foo42bar'))
None

这一次,量化的正则表达式是字符类[1-9]而不是简单的字符'-'

*?
+?
??

非贪婪(或懒惰)版本的*+?量词。

单独使用时,量词元字符*+?都是greedy,这意味着它们会产生最长的匹配。考虑这个例子:

>>>
>>> re.search('<.*>', '%<foo> <bar> <baz>%')
<_sre.SRE_Match object; span=(1, 18), match='<foo> <bar> <baz>'>

正则表达式<.*>实际上意味着:

  • 一个'<'字符
  • 然后是任意字符序列
  • 然后一个'>'字符

但哪个'>'角色?有以下三种可能:

  1. 紧随其后的那个 'foo'
  2. 紧随其后的那个 'bar'
  3. 紧随其后的那个 'baz'

由于*元字符是贪婪的,它规定了最长的可能匹配,包括直到并包括后面的'>'字符的所有内容'baz'。您可以从匹配对象中看到这是生成的匹配。

如果你想要最短的匹配,那么使用非贪婪的元字符序列*?

>>>
>>> re.search('<.*?>', '%<foo> <bar> <baz>%')
<_sre.SRE_Match object; span=(1, 6), match='<foo>'>

在这种情况下,匹配以'>'后面的字符结束'foo'

注意:您可以使用 regex 完成同样的事情<[^>]*>,这意味着:

  • 一个'<'字符
  • 然后是除 '>'
  • 然后一个'>'字符

这是某些不支持惰性量词的较旧解析器可用的唯一选项。令人高兴的是,Pythonre模块中的正则表达式解析器并非如此。

+?量词也有惰性版本:

>>>
 1>>> re.search('<.+>', '%<foo> <bar> <baz>%')
 2<_sre.SRE_Match object; span=(1, 18), match='<foo> <bar> <baz>'>
 3>>> re.search('<.+?>', '%<foo> <bar> <baz>%')
 4<_sre.SRE_Match object; span=(1, 6), match='<foo>'>
 5
 6>>> re.search('ba?', 'baaaa')
 7<_sre.SRE_Match object; span=(0, 2), match='ba'>
 8>>> re.search('ba??', 'baaaa')
 9<_sre.SRE_Match object; span=(0, 1), match='b'>

第 1行和第 3 行的前两个示例与上面显示的示例类似,只是使用+and+?代替*and *?

6 行和第 8 行的最后一个示例略有不同。通常,?元字符匹配零次或一次出现的前面的正则表达式。贪婪版本?匹配一次,因此ba?匹配'b'后跟单个'a'. 非贪婪版本??匹配零次出现,因此ba??只匹配'b'

{m}

完全匹配m前面正则表达式的重复。

这类似于*or +,但它准确指定了前面的正则表达式必须出现多少次才能使匹配成功:

>>>
>>> print(re.search('x-{3}x', 'x--x'))                # Two dashes
None

>>> re.search('x-{3}x', 'x---x')                      # Three dashes
<_sre.SRE_Match object; span=(0, 5), match='x---x'>

>>> print(re.search('x-{3}x', 'x----x'))              # Four dashes
None

在这里,x-{3}x匹配'x',后跟正好三个'-'字符的实例,然后是另一个'x'。当字符之间的破折号少于或多于三个时,匹配失败'x'

{m,n}

匹配前面正则表达式从m到 的任意数量的重复n,包括。

在下面的例子中,量化的<regex>-{2,4}。当字符之间有两个、三个或四个破折号时匹配成功,'x'否则失败:

>>>
>>> for i in range(1, 6):
...     s = f"x{'-' * i}x"
...     print(f'{i}  {s:10}', re.search('x-{2,4}x', s))
...
1  x-x        None
2  x--x       <_sre.SRE_Match object; span=(0, 4), match='x--x'>
3  x---x      <_sre.SRE_Match object; span=(0, 5), match='x---x'>
4  x----x     <_sre.SRE_Match object; span=(0, 6), match='x----x'>
5  x-----x    None

省略m意味着 的下限0,而省略n意味着无限的上限:

正则表达式 Matches Examples
<regex>{,n} 任何<regex>小于或等于的重复次数n <regex>{0,n}
<regex>{m,} 任何<regex>大于或等于的重复次数m ----
<regex>{,} 任意次数的重复 <regex> <regex>{0,}
<regex>*

如果省略所有的mn和逗号,然后在大括号不再功能的元字符。{}只匹配文字字符串'{}'

>>>
>>> re.search('x{}y', 'x{}y')
<_sre.SRE_Match object; span=(0, 4), match='x{}y'>

实际上,要具有任何特殊含义,带花括号的序列必须符合以下模式之一,其中mn是非负整数:

  • {m,n}
  • {m,}
  • {,n}
  • {,}

否则,它按字面匹配:

>>>
>>> re.search('x{foo}y', 'x{foo}y')
<_sre.SRE_Match object; span=(0, 7), match='x{foo}y'>
>>> re.search('x{a:b}y', 'x{a:b}y')
<_sre.SRE_Match object; span=(0, 7), match='x{a:b}y'>
>>> re.search('x{1,3,5}y', 'x{1,3,5}y')
<_sre.SRE_Match object; span=(0, 9), match='x{1,3,5}y'>
>>> re.search('x{foo,bar}y', 'x{foo,bar}y')
<_sre.SRE_Match object; span=(0, 11), match='x{foo,bar}y'>

在本教程的后面,当您了解DEBUG标志时,您将看到如何确认这一点。

{m,n}?

的非贪婪(懒惰)版本{m,n}

{m,n}将匹配尽可能多的字符,{m,n}?并将匹配尽可能少的字符:

>>>
>>> re.search('a{3,5}', 'aaaaaaaa')
<_sre.SRE_Match object; span=(0, 5), match='aaaaa'>

>>> re.search('a{3,5}?', 'aaaaaaaa')
<_sre.SRE_Match object; span=(0, 3), match='aaa'>

在这种情况下,a{3,5}产生最长的匹配,所以它匹配五个'a'字符。a{3,5}?产生最短的匹配,所以它匹配三个。

分组结构和反向引用

分组构造将 Python 中的正则表达式分解为子表达式或组。这有两个目的:

  1. 分组:一个组代表一个单一的句法实体。附加元字符作为一个单元应用于整个组。
  2. 捕获:某些分组构造还捕获与组中的子表达式匹配的搜索字符串部分。您可以稍后通过几种不同的机制检索捕获的匹配项。

下面来看看分组和捕获的工作原理。

(<regex>)

定义子表达式或组。

这是最基本的分组结构。括号中的正则表达式只匹配括号的内容:

>>>
>>> re.search('(bar)', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>

>>> re.search('bar', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>

作为正则表达式,(bar)匹配 string 'bar',与bar没有括号的正则表达式相同。

将组视为一个单元

组后面的量词元字符对组中指定的整个子表达式作为一个单元进行操作。

例如,以下示例匹配一个或多个出现的字符串'bar'

>>>
>>> re.search('(bar)+', 'foo bar baz')
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('(bar)+', 'foo barbar baz')
<_sre.SRE_Match object; span=(4, 10), match='barbar'>
>>> re.search('(bar)+', 'foo barbarbarbar baz')
<_sre.SRE_Match object; span=(4, 16), match='barbarbarbar'>

以下是带括号和不带括号的两个正则表达式之间差异的细分:

正则表达式 解释 Matches 例子
bar+ +元字符只适用于字符'r' 'ba' 紧接着出现一次或多次 'r' 'bar'
'barr'
'barrr'
(bar)+ +元字符适用于整个字符串'bar' 一次或多次出现 'bar' 'bar'
'barbar'
'barbarbar'

现在看一个更复杂的例子。正则表达式(ba[rz]){2,4}(qux)?匹配24的出现无论是'bar''baz',任选地随后'qux'

>>>
>>> re.search('(ba[rz]){2,4}(qux)?', 'bazbarbazqux')
<_sre.SRE_Match object; span=(0, 12), match='bazbarbazqux'>
>>> re.search('(ba[rz]){2,4}(qux)?', 'barbar')
<_sre.SRE_Match object; span=(0, 6), match='barbar'>

以下示例显示您可以嵌套分组括号:

>>>
>>> re.search('(foo(bar)?)+(\d\d\d)?', 'foofoobar')
<_sre.SRE_Match object; span=(0, 9), match='foofoobar'>
>>> re.search('(foo(bar)?)+(\d\d\d)?', 'foofoobar123')
<_sre.SRE_Match object; span=(0, 12), match='foofoobar123'>
>>> re.search('(foo(bar)?)+(\d\d\d)?', 'foofoo123')
<_sre.SRE_Match object; span=(0, 9), match='foofoo123'>

正则表达式(foo(bar)?)+(\d\d\d)?非常复杂,所以让我们把它分解成更小的部分:

正则表达式 Matches
foo(bar)? 'foo' 可选地跟随 'bar'
(foo(bar)?)+ 上述情况的一种或多种
\d\d\d 三位十进制数字字符
(\d\d\d)? 上述情况发生零次或一次

将它们串在一起,您会得到: 至少出现一次'foo'可选后跟'bar',所有可选后跟三个十进制数字字符。

如您所见,您可以使用分组括号在 Python 中构造非常复杂的正则表达式。

捕获组

分组并不是分组构造所服务的唯一有用目的。大多数(但不是全部)分组构造还捕获与组匹配的搜索字符串部分。您可以检索捕获的部分或稍后以几种不同的方式引用它。

还记得re.search()返回的匹配对象吗?为匹配对象定义了两种方法来提供对捕获组的访问:.groups().group().

m.groups()

返回一个包含正则表达式匹配中所有捕获组的元组。

考虑这个例子:

>>>
>>> m = re.search('(\w+),(\w+),(\w+)', 'foo,quux,baz')
>>> m
<_sre.SRE_Match object; span=(0, 12), match='foo:quux:baz'>

三个(\w+)表达式中的每一个都匹配一个单词字符序列。完整的正则表达式(\w+),(\w+),(\w+)将搜索字符串分成三个以逗号分隔的标记。

由于(\w+)表达式使用分组括号,因此会捕获相应的匹配标记。要访问捕获的匹配项,您可以使用.groups(),它会按顺序返回一个包含所有捕获的匹配项的元组

>>>
>>> m.groups()
('foo', 'quux', 'baz')

请注意,元组包含标记但不包含出现在搜索字符串中的逗号。这是因为组成标记的单词字符在分组括号内,但逗号不在。您在返回的标记之间看到的逗号是用于分隔元组中的值的标准分隔符。

m.group(<n>)

返回一个包含<n>th捕获匹配的字符串。

使用一个参数,.group()返回单个捕获的匹配项。请注意,参数是从一开始的,而不是从零开始的。因此,m.group(1)指的是第一个捕获的匹配项,m.group(2)第二个,依此类推:

>>>
>>> m = re.search('(\w+),(\w+),(\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')

>>> m.group(1)
'foo'
>>> m.group(2)
'quux'
>>> m.group(3)
'baz'

由于捕获的匹配项的编号是从 1 开始的,并且没有任何编号为 0 的组,m.group(0)因此具有特殊含义:

>>>
>>> m.group(0)
'foo,quux,baz'
>>> m.group()
'foo,quux,baz'

m.group(0)返回整个匹配项,并m.group()执行相同的操作。

m.group(<n1>, <n2>, ...)

返回包含指定捕获匹配项的元组。

使用多个参数,.group()返回包含按给定顺序指定的捕获匹配项的元组:

>>>
>>> m.groups()
('foo', 'quux', 'baz')

>>> m.group(2, 3)
('quux', 'baz')
>>> m.group(3, 2, 1)
('baz', 'quux', 'foo')

这只是方便的速记。您可以自己创建匹配元组:

>>>
>>> m.group(3, 2, 1)
('baz', 'qux', 'foo')
>>> (m.group(3), m.group(2), m.group(1))
('baz', 'qux', 'foo')

所示的两个语句在功能上是等效的。

反向引用

您可以稍后使用称为反向引用的特殊元字符序列在同一正则表达式中匹配先前捕获的组。

\<n>

匹配先前捕获的组的内容。

内在Python一个正则表达式,所述序列\<n>,其中<n>是从整数199,匹配的内容<n>th捕获基团。

这是一个匹配一个单词的正则表达式,后跟一个逗号,再后跟同一个单词:

>>>
 1>>> regex = r'(\w+),\1'
 2
 3>>> m = re.search(regex, 'foo,foo')
 4>>> m
 5<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
 6>>> m.group(1)
 7'foo'
 8
 9>>> m = re.search(regex, 'qux,qux')
10>>> m
11<_sre.SRE_Match object; span=(0, 7), match='qux,qux'>
12>>> m.group(1)
13'qux'
14
15>>> m = re.search(regex, 'foo,qux')
16>>> print(m)
17None

在第一个示例中,在第 3 行(\w+)匹配字符串的第一个实例'foo'并将其保存为第一个捕获的组。逗号字面匹配。然后\1是对第一个捕获组的反向引用并'foo'再次匹配。第二个例子,在第 9 行,除了(\w+)匹配之外是相同的'qux'

最后一个示例,在第 15 行,没有匹配,因为逗号之前的内容与其之后的内容不同,因此\1反向引用不匹配。

注意:任何时候在 Python 中使用带有编号反向引用的正则表达式时,最好将其指定为原始字符串。否则,解释器可能会将反向引用与八进制值混淆。

考虑这个例子:

>>>
>>> print(re.search('([a-z])#\1', 'd#d'))
None

正则表达式([a-z])#\1匹配一个小写字母,后跟'#',后跟相同的小写字母。在这种情况下的字符串是'd#d',应该匹配。但是匹配失败,因为 Python 将反向引用误解\1为八进制值为 1 的字符:

>>>
>>> oct(ord('\1'))
'0o1'

如果您将正则表达式指定为原始字符串,您将获得正确的匹配:

>>>
>>> re.search(r'([a-z])#\1', 'd#d')
<_sre.SRE_Match object; span=(0, 3), match='d#d'>

当您的正则表达式包含包含反斜杠的元字符序列时,请记住考虑使用原始字符串。

编号的反向引用是基于一个的,就像 的参数一样.group()。只能通过反向引用访问前九十九个捕获的组。解释器将其\100视为'@'八进制值为 100的字符。

其他分组结构

(<regex>)上面显示的元字符序列是在 Python 中的正则表达式中执行分组的最直接方式。下一节将向您介绍一些增强的分组结构,允许您调整分组发生的时间和方式。

(?P<name><regex>)

创建一个命名的捕获组。

这个元字符序列类似于分组括号,因为它创建了一个组匹配<regex>,可以通过匹配对象或后续的反向引用访问。在这种情况下,不同之处在于您通过其给定的符号<name>而不是其编号来引用匹配的组。

此前,您看到这个例子有三个捕获组编号12以及3

>>>
>>> m = re.search('(\w+),(\w+),(\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')

>>> m.group(1, 2, 3)
('foo', 'quux', 'baz')

下有效地做同样的事情除了组具有象征性的名称w1w2以及w3

>>>
>>> m = re.search('(?P<w1>\w+),(?P<w2>\w+),(?P<w3>\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'quux', 'baz')

您可以通过符号名称引用这些捕获的组:

>>>
>>> m.group('w1')
'foo'
>>> m.group('w3')
'baz'
>>> m.group('w1', 'w2', 'w3')
('foo', 'quux', 'baz')

如果您愿意,您仍然可以通过数字访问具有符号名称的组:

>>>
>>> m = re.search('(?P<w1>\w+),(?P<w2>\w+),(?P<w3>\w+)', 'foo,quux,baz')

>>> m.group('w1')
'foo'
>>> m.group(1)
'foo'

>>> m.group('w1', 'w2', 'w3')
('foo', 'quux', 'baz')
>>> m.group(1, 2, 3)
('foo', 'quux', 'baz')

<name>使用此构造指定的任何内容都必须符合Python identifier的规则,并且每个<name>正则表达式只能出现一次。

(?P=<name>)

匹配先前捕获的命名组的内容。

(?P=<name>)元字符序列是反向引用,类似\<n>,不同之处在于它是指一个命名的基团而不是一个编号的组。

这里还是上面的例子,它使用编号的反向引用来匹配一个单词,后跟一个逗号,然后再跟同一个单词:

>>>
>>> m = re.search(r'(\w+),\1', 'foo,foo')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
>>> m.group(1)
'foo'

以下代码使用命名组和反向引用执行相同的操作:

>>>
>>> m = re.search(r'(?P<word>\w+),(?P=word)', 'foo,foo')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='foo,foo'>
>>> m.group('word')
'foo'

(?P=<word>\w+)匹配'foo'并将其保存为一个名为 的捕获组word。同样,逗号字面匹配。然后(?P=word)是对命名捕获的反向引用并'foo'再次匹配。

注意:在创建命名组时需要尖括号(<>),name但在稍后通过反向引用或通过引用它时不需要.group()

>>>
>>> m = re.match(r'(?P<num>\d+)\.(?P=num)', '135.135')
>>> m
<_sre.SRE_Match object; span=(0, 7), match='135.135'>

>>> m.group('num')
'135'

在这里,创建捕获的组。但是相应的反向引用没有尖括号。(?P<num>\d+)(?P=num)

(?:<regex>)

创建一个非捕获组。

(?:<regex>)就像(<regex>)它匹配指定的<regex>. 但(?:<regex>)不捕获匹配供以后检索:

>>>
>>> m = re.search('(\w+),(?:\w+),(\w+)', 'foo,quux,baz')
>>> m.groups()
('foo', 'baz')

>>> m.group(1)
'foo'
>>> m.group(2)
'baz'

在这个例子中,中间词'quux'位于非捕获括号内,因此它从捕获组的元组中丢失。它不能从匹配对象中检索,也不能通过反向引用进行引用。

为什么要定义一个组而不捕获它?

请记住,正则表达式解析器会将<regex>内部分组括号视为一个单元。您可能会遇到需要此分组功能的情况,但您稍后不需要对值执行任何操作,因此您实际上并不需要捕获它。如果您使用非捕获分组,则捕获组的元组不会被您实际上不需要保留的值弄得乱七八糟。

此外,捕获一组需要一些时间和内存。如果执行匹配的代码执行多次并且您没有捕获以后不打算使用的组,那么您可能会看到轻微的性能优势。

(?(<n>)<yes-regex>|<no-regex>)
(?(<name>)<yes-regex>|<no-regex>)

指定条件匹配。

根据给定组是否存在,条件匹配匹配两个指定的正则表达式之一:

  • (?(<n>)<yes-regex>|<no-regex>)<yes-regex>如果<n>存在编号的组,则匹配。否则,它与 匹配<no-regex>

  • (?(<name>)<yes-regex>|<no-regex>)<yes-regex>如果<name>存在名为的组,则匹配。否则,它与 匹配<no-regex>

用一个例子更好地说明条件匹配。考虑这个正则表达式:

regex = r'^(###)?foo(?(1)bar|baz)'

以下是此正则表达式的部分内容,并附有一些解释:

  1. ^(###)?表示搜索字符串可选地以'###'. 如果是,则周围的分组括号###将创建一个编号为 的组1。否则,将不存在这样的组。
  2. 下一部分 ,foo字面上匹配字符串'foo'
  3. 最后,(?(1)bar|baz)匹配'bar'是否1存在组,'baz'如果不存在。

以下代码块演示了在几个不同的 Python 代码片段中使用上述正则表达式:

示例 1:

>>>
>>> re.search(regex, '###foobar')
<_sre.SRE_Match object; span=(0, 9), match='###foobar'>

搜索字符串'###foobar'确实以 开头'###',因此解析器会创建一个编号为 的组1。条件匹配然后是对'bar',它匹配。

示例 2:

>>>
>>> print(re.search(regex, '###foobaz'))
None

搜索字符串'###foobaz'确实以 开头'###',因此解析器会创建一个编号为 的组1。条件匹配然后是对'bar',它不匹配。

示例 3:

>>>
>>> print(re.search(regex, 'foobar'))
None

搜索字符串'foobar'不以 开头'###',因此没有编号为 的组1。条件匹配然后是对'baz',它不匹配。

示例 4:

>>>
>>> re.search(regex, 'foobaz')
<_sre.SRE_Match object; span=(0, 6), match='foobaz'>

搜索字符串'foobaz'不以 开头'###',因此没有编号为 的组1。条件匹配然后是对'baz',它匹配。

这是另一个使用命名组而不是编号组的条件匹配:

>>>
>>> regex = r'^(?P<ch>\W)?foo(?(ch)(?P=ch)|)$'

此正则表达式匹配 string 'foo',前面是单个非单词字符,后跟相同的非单词字符,或字符串'foo'本身。

再一次,让我们把它分解成几部分:

正则表达式 Matches
^ 字符串的开始
(?P<ch>\W) 单个非单词字符,在名为的组中捕获 ch
(?P<ch>\W)? 上述情况发生零次或一次
foo 文字串 'foo'
(?(ch)(?P=ch)|) ch如果存在则命名组的内容,如果不存在则为空字符串
$ 字符串的结尾

如果非单词字符位于 之前'foo',则解析器会创建一个名为的组ch,其中包含该字符。条件匹配然后匹配反对<yes-regex>,即(?P=ch),再次相同的字符。这意味着也必须遵循相同的字符才能'foo'使整个匹配成功。

如果'foo'前面没有非单词字符,则解析器不会创建 group ch<no-regex>是空字符串,这意味着'foo'整个匹配必须没有任何后续内容才能成功。由于^$锚定整个正则表达式,字符串必须'foo'完全相等。

以下是在 Python 代码中使用此正则表达式的一些搜索示例:

>>>
 1>>> re.search(regex, 'foo')
 2<_sre.SRE_Match object; span=(0, 3), match='foo'>
 3>>> re.search(regex, '#foo#')
 4<_sre.SRE_Match object; span=(0, 5), match='#foo#'>
 5>>> re.search(regex, '@foo@')
 6<_sre.SRE_Match object; span=(0, 5), match='@foo@'>
 7
 8>>> print(re.search(regex, '#foo'))
 9None
10>>> print(re.search(regex, 'foo@'))
11None
12>>> print(re.search(regex, '#foo@'))
13None
14>>> print(re.search(regex, '@foo#'))
15None

第 1 行'foo'它本身就是。在第 3 行和第 5 行,相同的非单词字符前后'foo'。正如宣传的那样,这些匹配成功了。

在其余情况下,匹配失败。

Python 中的条件正则表达式非常深奥且难以处理。如果您确实找到了使用一个的理由,那么您可能可以通过多个单独的re.search()调用来实现相同的目标,并且您的代码阅读和理解起来不会那么复杂。

Lookahead and Lookbehind Assertions

LookaheadLookbehind断言根据解析器在搜索字符串中的当前位置的后面(左侧)或前面(右侧)来确定 Python 中正则表达式匹配的成功或失败。

像锚点一样,前瞻和后视断言是零宽度断言,因此它们不消耗任何搜索字符串。此外,即使它们包含括号并执行分组,它们也不会捕获它们匹配的内容。

(?=<lookahead_regex>)

创建一个积极的前瞻断言。

(?=<lookahead_regex>)断言正则表达式解析器当前位置后面的内容必须匹配<lookahead_regex>

>>>
>>> re.search('foo(?=[a-z])', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>

前瞻断言(?=[a-z])指定后面的内容'foo'必须是小写字母字符。在这种情况下,它是字符'b',因此找到了匹配项。

另一方面,在下一个示例中,前瞻失败。之后的下一个字符'foo''1',所以没有匹配:

>>>
>>> print(re.search('foo(?=[a-z])', 'foo123'))
None

前瞻的独特之处<lookahead_regex>在于不消耗匹配的搜索字符串部分,并且它不是返回的匹配对象的一部分。

再看第一个例子:

>>>
>>> re.search('foo(?=[a-z])', 'foobar')
<_sre.SRE_Match object; span=(0, 3), match='foo'>

正则表达式解析器展望只到'b'后面'foo',但在它没有通过呢。您可以判断这'b'不被视为匹配的一部分,因为匹配对象显示match='foo'.

将其与使用分组括号而没有前瞻的类似示例进行比较:

>>>
>>> re.search('foo([a-z])', 'foobar')
<_sre.SRE_Match object; span=(0, 4), match='foob'>

这一次,正则表达式消耗了'b',它成为最终匹配的一部分。

这是另一个示例,说明前瞻与 Python 中的传统正则表达式有何不同:

>>>
 1>>> m = re.search('foo(?=[a-z])(?P<ch>.)', 'foobar')
 2>>> m.group('ch')
 3'b'
 4
 5>>> m = re.search('foo([a-z])(?P<ch>.)', 'foobar')
 6>>> m.group('ch')
 7'a'

在第一次搜索中,在第 1 行,解析器按如下方式进行:

  1. 正则表达式的第一部分foo, 匹配并'foo'使用搜索字符串'foobar'
  2. 下一部分(?=[a-z])是匹配 的前瞻'b',但解析器不会前进到'b'.
  3. 最后,(?P<ch>.)匹配下一个可用的单个字符,即'b',并将其捕获到名为 的组中ch

m.group('ch')调用确认名为的组ch包含'b'

将其与第5 行的搜索进行比较,后者不包含前瞻:

  1. 与第一个示例一样,正则表达式的第一部分foo, 匹配并'foo'使用搜索字符串'foobar'
  2. 下一部分 ,([a-z])匹配并消耗'b',解析器前进过去'b'
  3. 最后,(?P<ch>.)匹配下一个可用的单个字符,现在是'a'.

m.group('ch')确认,在这种情况下,名为的组ch包含'a'

(?!<lookahead_regex>)

创建一个否定的前瞻断言。

(?!<lookahead_regex>)断言正则表达式解析器当前位置后面的内容不能匹配<lookahead_regex>

以下是您之前看到的正面预测示例,以及它们的负面预测示例:

>>>
 1>>> re.search('foo(?=[a-z])', 'foobar')
 2<_sre.SRE_Match object; span=(0, 3), match='foo'>
 3>>> print(re.search('foo(?![a-z])', 'foobar'))
 4None
 5
 6>>> print(re.search('foo(?=[a-z])', 'foo123'))
 7None
 8>>> re.search('foo(?![a-z])', 'foo123')
 9<_sre.SRE_Match object; span=(0, 3), match='foo'>

3 行和第 8 行的否定前瞻断言规定后面的内容'foo'不应是小写字母字符。这在第 3 行失败,但在第 8成功。这与相应的正向前瞻断言发生的情况相反。

与正前瞻一样,与负前瞻匹配的内容不是返回的匹配对象的一部分,也不会被消耗。

(?<=<lookbehind_regex>)

创建一个积极的回顾断言。

(?<=<lookbehind_regex>)断言正则表达式解析器当前位置之前的内容必须匹配<lookbehind_regex>

在以下示例中,lookbehind 断言指定'foo'必须先于'bar'

>>>
>>> re.search('(?<=foo)bar', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>

这是这里的情况,所以匹配成功。与前瞻断言一样,与后视匹配的搜索字符串部分不会成为最终匹配的一部分。

下一个示例无法匹配,因为后视需要'qux'先于'bar'

>>>
>>> print(re.search('(?<=qux)bar', 'foobar'))
None

后视断言有一个限制,不适用于前瞻断言。将<lookbehind_regex>在向后断言必须指定一个匹配的固定长度。

例如,以下是不允许的,因为匹配的字符串的长度a+是不确定的:

>>>
>>> re.search('(?<=a+)def', 'aaadef')
Traceback (most recent call last):
  File "<pyshell#72>", line 1, in <module>
    re.search('(?<=a+)def', 'aaadef')
  File "C:\Python36\lib\re.py", line 182, in search
    return _compile(pattern, flags).search(string)
  File "C:\Python36\lib\re.py", line 301, in _compile
    p = sre_compile.compile(pattern, flags)
  File "C:\Python36\lib\sre_compile.py", line 566, in compile
    code = _code(p, flags)
  File "C:\Python36\lib\sre_compile.py", line 551, in _code
    _compile(code, p.data, flags)
  File "C:\Python36\lib\sre_compile.py", line 160, in _compile
    raise error("look-behind requires fixed-width pattern")
sre_constants.error: look-behind requires fixed-width pattern

但是,这没问题:

>>>
>>> re.search('(?<=a{3})def', 'aaadef')
<_sre.SRE_Match object; span=(3, 6), match='def'>

任何匹配的a{3}都将有一个固定长度为 3,因此a{3}在后视断言中是有效的。

(?<!--<lookbehind_regex-->)

创建否定的回顾断言。

(?<!--<lookbehind_regex-->)断言正则表达式解析器当前位置之前的内容必须匹配<lookbehind_regex>

>>>
>>> print(re.search('(?<!foo)bar', 'foobar'))
None

>>> re.search('(?<!qux)bar', 'foobar')
<_sre.SRE_Match object; span=(3, 6), match='bar'>

与正向后视断言一样,<lookbehind_regex>必须指定一个固定长度的匹配项。

杂项元字符

还有更多的元字符序列要涵盖。这些是杂散的元字符,显然不属于已经讨论过的任何类别。

(?#...)

指定注释。

正则表达式解析器会忽略序列中包含的任何内容(?#...)

>>>
>>> re.search('bar(?#This is a comment) *baz', 'foo bar baz qux')
<_sre.SRE_Match object; span=(4, 11), match='bar baz'>

这允许您在 Python 中的正则表达式中指定文档,如果正则表达式特别长,这将特别有用。

竖条或管道 ( |)

指定要匹配的一组备选方案。

以下形式的表达式至多与指定的表达式之一匹配:<regex1>|<regex2>|...|<regexn><regexi>

>>>
>>> re.search('foo|bar|baz', 'bar')
<_sre.SRE_Match object; span=(0, 3), match='bar'>

>>> re.search('foo|bar|baz', 'baz')
<_sre.SRE_Match object; span=(0, 3), match='baz'>

>>> print(re.search('foo|bar|baz', 'quux'))
None

在这里,foo|bar|baz将匹配任何的'foo''bar''baz'。您可以使用|.

交替是非贪婪的。正则表达式解析器按|从左到右的顺序查看由 分隔的表达式,并返回它找到的第一个匹配项。其余的表达式不会被测试,即使其中一个会产生更长的匹配:

>>>
 1>>> re.search('foo', 'foograult')
 2<_sre.SRE_Match object; span=(0, 3), match='foo'>
 3>>> re.search('grault', 'foograult')
 4<_sre.SRE_Match object; span=(3, 9), match='grault'>
 5
 6>>> re.search('foo|grault', 'foograult')
 7<_sre.SRE_Match object; span=(0, 3), match='foo'>

在这种情况下,第6 行,上指定的模式'foo|grault'将匹配'foo''grault'。返回的匹配是'foo'因为它在从左到右扫描时首先出现,即使'grault'是更长的匹配。

您可以组合交替、分组和任何其他元字符来实现您需要的任何复杂程度。在以下示例中,(foo|bar|baz)+表示一个或多个字符串'foo''bar'、 或的序列'baz'

>>>
>>> re.search('(foo|bar|baz)+', 'foofoofoo')
<_sre.SRE_Match object; span=(0, 9), match='foofoofoo'>
>>> re.search('(foo|bar|baz)+', 'bazbazbazbaz')
<_sre.SRE_Match object; span=(0, 12), match='bazbazbazbaz'>
>>> re.search('(foo|bar|baz)+', 'barbazfoo')
<_sre.SRE_Match object; span=(0, 9), match='barbazfoo'>

在下一个示例中,([0-9]+|[a-f]+)表示一个或多个十进制数字字符的序列或一个或多个字符的序列'a-f'

>>>
>>> re.search('([0-9]+|[a-f]+)', '456')
<_sre.SRE_Match object; span=(0, 3), match='456'>
>>> re.search('([0-9]+|[a-f]+)', 'ffda')
<_sre.SRE_Match object; span=(0, 4), match='ffda'>

对于re模块支持的所有元字符,天空实际上是极限。

就是这样,伙计们!

这完成了我们对 Pythonre模块支持的正则表达式元字符的浏览。(实际上,情况并非如此——您将在下面关于标志的讨论中了解更多的落后者。)

需要消化的内容很多,但是一旦您熟悉了 Python 中的正则表达式语法,您可以执行的模式匹配的复杂性几乎是无限的。当您编写代码来处理文本数据时,这些工具会派上用场。

如果您不熟悉正则表达式并希望更多地练习使用它们,或者如果您正在开发使用正则表达式的应用程序并希望以交互方式对其进行测试,请查看正则表达式 101网站。真的很酷!

修改后的正则表达式匹配标志

re模块中的大多数函数都采用可选<flags>参数。这包括您现在非常熟悉的功能,re.search().

re.search(<regex>, <string>, <flags>)

扫描字符串以查找正则表达式匹配,应用指定的修饰符<flags>

标志修改正则表达式解析行为,允许您进一步优化模式匹配。

支持的正则表达式标志

下表简要总结了可用的标志。除了re.DEBUG有一个简短的单字母名称和一个较长的全字名称之外的所有标志:

简称 长名称 Effect
re.I re.IGNORECASE 使字母字符的匹配不区分大小写
re.M re.MULTILINE 导致字符串开头和字符串结尾锚匹配嵌入的换行符
re.S re.DOTALL 使点元字符匹配换行符
re.X re.VERBOSE 允许在正则表达式中包含空格和注释
---- re.DEBUG 使正则表达式解析器向控制台显示调试信息
re.A re.ASCII 指定字符分类的 ASCII 编码
re.U re.UNICODE 为字符分类指定 Unicode 编码
re.L                             re.LOCALE 指定基于当前语言环境的字符分类编码

以下部分更详细地描述了这些标志如何影响匹配行为。

re.I
re.IGNORECASE

使匹配不区分大小写。

IGNORECASE有效时,字符匹配是不区分大小写的:

>>>
 1>>> re.search('a+', 'aaaAAA')
 2<_sre.SRE_Match object; span=(0, 3), match='aaa'>
 3>>> re.search('A+', 'aaaAAA')
 4<_sre.SRE_Match object; span=(3, 6), match='AAA'>
 5
 6>>> re.search('a+', 'aaaAAA', re.I)
 7<_sre.SRE_Match object; span=(0, 6), match='aaaAAA'>
 8>>> re.search('A+', 'aaaAAA', re.IGNORECASE)
 9<_sre.SRE_Match object; span=(0, 6), match='aaaAAA'>

在第1 行的搜索中,a+只匹配 的前三个字符'aaaAAA'。同样,在第 3 行A+只匹配最后三个字符。但在随后的搜查中,分析器忽略的情况下,这样既a+A+整个字符串相匹配。

IGNORECASE 也会影响涉及字符类的字母匹配:

>>>
>>> re.search('[a-z]+', 'aBcDeF')
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> re.search('[a-z]+', 'aBcDeF', re.I)
<_sre.SRE_Match object; span=(0, 6), match='aBcDeF'>

当情况显著,最长部分'aBcDeF'[a-z]+场比赛是刚刚起步'a'。指定re.I使搜索不区分大小写,因此[a-z]+匹配整个字符串。

re.M
re.MULTILINE

导致字符串开头和字符串结尾锚在嵌入的换行符处匹配。

默认情况下,^(start-of-string) 和$(end-of-string) 锚只匹配搜索字符串的开头和结尾:

>>>
>>> s = 'foo\nbar\nbaz'

>>> re.search('^foo', s)
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> print(re.search('^bar', s))
None
>>> print(re.search('^baz', s))
None

>>> print(re.search('foo$', s))
None
>>> print(re.search('bar$', s))
None
>>> re.search('baz$', s)
<_sre.SRE_Match object; span=(8, 11), match='baz'>

在这种情况下,即使搜索字符串'foo\nbar\nbaz'包含嵌入的换行符,也仅'foo'在锚定在字符串开头'baz'时匹配,并且仅在锚定在结尾时匹配。

但是,如果字符串嵌入了换行符,您可以将其视为由多个内部行组成。在这种情况下,如果MULTILINE设置了标志,则^$锚元字符也匹配内部行:

  • ^ 匹配字符串的开头或字符串中任何行的开头(即,紧跟在换行符之后)。
  • $ 匹配字符串的末尾或字符串中任何行的末尾(紧接在换行符之前)。

以下是与上图相同的搜索:

>>>
>>> s = 'foo\nbar\nbaz'
>>> print(s)
foo
bar
baz

>>> re.search('^foo', s, re.MULTILINE)
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> re.search('^bar', s, re.MULTILINE)
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('^baz', s, re.MULTILINE)
<_sre.SRE_Match object; span=(8, 11), match='baz'>

>>> re.search('foo$', s, re.M)
<_sre.SRE_Match object; span=(0, 3), match='foo'>
>>> re.search('bar$', s, re.M)
<_sre.SRE_Match object; span=(4, 7), match='bar'>
>>> re.search('baz$', s, re.M)
<_sre.SRE_Match object; span=(8, 11), match='baz'>

在字符串'foo\nbar\nbaz''foo''bar'、 和 中的所有三个都'baz'出现在字符串的开头或结尾,或者出现在字符串中一行的开头或结尾。随着MULTILINE标志设置,当要么挂靠三项都符合^$

注:MULTILINE标志仅修改了^$锚以这种方式。它对\A\Z锚点没有任何影响:

>>>
 1>>> s = 'foo\nbar\nbaz'
 2
 3>>> re.search('^bar', s, re.MULTILINE)
 4<_sre.SRE_Match object; span=(4, 7), match='bar'>
 5>>> re.search('bar$', s, re.MULTILINE)
 6<_sre.SRE_Match object; span=(4, 7), match='bar'>
 7
 8>>> print(re.search('\Abar', s, re.MULTILINE))
 9None
10>>> print(re.search('bar\Z', s, re.MULTILINE))
11None

第 3 行和第 5 行,锚点^$锚指示'bar'必须在一行的开头和结尾处找到。指定MULTILINE标志使这些匹配成功。

8 行和第 10 行的示例使用\A\Z标志代替。您可以看到即使MULTILINE标志有效,这些匹配也会失败。

re.S
re.DOTALL

使点 ( .) 元字符匹配换行符。

请记住,默认情况下,点元字符匹配除换行符之外的任何字符。该DOTALL标志解除了此限制:

>>>
 1>>> print(re.search('foo.bar', 'foo\nbar'))
 2None
 3>>> re.search('foo.bar', 'foo\nbar', re.DOTALL)
 4<_sre.SRE_Match object; span=(0, 7), match='foo\nbar'>
 5>>> re.search('foo.bar', 'foo\nbar', re.S)
 6<_sre.SRE_Match object; span=(0, 7), match='foo\nbar'>

在这个例子中,在第 1 行,点元字符与 中的换行符不匹配'foo\nbar'。在第 3 行和第 5 行DOTALL是有效的,所以点确实与换行符匹配。请注意,DOTALL标志的短名称是re.Sre.D与您预期的不同。

re.X
re.VERBOSE

允许在正则表达式中包含空格和注释。

VERBOSE标志指定了一些特殊行为:

  • 正则表达式解析器会忽略所有空格,除非它在字符类中或用反斜杠转义。

  • 如果正则表达式包含一个#不包含在字符类中或用反斜杠转义的字符,则解析器将忽略它及其右侧的所有字符。

这有什么用?它允许您在 Python 中格式化正则表达式,使其更具可读性和自文档化。

这是一个示例,展示了如何使用它。假设您要解析具有以下格式的电话号码:

  • 可选的三位数区号,在括号中
  • 可选空格
  • 三位数前缀
  • 分隔符('-''.'
  • 四位行号

以下正则表达式可以解决问题:

>>>
>>> regex = r'^(\(\d{3}\))?\s*\d{3}[-.]\d{4}$'

>>> re.search(regex, '414.9229')
<_sre.SRE_Match object; span=(0, 8), match='414.9229'>
>>> re.search(regex, '414-9229')
<_sre.SRE_Match object; span=(0, 8), match='414-9229'>
>>> re.search(regex, '(712)414-9229')
<_sre.SRE_Match object; span=(0, 13), match='(712)414-9229'>
>>> re.search(regex, '(712) 414-9229')
<_sre.SRE_Match object; span=(0, 14), match='(712) 414-9229'>

但是r'^(\(\d{3}\))?\s*\d{3}[-.]\d{4}$'是满眼的,不是吗?使用该VERBOSE标志,您可以在 Python 中编写相同的正则表达式,如下所示:

>>>
>>> regex = r'''^               # Start of string
...             (\(\d{3}\))?    # Optional area code
...             \s*             # Optional whitespace
...             \d{3}           # Three-digit prefix
...             [-.]            # Separator character
...             \d{4}           # Four-digit line number
...             $               # Anchor at end of string
...             '''

>>> re.search(regex, '414.9229', re.VERBOSE)
<_sre.SRE_Match object; span=(0, 8), match='414.9229'>
>>> re.search(regex, '414-9229', re.VERBOSE)
<_sre.SRE_Match object; span=(0, 8), match='414-9229'>
>>> re.search(regex, '(712)414-9229', re.X)
<_sre.SRE_Match object; span=(0, 13), match='(712)414-9229'>
>>> re.search(regex, '(712) 414-9229', re.X)
<_sre.SRE_Match object; span=(0, 14), match='(712) 414-9229'>

re.search()电话是一样的上面显示的,所以你可以看到,作为一个先前指定此正则表达式的工作原理相同。但乍一看并不难理解。

请注意,三重引用使得包含嵌入的换行符特别方便,这些换行符在VERBOSE模式中被视为忽略的空格。

使用VERBOSE标志时,请注意您确实打算重要的空白。考虑以下示例:

>>>
 1>>> re.search('foo bar', 'foo bar')
 2<_sre.SRE_Match object; span=(0, 7), match='foo bar'>
 3
 4>>> print(re.search('foo bar', 'foo bar', re.VERBOSE))
 5None
 6
 7>>> re.search('foo\ bar', 'foo bar', re.VERBOSE)
 8<_sre.SRE_Match object; span=(0, 7), match='foo bar'>
 9>>> re.search('foo[ ]bar', 'foo bar', re.VERBOSE)
10<_sre.SRE_Match object; span=(0, 7), match='foo bar'>

到目前为止,您已经看到了所有内容,您可能想知道为什么第 4 行的正则表达式foo bar与 string 不匹配'foo bar'。这不是因为该VERBOSE标志导致解析器忽略空格字符。

要按预期进行匹配,请使用反斜杠对空格字符进行转义或将其包含在字符类中,如第 7 行和第 9 行所示。

DOTALL标志一样,请注意VERBOSE标志有一个不直观的短名称:re.X,不是re.V

re.DEBUG

显示调试信息。

DEBUG标志使 Python 中的正则表达式解析器向控制台显示有关解析过程的调试信息:

>>>
>>> re.search('foo.bar', 'fooxbar', re.DEBUG)
LITERAL 102
LITERAL 111
LITERAL 111
ANY None
LITERAL 98
LITERAL 97
LITERAL 114
<_sre.SRE_Match object; span=(0, 7), match='fooxbar'>

当解析器显示LITERAL nnn在调试输出中时,它显示了正则表达式中文字字符的 ASCII 代码。在这种情况下,文字字符是'f''o','o''b''a''r'

这是一个更复杂的例子。这是VERBOSE之前关于标志的讨论中显示的电话号码正则表达式:

>>>
>>> regex = r'^(\(\d{3}\))?\s*\d{3}[-.]\d{4}$'

>>> re.search(regex, '414.9229', re.DEBUG)
AT AT_BEGINNING
MAX_REPEAT 0 1
  SUBPATTERN 1 0 0
    LITERAL 40
    MAX_REPEAT 3 3
      IN
        CATEGORY CATEGORY_DIGIT
    LITERAL 41
MAX_REPEAT 0 MAXREPEAT
  IN
    CATEGORY CATEGORY_SPACE
MAX_REPEAT 3 3
  IN
    CATEGORY CATEGORY_DIGIT
IN
  LITERAL 45
  LITERAL 46
MAX_REPEAT 4 4
  IN
    CATEGORY CATEGORY_DIGIT
AT AT_END
<_sre.SRE_Match object; span=(0, 8), match='414.9229'>

这看起来像很多您永远不需要的深奥信息,但它可能很有用。有关实际应用,请参阅下面的深入了解。

深入探讨:调试正则表达式解析

如您所知,元字符序列{m,n}表示特定的重复次数。它匹配从mn重复之前的任何地方:

>>>
>>> re.search('x[123]{2,4}y', 'x222y')
<_sre.SRE_Match object; span=(0, 5), match='x222y'>

您可以使用DEBUG标志验证这一点:

>>>
>>> re.search('x[123]{2,4}y', 'x222y', re.DEBUG)
LITERAL 120
MAX_REPEAT 2 4
  IN
    LITERAL 49
    LITERAL 50
    LITERAL 51
LITERAL 121
<_sre.SRE_Match object; span=(0, 5), match='x222y'>

MAX_REPEAT 2 4确认正则表达式解析器识别元字符序列{2,4}并将其解释为范围量词。

但是,如前所述,如果 Python 正则表达式中的一对大括号包含有效数字或数字范围以外的任何内容,那么它就失去了它的特殊意义。

您也可以验证这一点:

>>>
>>> re.search('x[123]{foo}y', 'x222y', re.DEBUG)
LITERAL 120
IN
  LITERAL 49
  LITERAL 50
  LITERAL 51
LITERAL 123
LITERAL 102
LITERAL 111
LITERAL 111
LITERAL 125
LITERAL 121

您可以看到MAX_REPEAT调试输出中没有令牌。该LITERAL标记表明该分析器把{foo}字面上,而不是作为一个量词元字符序列。123102111111125是文字串中字符的 ASCII 码'{foo}'

DEBUG标志显示的信息可以通过向您展示解析器如何解释您的正则表达式来帮助您进行故障排除。

奇怪的是,该re模块没有定义DEBUG标志的单字母版本。如果你想,你可以定义你自己的:

>>>
>>> import re
>>> re.D
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 're' has no attribute 'D'

>>> re.D = re.DEBUG
>>> re.search('foo', 'foo', re.D)
LITERAL 102
LITERAL 111
LITERAL 111
<_sre.SRE_Match object; span=(0, 3), match='foo'>

但这可能比帮助更令人困惑,因为您的代码的读者可能会将其误解为DOTALL标志的缩写。如果您确实完成了这项任务,最好将其完整记录下来。

re.A
re.ASCII
re.U
re.UNICODE
re.L
re.LOCALE

指定用于解析特殊正则表达式字符类的字符编码。

几个正则表达式元字符序列(\w\W\b\B\d\D\s\S)要求您将字符分配给某些类,如单词、数字或空格。该组中的标志确定用于将字符分配给这些类的编码方案。可能的编码是 ASCII、Unicode 或根据当前语言环境。

您在 Python 中的字符串和字符数据教程中对ord()内置函数的讨论中简要介绍了字符编码和 Unicode 。如需更深入的信息,请查看以下资源:

为什么字符编码在 Python 正则表达式的上下文中如此重要?这是一个快速示例。

您之前学习过\d指定单个数字字符。的的描述\d元字符序列指出,这是等同于字符类[0-9]。这恰好适用于英语和西欧语言,但对于世界上的大多数语言,字符'0'through'9'并不代表所有甚至任何数字。

例如,这是一个由三个梵文数字字符组成的字符串:

>>>
>>> s = '\u0967\u096a\u096c'
>>> s
'१४६'

为了让正则表达式解析器正确解释天城文脚本,数字元字符序列也\d必须匹配这些字符中的每一个。

Unicode协会创建的Unicode来处理这个问题。Unicode 是一种字符编码标准,旨在代表世界上所有的书写系统。默认情况下,Python 3 中的所有字符串,包括正则表达式,都是 Unicode。

那么,回到上面列出的标志。这些标志通过指定使用的编码是 ASCII、Unicode 还是当前语言环境来帮助确定字符是否属于给定的类:

  • re.Ure.UNICODE指定 Unicode 编码。Unicode 是默认的,所以这些标志是多余的。它们主要是为了向后兼容而受支持。
  • re.Are.ASCII根据 ASCII 编码强制确定。如果您碰巧使用英语操作,那么无论如何都会发生这种情况,因此该标志不会影响是否找到匹配项。
  • re.Lre.LOCALE根据当前语言环境进行确定。区域设置是一个过时的概念,不被认为是可靠的。除了在极少数情况下,您不太可能需要它。

使用默认的 Unicode 编码,正则表达式解析器应该能够处理您抛出的任何语言。在以下示例中,它正确地将字符串'१४६'中的每个字符识别为数字:

>>>
>>> s = '\u0967\u096a\u096c'
>>> s
'१४६'
>>> re.search('\d+', s)
<_sre.SRE_Match object; span=(0, 3), match='१४६'>

这是另一个示例,说明字符编码如何影响 Python 中的正则表达式匹配。考虑这个字符串:

>>>
>>> s = 'sch\u00f6n'
>>> s
'schön'

'schön'漂亮漂亮的德语单词)包含'ö'具有 16 位十六进制 Unicode 值的字符00f6。这个字符不能用传统的 7 位 ASCII 表示。

如果您使用德语工作,那么您应该合理地期望正则表达式解析器将输入的所有字符'schön'视为单词字符。但是看看如果s使用\w字符类搜索单词字符并强制使用 ASCII 编码会发生什么:

>>>
>>> re.search('\w+', s, re.ASCII)
<_sre.SRE_Match object; span=(0, 3), match='sch'>

当您将编码限制为 ASCII 时,正则表达式解析器仅将前三个字符识别为单词字符。比赛在'ö'

另一方面,如果您指定re.UNICODE或允许编码默认为 Unicode,则所有字符都'schön'可以作为单词字符:

>>>
>>> re.search('\w+', s, re.UNICODE)
<_sre.SRE_Match object; span=(0, 5), match='schön'>
>>> re.search('\w+', s)
<_sre.SRE_Match object; span=(0, 5), match='schön'>

ASCIILOCALE的情况下,你需要他们的特殊情况,标志是可用的。但总的来说,最好的策略是使用默认的 Unicode 编码。这应该正确处理任何世界语言。

<flags>在函数调用中组合参数

定义了标志值,以便您可以使用按位 OR ( |) 运算符组合它们。这允许您在单个函数调用中指定多个标志:

>>>
>>> re.search('^bar', 'FOO\nBAR\nBAZ', re.I|re.M)
<_sre.SRE_Match object; span=(4, 7), match='BAR'>

re.search()调用使用按位 OR一次指定IGNORECASEMULTILINE标志。

在正则表达式中设置和清除标志

除了能够将<flags>参数传递给大多数re模块函数调用之外,您还可以在 Python 的正则表达式中修改标志值。有两个正则表达式元字符序列可提供此功能。

(?<flags>)

为正则表达式的持续时间设置标志值。

在正则表达式中,元字符序列(?<flags>)为整个表达式设置指定的标志。

的值<flags>是从所述一组一个或多个字母aiLmsu,和x。以下是它们与re模块标志的对应方式:

Letter Flags
a re.A     re.ASCII
i re.I     re.IGNORECASE
L re.L     re.LOCALE
m re.M     re.MULTILINE
s re.S     re.DOTALL
u re.U     re.UNICODE
x re.X     re.VERBOSE

整个(?<flags>)元字符序列与空字符串匹配。它总是成功匹配并且不消耗任何搜索字符串。

以下示例是设置IGNORECASEMULTILINE标志的等效方法:

>>>
>>> re.search('^bar', 'FOO\nBAR\nBAZ\n', re.I|re.M)
<_sre.SRE_Match object; span=(4, 7), match='BAR'>

>>> re.search('(?im)^bar', 'FOO\nBAR\nBAZ\n')
<_sre.SRE_Match object; span=(4, 7), match='BAR'>

请注意,(?<flags>)元字符序列会为整个正则表达式设置给定的标志,无论您将它放在表达式中的哪个位置:

>>>
>>> re.search('foo.bar(?s).baz', 'foo\nbar\nbaz')
<_sre.SRE_Match object; span=(0, 11), match='foo\nbar\nbaz'>

>>> re.search('foo.bar.baz(?s)', 'foo\nbar\nbaz')
<_sre.SRE_Match object; span=(0, 11), match='foo\nbar\nbaz'>

在上面的例子中,两个点元字符都匹配换行符,因为DOTALL标志是有效的。即使(?s)出现在表达式的中间或末尾也是如此。

从 Python 3.7 开始,不推荐(?<flags>)在正则表达式中的任何位置指定而不是在开头:

>>>
>>> import sys
>>> sys.version
'3.8.0 (default, Oct 14 2019, 21:29:03) \n[GCC 7.4.0]'

>>> re.search('foo.bar.baz(?s)', 'foo\nbar\nbaz')
<stdin>:1: DeprecationWarning: Flags not at the start
    of the expression 'foo.bar.baz(?s)'
<re.Match object; span=(0, 11), match='foo\nbar\nbaz'>

它仍会生成适当的匹配项,但您会收到一条警告消息。

(?<set_flags>-<remove_flags>:<regex>)

设置或删除组持续时间的标志值。

(?<set_flags>-<remove_flags>:<regex>)定义一个与 匹配的非捕获组<regex>。对于<regex>组中包含的 ,正则表达式解析器设置 中指定的任何标志<set_flags>并清除 中指定的任何标志<remove_flags>

<set_flags><remove_flags>最常见的是imsx

在以下示例中,IGNORECASE为指定组设置了标志:

>>>
>>> re.search('(?i:foo)bar', 'FOObar')
<re.Match object; span=(0, 6), match='FOObar'>

这会产生一个匹配项,因为它(?i:foo)规定了匹配项'FOO'不区分大小写。

现在将其与此示例进行对比:

>>>
>>> print(re.search('(?i:foo)bar', 'FOOBAR'))
None

与前面的示例一样,匹配对'FOO'会成功,因为它不区分大小写。但是一旦在组外,IGNORECASE就不再有效,因此匹配'BAR'是区分大小写的并且失败。

下面的示例演示了为组关闭标志:

>>>
>>> print(re.search('(?-i:foo)bar', 'FOOBAR', re.IGNORECASE))
None

再次,没有匹配。尽管re.IGNORECASE为整个调用启用了不区分大小写的匹配,但元字符序列在该组的持续时间内(?-i:foo)关闭IGNORECASE,因此匹配'FOO'失败。

从 Python 3.7 开始,您可以指定uaLas<set_flags>来覆盖指定组的默认编码:

>>>
>>> s = 'sch\u00f6n'
>>> s
'schön'

>>> # Requires Python 3.7 or later
>>> re.search('(?a:\w+)', s)
<re.Match object; span=(0, 3), match='sch'>
>>> re.search('(?u:\w+)', s)
<re.Match object; span=(0, 5), match='schön'>

但是,您只能以这种方式设置编码。你不能删除它:

>>>
>>> re.search('(?-a:\w+)', s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/re.py", line 199, in search
    return _compile(pattern, flags).search(string)
  File "/usr/lib/python3.8/re.py", line 302, in _compile
    p = sre_compile.compile(pattern, flags)
  File "/usr/lib/python3.8/sre_compile.py", line 764, in compile
    p = sre_parse.parse(p, flags)
  File "/usr/lib/python3.8/sre_parse.py", line 948, in parse
    p = _parse_sub(source, state, flags & SRE_FLAG_VERBOSE, 0)
  File "/usr/lib/python3.8/sre_parse.py", line 443, in _parse_sub
    itemsappend(_parse(source, state, verbose, nested + 1,
  File "/usr/lib/python3.8/sre_parse.py", line 805, in _parse
    flags = _parse_flags(source, state, char)
  File "/usr/lib/python3.8/sre_parse.py", line 904, in _parse_flags
    raise source.error(msg)
re.error: bad inline flags: cannot turn off flags 'a', 'u' and 'L' at
position 4

ua, 和L是互斥的。每组只能出现其中一个。

结论

您对正则表达式匹配和 Pythonre模块的介绍到此结束。恭喜!你已经掌握了大量的材料。

您现在知道如何:

  • 用于re.search()在 Python 中执行正则表达式匹配
  • 使用正则表达式元字符创建复杂的模式匹配搜索
  • 使用标志调整正则表达式解析行为

但是您仍然只看到模块中的一个函数:re.search()! 该re模块有更多有用的函数和对象可以添加到您的模式匹配工具包中。本系列的下一个教程将向您介绍 Python 中的 regex 模块必须提供的其他功能。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。