re — 正则表达式操作

源代码: Lib/re/


此模块提供类似于 Perl 中正则表达式匹配操作。

要搜索的模式和字符串都可以是 Unicode 字符串 (str) 以及 8 位字符串 (bytes)。但是,Unicode 字符串和 8 位字符串不能混合使用:也就是说,您不能使用字节模式匹配 Unicode 字符串,反之亦然;同样,当要求替换时,替换字符串必须与模式和搜索字符串的类型相同。

正则表达式使用反斜杠字符 ('\') 来指示特殊形式或允许使用特殊字符而不调用其特殊含义。这与 Python 在字符串字面量中使用相同字符以达到相同目的相冲突;例如,要匹配一个字面反斜杠,可能需要将 '\\\\' 写为模式字符串,因为正则表达式必须是 \\,并且每个反斜杠都必须在常规 Python 字符串字面量中表示为 \\。另请注意,Python 在字符串字面量中使用反斜杠的任何无效转义序列现在都会生成 SyntaxWarning,并且将来这将变为 SyntaxError。即使它是正则表达式的有效转义序列,也会发生此行为。

解决方案是为正则表达式模式使用 Python 的原始字符串表示法;在以 'r' 为前缀的字符串字面量中,不会以任何特殊方式处理反斜杠。因此,r"\n" 是一个包含 '\''n' 的双字符字符串,而 "\n" 是一个包含换行符的单字符字符串。通常,模式将使用此原始字符串表示法在 Python 代码中表示。

重要的是要注意,大多数正则表达式操作都可以作为模块级函数和 已编译正则表达式 的方法使用。这些函数是快捷方式,不需要您先编译正则表达式对象,但会错过一些微调参数。

参见

第三方 regex 模块,它具有与标准库 re 模块兼容的 API,但提供额外的功能和更全面的 Unicode 支持。

正则表达式语法

正则表达式(或 RE)指定一组与其匹配的字符串;此模块中的函数允许您检查特定字符串是否与给定的正则表达式匹配(或者给定的正则表达式是否与特定字符串匹配,这归结为同一件事)。

可以将正则表达式连接起来以形成新的正则表达式;如果 AB 都是正则表达式,那么 AB 也是一个正则表达式。通常,如果字符串 pA 匹配,而另一个字符串 qB 匹配,则字符串 pq 将与 AB 匹配。除非 AB 包含低优先级操作;AB 之间的边界条件;或具有编号的组引用,否则这种情况成立。因此,可以很容易地从更简单的原始表达式(如这里描述的表达式)构建复杂的表达式。有关正则表达式的理论和实现的详细信息,请查阅 Friedl 的著作 [Frie09],或者几乎任何关于编译器构造的教科书。

下面是对正则表达式格式的简要说明。有关更多信息和更友好的介绍,请查阅 正则表达式 HOWTO

正则表达式可以包含特殊字符和普通字符。大多数普通字符,如 'A''a''0',是最简单的正则表达式;它们只是简单地匹配自身。您可以连接普通字符,因此 last 匹配字符串 'last'。(在本节的其余部分,我们将以 这种 特殊 样式(通常不带引号)编写 RE,并将要匹配的字符串用 '单引号' 表示。)

某些字符,如 '|''(',是特殊的。特殊字符要么代表普通字符的类,要么影响它们周围的正则表达式的解释方式。

重复运算符或量词(*+?{m,n} 等)不能直接嵌套。这避免了非贪婪修饰符后缀 ? 以及其他实现中的其他修饰符的歧义。要对内部重复应用第二次重复,可以使用括号。例如,表达式 (?:a{6})* 匹配六个 'a' 字符的任何倍数。

特殊字符是

.

(点号。)在默认模式下,这会匹配除换行符之外的任何字符。如果已指定 DOTALL 标志,则这会匹配包括换行符在内的任何字符。(?s:.) 匹配任何字符,而不管标志如何。

^

(插入符号。)匹配字符串的开头,并且在 MULTILINE 模式下也匹配每个换行符之后立即出现的内容。

$

匹配字符串的结尾或字符串结尾处换行符之前的内容,并且在 MULTILINE 模式下也匹配换行符之前的内容。foo 匹配 ‘foo’ 和 ‘foobar’,而正则表达式 foo$ 仅匹配 ‘foo’。更有趣的是,在 'foo1\nfoo2\n' 中搜索 foo.$ 通常匹配 ‘foo2’,但在 MULTILINE 模式下匹配 ‘foo1’;在 'foo\n' 中搜索单个 $ 将找到两个(空)匹配项:一个在换行符之前,一个在字符串的末尾。

*

使生成的 RE 匹配前面 RE 的 0 个或多个重复项,尽可能多的重复项。ab* 将匹配 ‘a’、‘ab’ 或 ‘a’ 后跟任意数量的 ‘b’。

+

使生成的正则表达式匹配前一个正则表达式的一次或多次重复。例如,ab+ 将匹配 ‘a’ 后面跟着任意数量(非零)的 ‘b’;它不会只匹配 ‘a’。

?

使生成的正则表达式匹配前一个正则表达式的零次或一次重复。例如,ab? 将匹配 ‘a’ 或 ‘ab’。

*?, +?, ??

'*''+''?' 量词都是 贪婪的;它们会尽可能多地匹配文本。有时,这种行为不是我们想要的;如果正则表达式 <.*>'<a> b <c>' 匹配,它将匹配整个字符串,而不仅仅是 '<a>'。在量词后添加 ? 会使其以 非贪婪最小 方式执行匹配;尽可能匹配的字符。使用正则表达式 <.*?> 将仅匹配 '<a>'

*+, ++, ?+

'*''+''?' 量词一样,那些附加 '+' 的量词也会尽可能多地匹配。然而,与真正的贪婪量词不同,当它后面的表达式匹配失败时,这些量词不允许回溯。这些量词被称为 占有 量词。例如,a*a 将匹配 'aaaa',因为 a* 将匹配所有 4 个 'a',但是,当遇到最后一个 'a' 时,表达式会回溯,以便最终 a* 最终匹配 3 个 'a',而第四个 'a' 由最后的 'a' 匹配。但是,当 a*+a 用于匹配 'aaaa' 时,a*+ 将匹配所有 4 个 'a',但当最后的 'a' 找不到更多字符来匹配时,表达式无法回溯,因此匹配会失败。x*+x++x?+ 分别等效于 (?>x*)(?>x+)(?>x?)

在 3.11 版本中添加。

{m}

指定前一个正则表达式必须精确匹配 m 次;少于此次数的匹配将导致整个正则表达式不匹配。例如,a{6} 将匹配正好六个 'a' 字符,但不会匹配五个。

{m,n}

使生成的正则表达式匹配前一个正则表达式的 mn 次重复,并尝试匹配尽可能多的重复次数。例如,a{3,5} 将匹配 3 到 5 个 'a' 字符。省略 m 指定下限为零,省略 n 指定上限为无限大。例如,a{4,}b 将匹配 'aaaab' 或一千个 'a' 字符后跟一个 'b',但不会匹配 'aaab'。逗号不能省略,否则修饰符会与之前描述的形式混淆。

{m,n}?

使生成的正则表达式匹配前一个正则表达式的 mn 次重复,并尝试匹配尽可能的重复次数。这是前一个量词的非贪婪版本。例如,在 6 个字符的字符串 'aaaaaa' 上,a{3,5} 将匹配 5 个 'a' 字符,而 a{3,5}? 将仅匹配 3 个字符。

{m,n}+

使生成的正则表达式匹配前一个正则表达式的 mn 次重复,并尝试匹配尽可能多的重复次数,而不建立任何回溯点。这是上述量词的占有版本。例如,在 6 个字符的字符串 'aaaaaa' 上,a{3,5}+aa 尝试匹配 5 个 'a' 字符,然后需要另外 2 个 'a',会需要比可用字符更多的字符,因此匹配失败,而 a{3,5}aa 将匹配,其中 a{3,5} 捕获 5 个,然后通过回溯捕获 4 个 'a',然后最后 2 个 'a' 由模式中的最后 aa 匹配。x{m,n}+ 等效于 (?>x{m,n})

在 3.11 版本中添加。

\

转义特殊字符(允许您匹配 '*''?' 等字符),或表示一个特殊序列;特殊序列将在下面讨论。

如果您没有使用原始字符串来表达模式,请记住 Python 也使用反斜杠作为字符串字面量中的转义序列;如果转义序列没有被 Python 的解析器识别,则反斜杠和后续字符将包含在结果字符串中。但是,如果 Python 识别结果序列,则应重复反斜杠两次。这很复杂且难以理解,因此强烈建议您为所有但最简单的表达式使用原始字符串。

[]

用于表示一组字符。在一个集合中

  • 字符可以单独列出,例如,[amk] 将匹配 'a''m''k'

  • 可以通过给出两个字符并用 '-' 分隔来指示字符范围,例如,[a-z] 将匹配任何小写 ASCII 字母,[0-5][0-9] 将匹配从 0059 的所有两位数字,并且 [0-9A-Fa-f] 将匹配任何十六进制数字。如果 - 被转义(例如,[a\-z])或者如果它被放置在第一个或最后一个字符(例如,[-a][a-]),它将匹配文字 '-'

  • 特殊字符在集合内部会失去其特殊含义。例如,[(+*)] 将匹配文字字符 '(''+''*'')' 中的任何一个。

  • 诸如 \w\S(如下定义)之类的字符类也可以在集合内部接受,尽管它们匹配的字符取决于使用的标志

  • 可以通过补集来匹配不在范围内的字符。如果集合的第一个字符是 '^',则所有在该集合中的字符都将被匹配。例如,[^5] 将匹配除 '5' 之外的任何字符,而 [^^] 将匹配除 '^' 之外的任何字符。如果 ^ 不是集合中的第一个字符,则它没有特殊含义。

  • 要在集合内匹配文字 ']',请在其前面加上反斜杠,或将其放置在集合的开头。例如,[()[\]{}][]()[{}] 都将匹配右括号,以及左括号、大括号和圆括号。

  • 未来可能会添加对嵌套集合和集合操作的支持,如 Unicode 技术标准 #18 中所述。这将改变语法,为了方便此更改,目前在有歧义的情况下会引发 FutureWarning 警告。这包括以字面量 '[' 开头或包含字面字符序列 '--''&&''~~''||' 的集合。要避免警告,请使用反斜杠对其进行转义。

3.7 版本更改: 如果字符集包含未来语义会发生变化的构造,则会引发 FutureWarning 警告。

|

A|B,其中 *A* 和 *B* 可以是任意正则表达式,创建一个正则表达式,它将匹配 *A* 或 *B*。 可以用 '|' 符号分隔任意数量的正则表达式。这也可以在组内使用(见下文)。当扫描目标字符串时,会从左到右尝试由 '|' 分隔的正则表达式。当一个模式完全匹配时,该分支被接受。这意味着一旦 *A* 匹配,即使 *B* 会产生更长的整体匹配,也不会进一步测试 *B*。换句话说,'|' 操作符永远不是贪婪的。 要匹配字面量 '|',请使用 \|,或将其括在字符类中,例如 [|]

(...)

匹配括号内的任何正则表达式,并指示组的开始和结束;在执行匹配后可以检索组的内容,并且可以使用 \number 特殊序列在字符串的后面进行匹配,如下所述。 要匹配字面量 '('')',请使用 \(\),或将它们括在字符类中:[(][)]

(?...)

这是一种扩展表示法('(' 后面的 '?' 在其他情况下没有意义)。 '?' 之后的第一个字符决定了构造的含义和进一步的语法。扩展通常不会创建新的组;(?P<name>...) 是此规则的唯一例外。以下是目前支持的扩展。

(?aiLmsux)

(来自集合 'a''i''L''m''s''u''x' 的一个或多个字母。) 该组匹配空字符串;这些字母为整个正则表达式设置相应的标志。

  • re.A (仅限 ASCII 匹配)

  • re.I (忽略大小写)

  • re.L (取决于语言环境)

  • re.M (多行)

  • re.S (点号匹配所有字符)

  • re.U (Unicode 匹配)

  • re.X (详细)

(这些标志在 模块内容 中描述。)如果您希望将标志作为正则表达式的一部分包含在内,而不是将 *flag* 参数传递给 re.compile() 函数,这将非常有用。标志应首先在表达式字符串中使用。

3.11 版本更改: 此构造只能在表达式的开头使用。

(?:...)

非捕获版本的普通括号。 匹配括号内的任何正则表达式,但该组匹配的子字符串在执行匹配后 *无法* 检索或在模式中稍后引用。

(?aiLmsux-imsx:...)

(来自集合 'a''i''L''m''s''u''x' 的零个或多个字母,可以选择后跟 '-',然后后跟来自 'i''m''s''x' 的一个或多个字母。) 这些字母为表达式的一部分设置或移除相应的标志。

  • re.A (仅限 ASCII 匹配)

  • re.I (忽略大小写)

  • re.L (取决于语言环境)

  • re.M (多行)

  • re.S (点号匹配所有字符)

  • re.U (Unicode 匹配)

  • re.X (详细)

(这些标志在 模块内容 中描述。)

'a''L''u' 作为内联标志使用时,它们是互斥的,因此它们不能组合或跟随 '-'。相反,当其中一个出现在内联组中时,它会覆盖封闭组中的匹配模式。 在 Unicode 模式中,(?a:...) 切换为仅限 ASCII 的匹配,而 (?u:...) 切换为 Unicode 匹配(默认)。在字节模式中,(?L:...) 切换为取决于语言环境的匹配,而 (?a:...) 切换为仅限 ASCII 的匹配(默认)。此覆盖仅对狭窄的内联组有效,并且原始匹配模式在组外部恢复。

在 3.6 版本中添加。

3.7 版本更改: 字母 'a''L''u' 也可以在组中使用。

(?>...)

尝试匹配 ...,就好像它是一个单独的正则表达式一样,如果成功,则继续匹配它后面的模式的其余部分。如果后续模式匹配失败,则堆栈只能展开到 (?>...) *之前* 的点,因为一旦退出,该表达式(称为原子组)就会丢弃其内部的所有堆栈点。因此,(?>.*). 将永远不会匹配任何内容,因为首先 .* 会匹配所有可能的字符,然后,在没有剩下任何内容可以匹配的情况下,最后的 . 将匹配失败。由于在原子组中没有保存堆栈点,并且在其之前没有堆栈点,因此整个表达式将匹配失败。

在 3.11 版本中添加。

(?P<name>...)

类似于普通括号,但组匹配的子字符串可以通过符号组名 *name* 访问。组名必须是有效的 Python 标识符,并且在 bytes 模式中,它们只能包含 ASCII 范围内的字节。每个组名在正则表达式中只能定义一次。符号组也是一个编号的组,就像该组未命名一样。

命名的组可以在三个上下文中引用。 如果模式是 (?P<quote>['"]).*?(?P=quote)(即匹配用单引号或双引号引起来的字符串)

引用组“quote”的上下文

引用它的方式

在同一模式本身中

  • (?P=quote)(如图所示)

  • \1

当处理匹配对象 *m* 时

  • m.group('quote')

  • m.end('quote') (等等)

在传递给 re.sub() 的 *repl* 参数的字符串中

  • \g<quote>

  • \g<1>

  • \1

3.12 版本更改: bytes 模式中,组 *name* 只能包含 ASCII 范围内的字节(b'\x00'-b'\x7f')。

(?P=name)

对命名组的反向引用;它匹配之前名为 *name* 的组所匹配的任何文本。

(?#...)

注释;括号的内容将被忽略。

(?=...)

如果 ... 在接下来匹配则匹配,但不消耗任何字符串。 这称为正向先行断言。例如,Isaac (?=Asimov) 仅当它后面跟着 'Asimov' 时才会匹配 'Isaac '

(?!...)

如果 ... 在接下来不匹配则匹配。 这称为负向先行断言。例如,Isaac (?!Asimov) 仅当它后面*不*跟着 'Asimov' 时才会匹配 'Isaac '

(?<=...)

如果字符串中的当前位置之前存在与 ... 匹配的内容,并且该匹配结束于当前位置,则匹配成功。这被称为正向后行断言(?<=abc)def 将在 'abcdef' 中找到匹配,因为后行断言会回溯 3 个字符,并检查包含的模式是否匹配。包含的模式必须仅匹配某些固定长度的字符串,这意味着允许使用 abca|b,但不允许使用 a*a{3,4}。请注意,以正向后行断言开头的模式不会在被搜索字符串的开头匹配;你很可能需要使用 search() 函数而不是 match() 函数。

>>> import re
>>> m = re.search('(?<=abc)def', 'abcdef')
>>> m.group(0)
'def'

此示例查找连字符后的单词

>>> m = re.search(r'(?<=-)\w+', 'spam-egg')
>>> m.group(0)
'egg'

在 3.5 版本中更改: 添加了对固定长度的组引用的支持。

(?<!...)

如果字符串中的当前位置之前不存在与 ... 匹配的内容,则匹配成功。这被称为负向后行断言。与正向后行断言类似,包含的模式必须仅匹配某些固定长度的字符串。以负向后行断言开头的模式可以在被搜索字符串的开头匹配。

(?(id/name)yes-pattern|no-pattern)

如果具有给定 idname 的组存在,则会尝试与 yes-pattern 匹配,如果不存在,则与 no-pattern 匹配。no-pattern 是可选的,可以省略。例如,(<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$) 是一个较差的电子邮件匹配模式,它将与 '<[email protected]>' 以及 '[email protected]' 匹配,但不会与 '<[email protected]''[email protected]>' 匹配。

在 3.12 版本中更改: id 只能包含 ASCII 数字。在 bytes 模式中,组 name 只能包含 ASCII 范围内的字节 (b'\x00'-b'\x7f')。

特殊序列由 '\' 和下面列表中的一个字符组成。如果普通字符不是 ASCII 数字或 ASCII 字母,则生成的正则表达式将匹配第二个字符。例如,\$ 匹配字符 '$'

\number

匹配相同编号的组的内容。组的编号从 1 开始。例如,(.+) \1 匹配 'the the''55 55',但不匹配 'thethe'(注意组后的空格)。此特殊序列只能用于匹配前 99 个组中的一个。如果 number 的第一位是 0,或者 number 的长度是 3 个八进制数字,则它不会被解释为组匹配,而是被解释为八进制值为 number 的字符。在字符类的 '['']' 内部,所有数字转义都被视为字符。

\A

仅在字符串的开头匹配。

\b

匹配空字符串,但仅在单词的开头或结尾匹配。单词定义为一系列单词字符。请注意,正式地,\b 定义为 \w\W 字符之间(或反之亦然)的边界,或者 \w 与字符串的开头或结尾之间的边界。这意味着 r'\bat\b' 匹配 'at''at.''(at)''as at ay',但不匹配 'attempt''atlas'

Unicode (str) 模式中的默认单词字符是 Unicode 字母数字和下划线,但这可以通过使用 ASCII 标志来更改。如果使用了 LOCALE 标志,则单词边界由当前区域设置确定。

注意

在字符范围内,\b 表示退格字符,以便与 Python 的字符串字面量兼容。

\B

匹配空字符串,但仅当它在单词的开头或结尾时匹配。这意味着 r'at\B' 匹配 'athens''atom''attorney',但不匹配 'at''at.''at!'\B\b 相反,因此 Unicode (str) 模式中的单词字符是 Unicode 字母数字或下划线,尽管可以通过使用 ASCII 标志来更改。如果使用了 LOCALE 标志,则单词边界由当前区域设置确定。

注意

请注意,\B 不匹配空字符串,这与其他编程语言(如 Perl)中的正则表达式实现不同。保留此行为是为了兼容性原因。

\d
对于 Unicode (str) 模式

匹配任何 Unicode 十进制数字(即,Unicode 字符类别 [Nd] 中的任何字符)。这包括 [0-9],以及许多其他数字字符。

如果使用了 ASCII 标志,则匹配 [0-9]

对于 8 位 (bytes) 模式

匹配 ASCII 字符集中的任何十进制数字;这等效于 [0-9]

\D

匹配任何不是十进制数字的字符。这是 \d 的反义词。

如果使用了 ASCII 标志,则匹配 [^0-9]

\s
对于 Unicode (str) 模式

匹配 Unicode 空白字符(由 str.isspace() 定义)。这包括 [ \t\n\r\f\v],以及许多其他字符,例如许多语言的排版规则要求的非断空格。

如果使用了 ASCII 标志,则匹配 [ \t\n\r\f\v]

对于 8 位 (bytes) 模式

匹配 ASCII 字符集中被认为是空白的字符;这等效于 [ \t\n\r\f\v]

\S

匹配任何不是空白字符的字符。这是 \s 的反义词。

如果使用了 ASCII 标志,则匹配 [^ \t\n\r\f\v]

\w
对于 Unicode (str) 模式

匹配 Unicode 单词字符;这包括所有 Unicode 字母数字字符(由 str.isalnum() 定义)以及下划线 (_)。

如果使用了 ASCII 标志,则匹配 [a-zA-Z0-9_]

对于 8 位 (bytes) 模式

匹配 ASCII 字符集中被认为是字母数字的字符;这等效于 [a-zA-Z0-9_]。如果使用了 LOCALE 标志,则匹配当前区域设置中被认为是字母数字的字符和下划线。

\W

匹配任何不是单词字符的字符。这是 \w 的反义词。默认情况下,匹配 str.isalnum() 返回 False 的非下划线 (_) 字符。

如果使用了 ASCII 标志,则匹配 [^a-zA-Z0-9_]

如果使用了 LOCALE 标志,则匹配当前区域设置中既不是字母数字也不是下划线的字符。

\Z

仅在字符串的结尾匹配。

正则表达式解析器也接受 Python 字符串字面量支持的大多数 转义序列

\a      \b      \f      \n
\N      \r      \t      \u
\U      \v      \x      \\

(请注意,\b 用于表示单词边界,仅在字符类内部表示“退格”。)

'\u''\U''\N' 转义序列仅在 Unicode (str) 模式中识别。在字节模式中,它们是错误。ASCII 字母的未知转义保留供将来使用,并被视为错误。

八进制转义以有限的形式包含在内。如果第一个数字是 0,或者有三个八进制数字,则将其视为八进制转义。否则,它是一个组引用。对于字符串字面量,八进制转义的长度始终最多为三个数字。

在 3.3 版本中更改: 添加了 '\u''\U' 转义序列。

在 3.6 版本中更改:'\' 和 ASCII 字母组成的未知转义现在会报错。

在 3.8 版本中更改: 添加了 '\N{name}' 转义序列。与字符串字面量中一样,它会展开为命名的 Unicode 字符(例如 '\N{EM DASH}')。

模块内容

该模块定义了几个函数、常量和一个异常。其中一些函数是编译后的正则表达式的完整功能的简化版本。大多数非平凡的应用程序总是使用编译形式。

标志

在 3.6 版本中更改: 标志常量现在是 RegexFlag 的实例,它是 enum.IntFlag 的子类。

class re.RegexFlag

一个包含下面列出的正则表达式选项的 enum.IntFlag 类。

在 3.11 版本中添加: - 添加到 __all__

re.A
re.ASCII

使 \w\W\b\B\d\D\s\S 执行仅限 ASCII 的匹配,而不是完整的 Unicode 匹配。这仅对 Unicode (str) 模式有意义,对字节模式则忽略。

对应于内联标志 (?a)

注意

U 标志仍然存在以向后兼容,但在 Python 3 中是多余的,因为默认情况下,str 模式的匹配是 Unicode,并且字节模式不允许 Unicode 匹配。UNICODE 和内联标志 (?u) 同样是多余的。

re.DEBUG

显示有关已编译表达式的调试信息。

没有对应的内联标志。

re.I
re.IGNORECASE

执行不区分大小写的匹配;像 [A-Z] 这样的表达式也将匹配小写字母。完整的 Unicode 匹配(例如 Ü 匹配 ü)也有效,除非使用 ASCII 标志来禁用非 ASCII 匹配。除非也使用 LOCALE 标志,否则当前区域设置不会改变此标志的效果。

对应于内联标志 (?i)

请注意,当 Unicode 模式 [a-z][A-Z]IGNORECASE 标志结合使用时,它们将匹配 52 个 ASCII 字母和 4 个额外的非 ASCII 字母:‘İ’ (U+0130, 带有点的拉丁大写字母 I)、‘ı’ (U+0131, 拉丁小写无点字母 i)、‘ſ’ (U+017F, 拉丁小写长字母 s) 和 ‘K’ (U+212A, 开尔文符号)。如果使用 ASCII 标志,则仅匹配字母 'a' 到 'z' 和 'A' 到 'Z'。

re.L
re.LOCALE

使 \w\W\b\B 和不区分大小写的匹配取决于当前区域设置。此标志只能与字节模式一起使用。

对应于内联标志 (?L)

警告

不鼓励使用此标志;请考虑改用 Unicode 匹配。区域设置机制非常不可靠,因为它一次只能处理一种“文化”,并且只能与 8 位区域设置一起使用。默认情况下,Unicode (str) 模式启用了 Unicode 匹配,并且能够处理不同的区域设置和语言。

在 3.6 版本中更改: LOCALE 只能与字节模式一起使用,并且与 ASCII 不兼容。

在 3.7 版本中更改: 带有 LOCALE 标志的已编译正则表达式对象不再依赖于编译时的区域设置。只有匹配时的区域设置才会影响匹配结果。

re.M
re.MULTILINE

指定后,模式字符 '^' 在字符串的开头和每行的开头(紧跟每个换行符)处匹配;模式字符 '$' 在字符串的末尾和每行的末尾(紧接在每个换行符之前)匹配。默认情况下,'^' 仅在字符串的开头匹配,而 '$' 仅在字符串的末尾和紧接在字符串末尾的换行符(如果有)之前匹配。

对应于内联标志 (?m)

re.NOFLAG

表示没有应用任何标志,该值为 0。此标志可以用作函数关键字参数的默认值,或者用作将与其他标志进行有条件 OR 运算的基本值。用作默认值的示例

def myfunc(text, flag=re.NOFLAG):
    return re.match(text, flag)

在 3.11 版本中添加。

re.S
re.DOTALL

使 '.' 特殊字符匹配任何字符,包括换行符;如果没有此标志,'.' 将匹配任何内容,除了换行符。

对应于内联标志 (?s)

re.U
re.UNICODE

在 Python 3 中,默认情况下,Unicode 字符与 str 模式匹配。因此,此标志是多余的,没有效果,仅保留用于向后兼容。

请参阅 ASCII 以限制匹配仅限 ASCII 字符。

re.X
re.VERBOSE

此标志允许你编写更美观、更易读的正则表达式,通过允许你直观地分隔模式的逻辑部分并添加注释。模式中的空白将被忽略,除非它位于字符类中、前面有未转义的反斜杠,或位于 *?(?:(?P<...> 等标记内。例如,不允许使用 (? :* ?。当一行包含一个不在字符类中且前面没有未转义反斜杠的 # 时,从最左边的这个 # 到行尾的所有字符都将被忽略。

这意味着以下两个匹配十进制数的正则表达式对象在功能上是相等的

a = re.compile(r"""\d +  # the integral part
                   \.    # the decimal point
                   \d *  # some fractional digits""", re.X)
b = re.compile(r"\d+\.\d*")

对应于内联标志 (?x)

函数

re.compile(pattern, flags=0)

将正则表达式模式编译为一个正则表达式对象,该对象可用于使用其match()search() 和其他如下所述的方法进行匹配。

可以通过指定 flags 值来修改表达式的行为。 值可以是任何flags变量,使用按位或(| 运算符)组合。

以下序列

prog = re.compile(pattern)
result = prog.match(string)

等效于

result = re.match(pattern, string)

但是,当表达式在单个程序中多次使用时,使用 re.compile() 并保存生成的正则表达式对象以供重用会更有效。

注意

传递给 re.compile() 和模块级匹配函数的最新模式的编译版本会被缓存,因此一次只使用少量正则表达式的程序无需担心编译正则表达式。

re.search(pattern, string, flags=0)

扫描 string,查找正则表达式 pattern 产生匹配的第一个位置,并返回相应的 Match。 如果字符串中没有任何位置与模式匹配,则返回 None; 请注意,这与在字符串中的某个位置找到零长度匹配不同。

可以通过指定 flags 值来修改表达式的行为。 值可以是任何flags变量,使用按位或(| 运算符)组合。

re.match(pattern, string, flags=0)

如果 string 开头的零个或多个字符与正则表达式 pattern 匹配,则返回相应的 Match。如果字符串与模式不匹配,则返回 None;请注意,这与零长度匹配不同。

请注意,即使在 MULTILINE 模式下,re.match() 也只会匹配字符串的开头,而不会匹配每行的开头。

如果要在 string 中的任何位置查找匹配项,请改用 search()(另请参阅 search() 与 match())。

可以通过指定 flags 值来修改表达式的行为。 值可以是任何flags变量,使用按位或(| 运算符)组合。

re.fullmatch(pattern, string, flags=0)

如果整个 string 与正则表达式 pattern 匹配,则返回相应的 Match。如果字符串与模式不匹配,则返回 None;请注意,这与零长度匹配不同。

可以通过指定 flags 值来修改表达式的行为。 值可以是任何flags变量,使用按位或(| 运算符)组合。

在 3.4 版本中新增。

re.split(pattern, string, maxsplit=0, flags=0)

通过 pattern 的出现次数拆分 string。 如果在 pattern 中使用了捕获括号,则该模式中所有组的文本也会作为结果列表的一部分返回。 如果 maxsplit 不为零,则最多发生 maxsplit 次拆分,并且字符串的其余部分作为列表的最后一个元素返回。

>>> re.split(r'\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'\W+', 'Words, words, words.', maxsplit=1)
['Words', 'words, words.']
>>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE)
['0', '3', '9']

如果在分隔符中存在捕获组,并且它与字符串的开头匹配,则结果将以空字符串开头。 这对于字符串的结尾也成立

>>> re.split(r'(\W+)', '...words, words...')
['', '...', 'words', ', ', 'words', '...', '']

这样,分隔符组件始终在结果列表中以相同的相对索引找到。

仅当模式的空匹配不与之前的空匹配相邻时,才会拆分字符串。

>>> re.split(r'\b', 'Words, words, words.')
['', 'Words', ', ', 'words', ', ', 'words', '.']
>>> re.split(r'\W*', '...words...')
['', '', 'w', 'o', 'r', 'd', 's', '', '']
>>> re.split(r'(\W*)', '...words...')
['', '...', '', '', 'w', '', 'o', '', 'r', '', 'd', '', 's', '...', '', '', '']

可以通过指定 flags 值来修改表达式的行为。 值可以是任何flags变量,使用按位或(| 运算符)组合。

在 3.1 版本中更改:添加了可选的 flags 参数。

在 3.7 版本中更改:添加了对在可能匹配空字符串的模式上进行拆分的支持。

自 3.13 版本起弃用:maxsplitflags 作为位置参数传递已弃用。 在未来的 Python 版本中,它们将是 仅限关键字的参数

re.findall(pattern, string, flags=0)

以字符串或元组列表的形式返回 patternstring 中的所有非重叠匹配项。 从左到右扫描 string,并按找到的顺序返回匹配项。 空匹配项包含在结果中。

结果取决于模式中捕获组的数量。 如果没有组,则返回匹配整个模式的字符串列表。 如果只有一个组,则返回与该组匹配的字符串列表。 如果存在多个组,则返回与组匹配的字符串元组列表。 非捕获组不影响结果的形式。

>>> re.findall(r'\bf[a-z]*', 'which foot or hand fell fastest')
['foot', 'fell', 'fastest']
>>> re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10')
[('width', '20'), ('height', '10')]

可以通过指定 flags 值来修改表达式的行为。 值可以是任何flags变量,使用按位或(| 运算符)组合。

在 3.7 版本中更改:非空匹配现在可以在之前的空匹配之后立即开始。

re.finditer(pattern, string, flags=0)

返回一个 迭代器,该迭代器在 string 中为 RE pattern 的所有非重叠匹配项产生 Match 对象。 从左到右扫描 string,并按找到的顺序返回匹配项。 空匹配项包含在结果中。

可以通过指定 flags 值来修改表达式的行为。 值可以是任何flags变量,使用按位或(| 运算符)组合。

在 3.7 版本中更改:非空匹配现在可以在之前的空匹配之后立即开始。

re.sub(pattern, repl, string, count=0, flags=0)

返回通过将 string 中最左侧的非重叠 pattern 出现次数替换为替换项 repl 而获得的字符串。 如果找不到模式,则返回 string,而不进行任何更改。 repl 可以是字符串或函数;如果它是字符串,则会处理其中的任何反斜杠转义。 也就是说,\n 会转换为单个换行符,\r 会转换为回车符,依此类推。 ASCII 字母的未知转义符保留供将来使用,并视为错误。 其他未知转义符(例如 \&)将保持原样。 反向引用(例如 \6)会替换为模式中第 6 组匹配的子字符串。 例如

>>> re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(\s*\):',
...        r'static PyObject*\npy_\1(void)\n{',
...        'def myfunc():')
'static PyObject*\npy_myfunc(void)\n{'

如果 repl 是一个函数,则会为每次 pattern 的非重叠出现调用该函数。 该函数接受单个 Match 参数,并返回替换字符串。 例如

>>> def dashrepl(matchobj):
...     if matchobj.group(0) == '-': return ' '
...     else: return '-'
...
>>> re.sub('-{1,2}', dashrepl, 'pro----gram-files')
'pro--gram files'
>>> re.sub(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE)
'Baked Beans & Spam'

模式可以是字符串或 Pattern

可选参数 count 是要替换的模式出现的最大次数;count 必须是非负整数。如果省略或为零,则所有出现都将被替换。仅当空匹配不与先前的空匹配相邻时,才会替换模式的空匹配,因此 sub('x*', '-', 'abxd') 返回 '-a-b--d-'

在字符串类型的 repl 参数中,除了上面描述的字符转义和反向引用外,\g<name> 将使用由 (?P<name>...) 语法定义的名为 name 的组匹配的子字符串。\g<number> 使用对应的组编号;因此 \g<2> 等效于 \2,但在诸如 \g<2>0 的替换中不会产生歧义。\20 将被解释为对第 20 组的引用,而不是对第 2 组的引用,后跟字面字符 '0'。反向引用 \g<0> 将替换为正则表达式匹配的整个子字符串。

可以通过指定 flags 值来修改表达式的行为。 值可以是任何flags变量,使用按位或(| 运算符)组合。

在 3.1 版本中更改:添加了可选的 flags 参数。

在 3.5 版本中更改: 未匹配的组将替换为空字符串。

在 3.6 版本中更改: pattern 中由 '\' 和 ASCII 字母组成未知转义现在会报错。

在 3.7 版本中更改: repl 中由 '\' 和 ASCII 字母组成未知转义现在会报错。当模式的空匹配与先前的非空匹配相邻时,将被替换。

在 3.12 版本中更改: id 只能包含 ASCII 数字。在 bytes 替换字符串中,组 name 只能包含 ASCII 范围内的字节 (b'\x00'-b'\x7f')。

自 3.13 版本起弃用: countflags 作为位置参数传递已被弃用。在未来的 Python 版本中,它们将成为 仅限关键字的参数

re.subn(pattern, repl, string, count=0, flags=0)

执行与 sub() 相同的操作,但返回一个元组 (new_string, number_of_subs_made)

可以通过指定 flags 值来修改表达式的行为。 值可以是任何flags变量,使用按位或(| 运算符)组合。

re.escape(pattern)

转义 pattern 中的特殊字符。如果您想匹配可能包含正则表达式元字符的任意字面字符串,这将非常有用。例如

>>> print(re.escape('https://pythonlang.cn'))
https://www\.python\.org

>>> legal_chars = string.ascii_lowercase + string.digits + "!#$%&'*+-.^_`|~:"
>>> print('[%s]+' % re.escape(legal_chars))
[abcdefghijklmnopqrstuvwxyz0123456789!\#\$%\&'\*\+\-\.\^_`\|\~:]+

>>> operators = ['+', '-', '*', '/', '**']
>>> print('|'.join(map(re.escape, sorted(operators, reverse=True))))
/|\-|\+|\*\*|\*

此函数不能用于 sub()subn() 中的替换字符串,只有反斜杠应被转义。例如

>>> digits_re = r'\d+'
>>> sample = '/usr/sbin/sendmail - 0 errors, 12 warnings'
>>> print(re.sub(digits_re, digits_re.replace('\\', r'\\'), sample))
/usr/sbin/sendmail - \d+ errors, \d+ warnings

在 3.3 版本中更改: 不再转义 '_' 字符。

在 3.7 版本中更改: 仅转义在正则表达式中可能具有特殊含义的字符。因此,不再转义 '!', '"', '%', "'", ',', '/', ':', ';', '<', '=', '>', '@'"`"

re.purge()

清除正则表达式缓存。

异常

exception re.PatternError(msg, pattern=None, pos=None)

当传递给此处函数之一的字符串不是有效的正则表达式(例如,它可能包含不匹配的括号)时,或者在编译或匹配过程中发生其他错误时引发的异常。如果字符串不包含模式的匹配项,则永远不会发生错误。PatternError 实例具有以下附加属性

msg

未格式化的错误消息。

pattern

正则表达式模式。

pos

pattern 中编译失败的索引(可能为 None)。

lineno

pos 对应的行(可能为 None)。

colno

pos 对应的列(可能为 None)。

在 3.5 版本中更改: 添加了其他属性。

在 3.13 版本中更改: PatternError 最初名为 error;后者保留为向后兼容的别名。

正则表达式对象

class re.Pattern

re.compile() 返回的已编译正则表达式对象。

在 3.9 版本中更改: re.Pattern 支持 [] 来指示 Unicode (str) 或字节模式。请参阅 通用别名类型

Pattern.search(string[, pos[, endpos]])

扫描整个 string,查找此正则表达式产生匹配的第一个位置,并返回相应的 Match。如果字符串中没有位置与模式匹配,则返回 None;请注意,这与在字符串中的某个点找到零长度匹配不同。

可选的第二个参数 pos 给出了字符串中开始搜索的索引;它默认为 0。这并不完全等同于切片字符串; '^' 模式字符在字符串的实际开头和换行符后的位置匹配,但不一定在开始搜索的索引处匹配。

可选参数 endpos 限制了字符串的搜索范围;它就像字符串的长度为 endpos 个字符一样,因此只有从 posendpos - 1 的字符会被搜索匹配。如果 endpos 小于 pos,则不会找到匹配项;否则,如果 rx 是已编译的正则表达式对象,则 rx.search(string, 0, 50) 等效于 rx.search(string[:50], 0)

>>> pattern = re.compile("d")
>>> pattern.search("dog")     # Match at index 0
<re.Match object; span=(0, 1), match='d'>
>>> pattern.search("dog", 1)  # No match; search doesn't include the "d"
Pattern.match(string[, pos[, endpos]])

如果 string开头的零个或多个字符与此正则表达式匹配,则返回相应的 Match。如果字符串与模式不匹配,则返回 None;请注意,这与零长度匹配不同。

可选的 posendpos 参数的含义与 search() 方法相同。

>>> pattern = re.compile("o")
>>> pattern.match("dog")      # No match as "o" is not at the start of "dog".
>>> pattern.match("dog", 1)   # Match as "o" is the 2nd character of "dog".
<re.Match object; span=(1, 2), match='o'>

如果要在 string 中的任何位置查找匹配项,请改用 search()(另请参阅 search() 与 match())。

Pattern.fullmatch(string[, pos[, endpos]])

如果整个 string 与此正则表达式匹配,则返回一个对应的 Match。如果字符串与模式不匹配,则返回 None;请注意,这与零长度匹配不同。

可选的 posendpos 参数的含义与 search() 方法相同。

>>> pattern = re.compile("o[gh]")
>>> pattern.fullmatch("dog")      # No match as "o" is not at the start of "dog".
>>> pattern.fullmatch("ogre")     # No match as not the full string matches.
>>> pattern.fullmatch("doggie", 1, 3)   # Matches within given limits.
<re.Match object; span=(1, 3), match='og'>

在 3.4 版本中新增。

Pattern.split(string, maxsplit=0)

split() 函数相同,使用编译后的模式。

Pattern.findall(string[, pos[, endpos]])

findall() 函数类似,使用编译后的模式,但也接受可选的 posendpos 参数,这些参数限制搜索区域,就像 search() 一样。

Pattern.finditer(string[, pos[, endpos]])

finditer() 函数类似,使用编译后的模式,但也接受可选的 posendpos 参数,这些参数限制搜索区域,就像 search() 一样。

Pattern.sub(repl, string, count=0)

sub() 函数相同,使用编译后的模式。

Pattern.subn(repl, string, count=0)

subn() 函数相同,使用编译后的模式。

Pattern.flags

正则表达式匹配标志。这是传递给 compile() 的标志、模式中的任何 (?...) 内联标志以及隐式标志(例如,如果模式是 Unicode 字符串,则为 UNICODE)的组合。

Pattern.groups

模式中捕获组的数量。

Pattern.groupindex

一个字典,将由 (?P<id>) 定义的任何符号组名称映射到组编号。如果模式中未使用任何符号组,则字典为空。

Pattern.pattern

从中编译模式对象的模式字符串。

3.7 版本更改: 添加了对 copy.copy()copy.deepcopy() 的支持。编译后的正则表达式对象被认为是原子对象。

匹配对象

匹配对象的布尔值始终为 True。由于 match()search() 在没有匹配项时返回 None,因此可以使用简单的 if 语句测试是否存在匹配项

match = re.search(pattern, string)
if match:
    process(match)
class re.Match

由成功的 matchsearch 返回的匹配对象。

3.9 版本更改: re.Match 支持 [] 来指示 Unicode (str) 或字节匹配。请参阅 通用别名类型

Match.expand(template)

返回通过对模板字符串 template 执行反斜杠替换获得的字符串,就像 sub() 方法所做的那样。诸如 \n 之类的转义符被转换为相应的字符,数字反向引用(\1, \2)和命名反向引用(\g<1>, \g<name>)将替换为相应组的内容。反向引用 \g<0> 将被整个匹配项替换。

在 3.5 版本中更改: 未匹配的组将替换为空字符串。

Match.group([group1, ...])

返回匹配的一个或多个子组。如果只有一个参数,则结果是单个字符串;如果有多个参数,则结果是包含每个参数一个项的元组。如果没有参数,则 group1 默认为零(返回整个匹配项)。如果 groupN 参数为零,则相应的返回值是整个匹配字符串;如果它在 [1..99] 的包含范围内,则它是与相应带括号的组匹配的字符串。如果组号为负数或大于模式中定义的组数,则会引发 IndexError 异常。如果组包含在模式中不匹配的部分中,则相应的结果为 None。如果组包含在模式中匹配多次的部分中,则返回最后一个匹配项。

>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m.group(0)       # The entire match
'Isaac Newton'
>>> m.group(1)       # The first parenthesized subgroup.
'Isaac'
>>> m.group(2)       # The second parenthesized subgroup.
'Newton'
>>> m.group(1, 2)    # Multiple arguments give us a tuple.
('Isaac', 'Newton')

如果正则表达式使用了 (?P<name>...) 语法,则 groupN 参数也可以是字符串,通过其组名来标识组。如果一个字符串参数没有被用作模式中的组名,则会引发 IndexError 异常。

一个稍微复杂的例子

>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.group('first_name')
'Malcolm'
>>> m.group('last_name')
'Reynolds'

命名组也可以通过索引来引用

>>> m.group(1)
'Malcolm'
>>> m.group(2)
'Reynolds'

如果一个组匹配多次,则只能访问最后一次匹配

>>> m = re.match(r"(..)+", "a1b2c3")  # Matches 3 times.
>>> m.group(1)                        # Returns only the last match.
'c3'
Match.__getitem__(g)

这与 m.group(g) 相同。这允许更轻松地从匹配中访问单个组

>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m[0]       # The entire match
'Isaac Newton'
>>> m[1]       # The first parenthesized subgroup.
'Isaac'
>>> m[2]       # The second parenthesized subgroup.
'Newton'

也支持命名组

>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Isaac Newton")
>>> m['first_name']
'Isaac'
>>> m['last_name']
'Newton'

在 3.6 版本中添加。

Match.groups(default=None)

返回一个元组,其中包含匹配的所有子组,从 1 到模式中存在的组数。default 参数用于那些没有参与匹配的组;它默认为 None

例如

>>> m = re.match(r"(\d+)\.(\d+)", "24.1632")
>>> m.groups()
('24', '1632')

如果我们使小数点及其后的所有内容变为可选,则并非所有组都可能参与匹配。除非给出 default 参数,否则这些组将默认为 None

>>> m = re.match(r"(\d+)\.?(\d+)?", "24")
>>> m.groups()      # Second group defaults to None.
('24', None)
>>> m.groups('0')   # Now, the second group defaults to '0'.
('24', '0')
Match.groupdict(default=None)

返回一个字典,其中包含匹配的所有命名子组,键为子组名称。default 参数用于那些没有参与匹配的组;它默认为 None。例如

>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.groupdict()
{'first_name': 'Malcolm', 'last_name': 'Reynolds'}
Match.start([group])
Match.end([group])

返回 group 匹配的子字符串的起始和结束索引;group 默认为零(表示整个匹配的子字符串)。如果 group 存在但没有参与匹配,则返回 -1。对于匹配对象 m 和参与匹配的组 g,组 g 匹配的子字符串(等效于 m.group(g))为

m.string[m.start(g):m.end(g)]

请注意,如果 group 匹配一个空字符串,则 m.start(group) 将等于 m.end(group)。例如,在 m = re.search('b(c?)', 'cba') 之后,m.start(0) 为 1,m.end(0) 为 2,m.start(1)m.end(1) 都为 2,并且 m.start(2) 引发 IndexError 异常。

一个从电子邮件地址中删除 *remove_this* 的示例

>>> email = "tony@tiremove_thisger.net"
>>> m = re.search("remove_this", email)
>>> email[:m.start()] + email[m.end():]
'[email protected]'
Match.span([group])

对于匹配 m,返回 2 元组 (m.start(group), m.end(group))。请注意,如果 group 没有参与匹配,则为 (-1, -1)group 默认为零,即整个匹配。

Match.pos

传递给 search()match() 方法的 正则表达式对象pos 值。这是正则表达式引擎开始查找匹配的字符串中的索引。

Match.endpos

传递给 search()match() 方法的 正则表达式对象endpos 值。这是正则表达式引擎将不会超出范围的字符串中的索引。

Match.lastindex

最后匹配的捕获组的整数索引,如果根本没有匹配任何组,则为 None。例如,如果将表达式 (a)b((a)(b))((ab)) 应用于字符串 'ab',则它们的 lastindex == 1,而如果将表达式 (a)(b) 应用于同一字符串,则 lastindex == 2

Match.lastgroup

最后匹配的捕获组的名称,如果该组没有名称,或者根本没有匹配任何组,则为 None

Match.re

正则表达式对象match()search() 方法生成此匹配实例。

Match.string

传递给 match()search() 的字符串。

在 3.7 版本中更改: 增加了对 copy.copy()copy.deepcopy() 的支持。匹配对象被认为是原子的。

正则表达式示例

检查一对

在此示例中,我们将使用以下辅助函数来更优雅地显示匹配对象

def displaymatch(match):
    if match is None:
        return None
    return '<Match: %r, groups=%r>' % (match.group(), match.groups())

假设您正在编写一个扑克程序,其中玩家的手牌表示为 5 个字符的字符串,每个字符代表一张牌,“a” 代表 ace,“k” 代表 king,“q” 代表 queen,“j” 代表 jack,“t” 代表 10,以及“2”到“9”代表该值的牌。

要查看给定的字符串是否是有效的手牌,可以执行以下操作

>>> valid = re.compile(r"^[a2-9tjqk]{5}$")
>>> displaymatch(valid.match("akt5q"))  # Valid.
"<Match: 'akt5q', groups=()>"
>>> displaymatch(valid.match("akt5e"))  # Invalid.
>>> displaymatch(valid.match("akt"))    # Invalid.
>>> displaymatch(valid.match("727ak"))  # Valid.
"<Match: '727ak', groups=()>"

最后一张手牌 "727ak" 包含一对,或两张相同值的牌。要使用正则表达式匹配此项,可以使用反向引用,如下所示

>>> pair = re.compile(r".*(.).*\1")
>>> displaymatch(pair.match("717ak"))     # Pair of 7s.
"<Match: '717', groups=('7',)>"
>>> displaymatch(pair.match("718ak"))     # No pairs.
>>> displaymatch(pair.match("354aa"))     # Pair of aces.
"<Match: '354aa', groups=('a',)>"

要找出该对由哪张牌组成,可以使用匹配对象的 group() 方法,如下所示

>>> pair = re.compile(r".*(.).*\1")
>>> pair.match("717ak").group(1)
'7'

# Error because re.match() returns None, which doesn't have a group() method:
>>> pair.match("718ak").group(1)
Traceback (most recent call last):
  File "<pyshell#23>", line 1, in <module>
    re.match(r".*(.).*\1", "718ak").group(1)
AttributeError: 'NoneType' object has no attribute 'group'

>>> pair.match("354aa").group(1)
'a'

模拟 scanf()

Python 目前没有与 scanf() 等效的功能。正则表达式通常比 scanf() 格式字符串更强大,但也更冗长。下表提供了 scanf() 格式标记和正则表达式之间的一些或多或少的等效映射。

scanf() 标记

正则表达式

%c

.

%5c

.{5}

%d

[-+]?\d+

%e, %E, %f, %g

[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?

%i

[-+]?(0[xX][\dA-Fa-f]+|0[0-7]*|\d+)

%o

[-+]?[0-7]+

%s

\S+

%u

\d+

%x, %X

[-+]?(0[xX])?[\dA-Fa-f]+

要从如下字符串中提取文件名和数字

/usr/sbin/sendmail - 0 errors, 4 warnings

你将使用类似这样的 scanf() 格式

%s - %d errors, %d warnings

等效的正则表达式将是

(\S+) - (\d+) errors, (\d+) warnings

search() 与 match()

Python 基于正则表达式提供不同的原始操作

  • re.match() 仅检查字符串的开头是否有匹配项

  • re.search() 检查字符串中的任何位置是否有匹配项(这是 Perl 默认执行的操作)

  • re.fullmatch() 检查整个字符串是否匹配

例如

>>> re.match("c", "abcdef")    # No match
>>> re.search("c", "abcdef")   # Match
<re.Match object; span=(2, 3), match='c'>
>>> re.fullmatch("p.*n", "python") # Match
<re.Match object; span=(0, 6), match='python'>
>>> re.fullmatch("r.*n", "python") # No match

'^' 开头的正则表达式可以与 search() 一起使用,以限制匹配在字符串的开头

>>> re.match("c", "abcdef")    # No match
>>> re.search("^c", "abcdef")  # No match
>>> re.search("^a", "abcdef")  # Match
<re.Match object; span=(0, 1), match='a'>

请注意,在 MULTILINE 模式下,match() 仅在字符串的开头进行匹配,而使用以 '^' 开头的正则表达式的 search() 将在每行的开头进行匹配。

>>> re.match("X", "A\nB\nX", re.MULTILINE)  # No match
>>> re.search("^X", "A\nB\nX", re.MULTILINE)  # Match
<re.Match object; span=(4, 5), match='X'>

制作电话簿

split() 将字符串按照传入的模式分隔成列表。此方法对于将文本数据转换为 Python 可以轻松读取和修改的数据结构非常有用,如下面的示例演示了如何创建电话簿。

首先,这是输入。通常它可能来自文件,这里我们使用三引号字符串语法

>>> text = """Ross McFluff: 834.345.1254 155 Elm Street
...
... Ronald Heathmore: 892.345.3428 436 Finley Avenue
... Frank Burger: 925.541.7625 662 South Dogwood Way
...
...
... Heather Albrecht: 548.326.4584 919 Park Place"""

条目由一个或多个换行符分隔。现在,我们将字符串转换为列表,其中每个非空行都有自己的条目

>>> entries = re.split("\n+", text)
>>> entries
['Ross McFluff: 834.345.1254 155 Elm Street',
'Ronald Heathmore: 892.345.3428 436 Finley Avenue',
'Frank Burger: 925.541.7625 662 South Dogwood Way',
'Heather Albrecht: 548.326.4584 919 Park Place']

最后,将每个条目拆分为包含名字、姓氏、电话号码和地址的列表。我们使用 split()maxsplit 参数,因为地址中包含空格,也就是我们的分隔模式

>>> [re.split(":? ", entry, maxsplit=3) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155 Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436 Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662 South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919 Park Place']]

:? 模式匹配姓氏后的冒号,以便它不出现在结果列表中。使用 4maxsplit,我们可以将门牌号从街道名称中分离出来

>>> [re.split(":? ", entry, maxsplit=4) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155', 'Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436', 'Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662', 'South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919', 'Park Place']]

文本处理

sub() 将模式的每次出现替换为字符串或函数的结果。此示例演示如何使用 sub() 和函数来“mung”文本,或随机化句子中每个单词的所有字符的顺序,但第一个和最后一个字符除外

>>> def repl(m):
...     inner_word = list(m.group(2))
...     random.shuffle(inner_word)
...     return m.group(1) + "".join(inner_word) + m.group(3)
...
>>> text = "Professor Abdolmalek, please report your absences promptly."
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Poefsrosr Aealmlobdk, pslaee reorpt your abnseces plmrptoy.'
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Pofsroser Aodlambelk, plasee reoprt yuor asnebces potlmrpy.'

查找所有副词

findall() 匹配模式的所有出现,而不仅仅是像 search() 那样的第一个。例如,如果作者想在某些文本中查找所有副词,他们可能会以下列方式使用 findall()

>>> text = "He was carefully disguised but captured quickly by police."
>>> re.findall(r"\w+ly\b", text)
['carefully', 'quickly']

查找所有副词及其位置

如果需要有关模式所有匹配项的更多信息,而不仅仅是匹配的文本,finditer() 非常有用,因为它提供 Match 对象而不是字符串。继续前面的示例,如果作者想要在某些文本中查找所有副词及其位置,他们将以下列方式使用 finditer()

>>> text = "He was carefully disguised but captured quickly by police."
>>> for m in re.finditer(r"\w+ly\b", text):
...     print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))
07-16: carefully
40-47: quickly

原始字符串表示法

原始字符串表示法(r"text")使正则表达式保持理智。如果没有它,正则表达式中的每个反斜杠('\')都必须加上另一个反斜杠来转义它。例如,以下两行代码在功能上是相同的

>>> re.match(r"\W(.)\1\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>
>>> re.match("\\W(.)\\1\\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>

当需要匹配字面反斜杠时,必须在正则表达式中对其进行转义。使用原始字符串表示法,这意味着 r"\\"。不使用原始字符串表示法,则必须使用 "\\\\",这使得以下几行代码在功能上是相同的

>>> re.match(r"\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>
>>> re.match("\\\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>

编写一个分词器

分词器或扫描器 分析字符串以对字符组进行分类。这是编写编译器或解释器的有用的第一步。

文本类别用正则表达式指定。该技术是将它们组合成一个主正则表达式,并循环遍历连续的匹配项

from typing import NamedTuple
import re

class Token(NamedTuple):
    type: str
    value: str
    line: int
    column: int

def tokenize(code):
    keywords = {'IF', 'THEN', 'ENDIF', 'FOR', 'NEXT', 'GOSUB', 'RETURN'}
    token_specification = [
        ('NUMBER',   r'\d+(\.\d*)?'),  # Integer or decimal number
        ('ASSIGN',   r':='),           # Assignment operator
        ('END',      r';'),            # Statement terminator
        ('ID',       r'[A-Za-z]+'),    # Identifiers
        ('OP',       r'[+\-*/]'),      # Arithmetic operators
        ('NEWLINE',  r'\n'),           # Line endings
        ('SKIP',     r'[ \t]+'),       # Skip over spaces and tabs
        ('MISMATCH', r'.'),            # Any other character
    ]
    tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification)
    line_num = 1
    line_start = 0
    for mo in re.finditer(tok_regex, code):
        kind = mo.lastgroup
        value = mo.group()
        column = mo.start() - line_start
        if kind == 'NUMBER':
            value = float(value) if '.' in value else int(value)
        elif kind == 'ID' and value in keywords:
            kind = value
        elif kind == 'NEWLINE':
            line_start = mo.end()
            line_num += 1
            continue
        elif kind == 'SKIP':
            continue
        elif kind == 'MISMATCH':
            raise RuntimeError(f'{value!r} unexpected on line {line_num}')
        yield Token(kind, value, line_num, column)

statements = '''
    IF quantity THEN
        total := total + price * quantity;
        tax := price * 0.05;
    ENDIF;
'''

for token in tokenize(statements):
    print(token)

分词器产生以下输出

Token(type='IF', value='IF', line=2, column=4)
Token(type='ID', value='quantity', line=2, column=7)
Token(type='THEN', value='THEN', line=2, column=16)
Token(type='ID', value='total', line=3, column=8)
Token(type='ASSIGN', value=':=', line=3, column=14)
Token(type='ID', value='total', line=3, column=17)
Token(type='OP', value='+', line=3, column=23)
Token(type='ID', value='price', line=3, column=25)
Token(type='OP', value='*', line=3, column=31)
Token(type='ID', value='quantity', line=3, column=33)
Token(type='END', value=';', line=3, column=41)
Token(type='ID', value='tax', line=4, column=8)
Token(type='ASSIGN', value=':=', line=4, column=12)
Token(type='ID', value='price', line=4, column=15)
Token(type='OP', value='*', line=4, column=21)
Token(type='NUMBER', value=0.05, line=4, column=23)
Token(type='END', value=';', line=4, column=27)
Token(type='ENDIF', value='ENDIF', line=5, column=4)
Token(type='END', value=';', line=5, column=9)
[Frie09]

Friedl, Jeffrey. Mastering Regular Expressions. 3rd ed., O’Reilly Media, 2009. 本书的第三版不再涵盖 Python,但第一版详细介绍了编写优秀的正则表达式模式。