re — 正则表达式操作

源代码: Lib/re/


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

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

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

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

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

参见

第三方 regex 模块的 API 与标准库 re 模块兼容,但提供了额外的功能和更彻底的 Unicode 支持。

正则表达式语法

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

正则表达式可以连接起来形成新的正则表达式;如果 *A* 和 *B* 都是正则表达式,那么 *AB* 也是一个正则表达式。通常,如果字符串 *p* 匹配 *A*,另一个字符串 *q* 匹配 *B*,则字符串 *pq* 将匹配 AB。除非 *A* 或 *B* 包含低优先级操作;*A* 和 *B* 之间的边界条件;或者具有编号组引用。因此,复杂的表达式可以很容易地由此处描述的简单基本表达式构建。有关正则表达式的理论和实现的详细信息,请查阅 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'。

+

导致生成的 RE 匹配前面 RE 的 1 次或更多次重复。ab+ 将匹配 'a' 后跟任意非零数量的 'b';它不会只匹配 'a'。

?

导致生成的 RE 匹配前面 RE 的 0 次或 1 次重复。ab? 将匹配 'a' 或 'ab'。

*?, +?, ??

'*''+''?' 量词都是 *贪婪的*;它们会尽可能多地匹配文本。有时不希望这种行为;如果 RE <.*>'<a> b <c>' 匹配,它将匹配整个字符串,而不仅仅是 '<a>'。在量词后添加 ? 会使其以 *非贪婪* 或 *最小* 方式执行匹配;将匹配 *尽可能少* 的字符。使用 RE <.*?> 将只匹配 '<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}

指定必须匹配前面 RE 的正好 *m* 个副本;匹配次数较少会导致整个 RE 不匹配。例如,a{6} 将匹配正好六个 'a' 字符,但不匹配五个。

{m,n}

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

{m,n}?

导致生成的 RE 匹配前面 RE 的 *m* 到 *n* 次重复,尝试匹配 *尽可能少* 的重复。这是前面量词的非贪婪版本。例如,在 6 个字符的字符串 'aaaaaa' 上,a{3,5} 将匹配 5 个 'a' 字符,而 a{3,5}? 将只匹配 3 个字符。

{m,n}+

导致生成的 RE 匹配前面 RE 的 *m* 到 *n* 次重复,尝试匹配尽可能多的重复 *而不* 建立任何回溯点。这是上述量词的独占版本。例如,在 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-]),它将匹配一个字面量 '-'

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

  • 反斜杠要么转义在集合中具有特殊含义的字符,例如 '-'']''^''\\' 本身,要么表示一个特殊序列,该序列代表单个字符,例如 \xa0\n,或者一个字符类,例如 \w\S(下面定义)。请注意,\b 表示单个“退格”字符,而不是集合外部的单词边界,并且数字转义,例如 \1 始终是八进制转义,而不是组引用。不允许不匹配单个字符的特殊序列,例如 \A\z

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

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

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

3.7 新版功能: 如果字符集包含将来语义会改变的构造,则会引发 FutureWarning

|

A|B,其中 *A* 和 *B* 可以是任意 RE,创建一个正则表达式,它将匹配 *A* 或 *B*。任意数量的 RE 可以用 '|' 以这种方式分隔。这也可以在组内部使用(见下文)。当目标字符串被扫描时,用 '|' 分隔的 RE 从左到右尝试。当一个模式完全匹配时,该分支被接受。这意味着一旦 *A* 匹配,*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 (详细)

(这些标志在模块内容中描述。)这对于希望将标志作为正则表达式的一部分而不是将 *flags* 参数传递给 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)

如果存在给定 *id* 或 *name* 的组,则尝试匹配 yes-pattern,如果不存在,则匹配 no-patternno-pattern 是可选的,可以省略。例如,(<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$) 是一个糟糕的电子邮件匹配模式,它将匹配 '<user@host.com>''user@host.com',但不匹配 '<user@host.com' 也不能匹配 'user@host.com>'

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

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

\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 标志,则单词边界由当前语言环境决定。

3.14 新版功能: \B 现在匹配空输入字符串。

\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

仅在字符串的末尾匹配。

在 3.14 版本加入。

\Z

\z 相同。为了兼容旧的 Python 版本。

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 匹配默认对 Unicode (str) 模式启用,并且能够处理不同的区域设置和语言。

3.6 新版功能: LOCALE 只能与字节模式一起使用,并且与 ASCII 不兼容。

3.7 新版功能: 带有 LOCALE 标志的已编译正则表达式对象不再在编译时依赖于语言环境。只有匹配时的语言环境会影响匹配结果。

re.M
re.MULTILINE

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

对应于内联标志 (?m)

re.NOFLAG

表示不应用任何标志,值为 0。此标志可用作函数关键字参数的默认值,或作为将有条件地与其它标志进行按位或操作的基值。用作默认值的示例

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* 值来修改。值可以是任何标志变量,使用位或(| 运算符)组合。

序列

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* 值来修改。值可以是任何标志变量,使用位或(| 运算符)组合。

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

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

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

如果你想在 *string* 中的任何位置找到匹配项,请改用 search() (另请参阅 search() vs. match())。

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

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

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

表达式的行为可以通过指定 *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* 值来修改。值可以是任何标志变量,使用位或(| 运算符)组合。

3.1 新版功能: 增加了可选的 flags 参数。

3.7 新版功能: 增加了对可匹配空字符串的模式进行拆分的支持。

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

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

返回 stringpattern 的所有非重叠匹配项,作为字符串列表或元组列表。 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* 值来修改。值可以是任何标志变量,使用位或(| 运算符)组合。

版本 3.7 中的变化: 非空匹配项现在可以在前一个空匹配项之后立即开始。

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

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

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

版本 3.7 中的变化: 非空匹配项现在可以在前一个空匹配项之后立即开始。

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

返回将 stringpattern 的最左边非重叠出现项替换为替换项 repl 后得到的字符串。 如果未找到模式,则返回未更改的 stringrepl 可以是字符串或函数;如果它是字符串,则其中所有反斜杠转义符都会被处理。 也就是说,\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-' 而不是 '-a-b-d-'

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

表达式的行为可以通过指定 *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* 值来修改。值可以是任何标志变量,使用位或(| 运算符)组合。

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')

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

>>> 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():]
'tony@tiger.net'
Match.span([group])

对于匹配 m,返回 2 元组 (m.start(group), m.end(group))。 请注意,如果 group 未对匹配做出贡献,则为 (-1, -1)group 默认为零,即整个匹配。

Match.pos

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

Match.endpos

传递给 search()match() 方法的 endpos 值,该方法属于 正则表达式对象。 这是 RE 引擎不会越过的字符串索引。

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”代表 A,“k”代表 K,“q”代表 Q,“j”代表 J,“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']]

:? 模式匹配姓氏后的冒号,因此它不会出现在结果列表中。使用 maxsplit4,我们可以将门牌号与街道名称分开

>>> [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() 来“混淆”文本,即随机化句子中每个单词中除第一个和最后一个字符之外的所有字符的顺序

>>> 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. 精通正则表达式。第三版,O'Reilly Media,2009。该书的第三版已不再涵盖 Python,但第一版详细介绍了如何编写优秀的正则表达式模式。