re
— 正则表达式操作¶
源代码: Lib/re/
此模块提供了类似于 Perl 中的正则表达式匹配操作。
模式和要搜索的字符串都可以是 Unicode 字符串(str
)以及 8 位字符串(bytes
)。但是,Unicode 字符串和 8 位字符串不能混用:也就是说,您不能将 Unicode 字符串与字节模式匹配,反之亦然;同样,在请求替换时,替换字符串的类型必须与模式和搜索字符串的类型相同。
正则表达式使用反斜杠字符 ('\'
) 来表示特殊形式或允许使用特殊字符而不调用其特殊含义。这与 Python 在字符串字面量中使用相同字符用于相同目的相冲突;例如,要匹配字面反斜杠,可能必须编写 '\\\\'
作为模式字符串,因为正则表达式必须是 \\
,并且每个反斜杠都必须在常规 Python 字符串字面量中表示为 \\
。另外,请注意,Python 在字符串字面量中使用反斜杠时,任何无效的转义序列现在都会生成 SyntaxWarning
,并且将来会变成 SyntaxError
。即使它是正则表达式的有效转义序列,也会发生此行为。
解决方案是对正则表达式模式使用 Python 的原始字符串表示法;在以 'r'
为前缀的字符串字面量中,反斜杠不会以任何特殊方式处理。所以 r"\n"
是一个包含 '\'
和 'n'
的双字符字符串,而 "\n"
是一个包含换行符的单字符字符串。通常,模式将在 Python 代码中使用这种原始字符串表示法来表示。
重要的是要注意,大多数正则表达式操作都可以作为模块级函数和 已编译正则表达式 上的方法使用。这些函数是快捷方式,不需要您先编译正则表达式对象,但会错过一些微调参数。
正则表达式语法¶
正则表达式(或 RE)指定与其匹配的一组字符串;此模块中的函数允许您检查特定字符串是否与给定的正则表达式匹配(或者给定的正则表达式是否与特定字符串匹配,这归结为同一件事)。
正则表达式可以连接起来形成新的正则表达式;如果 *A* 和 *B* 都是正则表达式,那么 *AB* 也是正则表达式。通常,如果字符串 *p* 与 *A* 匹配,而另一个字符串 *q* 与 *B* 匹配,则字符串 *pq* 将与 AB 匹配。除非 *A* 或 *B* 包含低优先级操作;*A* 和 *B* 之间的边界条件;或者具有编号的组引用,否则这成立。因此,可以很容易地从这里描述的更简单的原始表达式构造复杂的表达式。有关正则表达式理论和实现的详细信息,请参阅 Friedl 的书 [Frie09],或几乎任何关于编译器构造的教科书。
下面是对正则表达式格式的简要说明。有关更多信息和更温和的介绍,请参阅 正则表达式指南。
正则表达式可以包含特殊字符和普通字符。大多数普通字符,如 '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'
中搜索单个$
将找到两个(空)匹配项:一个在换行符之前,一个在字符串的结尾。
*
使生成的正则表达式匹配前面正则表达式的 0 次或多次重复,尽可能多地匹配。
ab*
将匹配“a”、“ab”或“a”后跟任意数量的“b”。
+
使生成的正则表达式匹配前面正则表达式的 1 次或多次重复。
ab+
将匹配“a”后跟任意非零数量的“b”;它不会只匹配“a”。
?
使生成的正则表达式匹配前面正则表达式的 0 次或 1 次重复。
ab?
将匹配“a”或“ab”。
*?
,+?
,??
'*'
、'+'
和'?'
量词都是贪婪的;它们匹配尽可能多的文本。有时不希望这种行为;如果正则表达式<.*>
与'<a> b <c>'
匹配,它将匹配整个字符串,而不仅仅是'<a>'
。在量词后面添加?
使其以非贪婪或最小的方式执行匹配;将匹配尽可能少的字符。使用正则表达式<.*?>
将只匹配'<a>'
。
*+
,++
,?+
与
'*'
、'+'
和'?'span>
量词一样,附加了'+'
的量词也会尽可能多地匹配。但是,与真正的贪婪量词不同,当其后的表达式匹配失败时,这些量词不允许回溯。这些被称为占有式量词。例如,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}
使生成的正则表达式匹配前面正则表达式的 m 到 n 次重复,尝试匹配尽可能多的重复。例如,
a{3,5}
将匹配 3 到 5 个'a'
字符。省略 m 指定下限为零,省略 n 指定上限为无穷大。例如,a{4,}b
将匹配'aaaab'
或一千个'a'
字符后跟一个'b'
,但不会匹配'aaab'
。逗号不能省略,否则修饰符会与前面描述的形式混淆。{m,n}?
使生成的正则表达式匹配前面正则表达式的 m 到 n 次重复,尝试匹配尽可能少的重复。这是前面量词的非贪婪版本。例如,在 6 个字符的字符串
'aaaaaa'
上,a{3,5}
将匹配 5 个'a'
字符,而a{3,5}?
将只匹配 3 个字符。{m,n}+
使生成的正则表达式匹配前面正则表达式的 m 到 n 次重复,尝试匹配尽可能多的重复,而不建立任何回溯点。这是上面量词的占有式版本。例如,在 6 个字符的字符串
'aaaaaa'
上,a{3,5}+aa
尝试匹配 5 个'a'
字符,然后,需要另外 2 个'a'
,将需要比可用字符更多的字符,因此失败,而a{3,5}aa
将匹配a{3,5}
捕获 5 个,然后通过回溯捕获 4 个'a'
,然后最后的 2 个'a'
由模式中的最后 2 个aa
匹配。x{m,n}+
等效于(?>x{m,n})
。3.11 版新增。
\
要么转义特殊字符(允许您匹配
'*'
、'?'
等字符),要么表示特殊序列;特殊序列将在下面讨论。如果您没有使用原始字符串来表示模式,请记住,Python 也使用反斜杠作为字符串字面量中的转义序列;如果 Python 的解析器无法识别转义序列,则反斜杠和后续字符将包含在生成的字符串中。但是,如果 Python 能够识别生成的序列,则应重复两次反斜杠。这很复杂且难以理解,因此强烈建议您对除最简单的表达式之外的所有表达式使用原始字符串。
[]
用于指示一组字符。在一个集合中
字符可以单独列出,例如
[amk]
将匹配'a'
、'm'
或'k'
。
可以通过给出两个字符并用
'-'
分隔它们来指示字符范围,例如[a-z]
将匹配任何小写 ASCII 字母,[0-5][0-9]
将匹配从00
到59
的所有两位数字,而[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* 匹配,就不会 further 测试 *B*,即使它会产生更长的整体匹配。换句话说,'|'
运算符永远不会贪婪。要匹配字面量'|'
,请使用\|
,或将其括在字符类中,如[|]
。
(...)
匹配圆括号内的任何正则表达式,并指示组的开始和结束;在执行匹配后可以检索组的内容,并且可以使用下面描述的
\number
特殊序列在字符串中稍后进行匹配。要匹配字面量'('
或')'
,请使用\(
或\)
,或将其括在字符类中:[(]
、[)]
。
(?...)
这是一种扩展符号(
'('
后面的'?'
否则没有意义)。'?'
后面的第一个字符决定了该结构的含义和 further 语法。扩展通常不会创建新组;(?P<name>...)
是此规则的唯一例外。以下是当前支持的扩展。(?aiLmsux)
(来自集合
'a'
、'i'
、'L'
、'm'
、's'
、'u'
、'x'
的一个或多个字母。)该组匹配空字符串;这些字母为整个正则表达式设置相应的标志(这些标志在 模块内容 中有描述。)如果您希望将标志作为正则表达式的一部分包含在内,而不是将 *flag* 参数传递给
re.compile()
函数,这将非常有用。标志应该在表达式字符串中首先使用。在 3.11 版更改: 此结构只能在表达式的开头使用。
(?:...)
正则括号的非捕获版本。匹配圆括号内的任何正则表达式,但与组匹配的子字符串在执行匹配后不能被检索,也不能在模式中稍后被引用。
(?aiLmsux-imsx:...)
(来自集合
'a'
、'i'
、'L'
、'm'
、's'
、'u'
、'x'
的零个或多个字母,可选后跟'-'
,再后跟来自'i'
、'm'
、's'
、'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)
仅当'Isaac '
后跟'Asimov'
时才会匹配它。
(?!...)
如果
...
接下来不匹配,则匹配。这是一个负先行断言。例如,Isaac (?!Asimov)
仅当'Isaac '
后面*不*跟'Asimov'
时才会匹配它。
(?<=...)
如果字符串中的当前位置前面是与
...
匹配的内容,并且该匹配在当前位置结束,则匹配。这称为正向后视断言。(?<=abc)def
将在'abcdef'
中找到匹配项,因为后视将备份 3 个字符并检查包含的模式是否匹配。包含的模式只能匹配某些固定长度的字符串,这意味着允许使用abc
或a|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-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 字母,则生成的 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
标志,则单词边界由当前语言环境确定。
\d
\D
匹配任何非十进制数字的字符。这与
\d
相反。如果使用
ASCII
标志,则匹配[^0-9]
。
\s
- 对于 Unicode (str) 模式
匹配 Unicode 空白字符(包括
[ \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)
。
- 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.7 版更改: 带有
LOCALE
标志的已编译正则表达式对象不再依赖于编译时的语言环境。只有匹配时的语言环境才会影响匹配结果。
- re.M¶
- re.MULTILINE¶
指定后,模式字符
'^'
在字符串的开头和每行的开头(紧跟在每个换行符之后)匹配;模式字符'$'
在字符串的结尾和每行的结尾(紧跟在每个换行符之前)匹配。默认情况下,'^'
仅在字符串的开头匹配,而'$'
仅在字符串的结尾和字符串结尾处的换行符(如果有)之前匹配。对应于内联标志
(?m)
。
- re.NOFLAG¶
表示未应用任何标志,值为
0
。此标志可用作函数关键字参数的默认值,或用作将与其他标志进行条件“或”运算的基础值。用作默认值的示例def myfunc(text, flag=re.NOFLAG): return re.match(text, flag)
3.11 版新增。
- re.U¶
- re.UNICODE¶
在 Python 3 中,默认情况下,
str
模式会匹配 Unicode 字符。因此,此标志是多余的,**不起作用**,仅保留以实现向后兼容性。请参阅
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() 与 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.', 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 版更改: 添加了对拆分可能匹配空字符串的模式的支持。
- re.findall(pattern, string, flags=0)¶
以字符串列表或元组列表的形式返回在 string 中所有与 pattern 不重叠的匹配。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 中正则表达式 pattern 的所有非重叠匹配生成
Match
对象。string 从左到右扫描,匹配项按找到的顺序返回。结果中包含空匹配。可以通过指定 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>
还将使用与名为name
的组匹配的子字符串,如(?P<name>...)
语法所定义。\g<number>
使用相应的组号;因此\g<2>
等效于\2
,但在\g<2>0
等替换中没有歧义。\20
将被解释为对第 20 组的引用,而不是对第 2 组的引用后跟字符'0'
。反向引用\g<0>
替换为与正则表达式匹配的整个子字符串。可以通过指定 flags 值来修改表达式的行为。值可以是任何 标志 变量,使用按位或运算符(
|
运算符)组合。在 3.1 版更改: 添加了可选的 flags 参数。
3.5 版后已变更: 不匹配的组将替换为空字符串。
3.6 版后已变更: pattern 中由
'\'
和 ASCII 字母组成的未知转义符现在是错误。3.7 版后已变更: repl 中由
'\'
和 ASCII 字母组成的未知转义符现在是错误。3.7 版后已变更: 当与先前的非空匹配项相邻时,将替换模式的空匹配项。
3.12 版后已变更: 组 id 只能包含 ASCII 数字。在
bytes
替换字符串中,组 name 只能包含 ASCII 范围内的字节(b'\x00'
-b'\x7f'
)。
- re.subn(pattern, repl, string, count=0, flags=0)¶
执行与
sub()
相同的操作,但返回一个元组(new_string, number_of_subs_made)
。在 3.1 版更改: 添加了可选的 flags 参数。
3.5 版后已变更: 不匹配的组将替换为空字符串。
可以通过指定 flags 值来修改表达式的行为。值可以是任何 标志 变量,使用按位或运算符(
|
运算符)组合。
- re.escape(pattern)¶
转义 pattern 中的特殊字符。如果要匹配可能包含正则表达式元字符的任意字面字符串,这将非常有用。例如
>>> print(re.escape('https://www.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()¶
清除正则表达式缓存。
异常¶
正则表达式对象¶
- 类 re.Pattern¶
由
re.compile()
返回的已编译正则表达式对象。版本 3.9 中变更:
re.Pattern
支持[]
来表示 Unicode (str) 或字节模式。请参阅 泛型别名类型。
- Pattern.search(string[, pos[, endpos]])¶
扫描 string,查找此正则表达式产生匹配项的第一个位置,并返回相应的
Match
。如果字符串中没有位置与模式匹配,则返回None
;请注意,这与在字符串中的某个点找到零长度匹配不同。可选的第二个参数 pos 给出了字符串中开始搜索的索引;它默认为
0
。这与切片字符串不完全等效;'^'
模式字符匹配字符串的真正开头和换行符之后的位置,但不一定匹配开始搜索的索引。可选参数 endpos 限制了字符串的搜索范围;就好像字符串的长度为 endpos 个字符一样,因此只会搜索从 pos 到
endpos - 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
;请注意,这与零长度匹配不同。可选的 pos 和 endpos 参数与
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
;请注意,这与零长度匹配不同。可选的 pos 和 endpos 参数与
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.findall(string[, pos[, endpos]])¶
与
findall()
函数类似,使用已编译的模式,但也接受可选的 pos 和 endpos 参数,这些参数像search()
一样限制搜索区域。
- Pattern.finditer(string[, pos[, endpos]])¶
类似于
finditer()
函数,使用已编译的模式,但也接受可选的 pos 和 endpos 参数,用于限制搜索区域,就像search()
一样。
- 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¶
由成功的
match
和search
返回的匹配对象。
- 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():] '[email protected]'
- Match.span([group])¶
对于匹配项 *m*,返回 2 元组
(m.start(group), m.end(group))
。请注意,如果 *group* 对匹配没有贡献,则返回(-1, -1)
。*group* 默认为零,即整个匹配项。
- Match.lastindex¶
最后一个匹配的捕获组的整数索引,如果根本没有匹配到任何组,则为
None
。例如,如果表达式(a)b
、((a)(b))
和((ab))
应用于字符串'ab'
,则它们的lastindex == 1
,而如果表达式(a)(b)
应用于相同的字符串,则其lastindex == 2
。
- Match.lastgroup¶
最后一个匹配的捕获组的名称,如果该组没有名称,或者根本没有匹配到任何组,则为
None
。
在 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()
格式标记和正则表达式之间的映射。
|
正则表达式 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
要从如下字符串中提取文件名和数字
/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, 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']]
:?
模式匹配姓氏后的冒号,因此它不会出现在结果列表中。 使用 4
的 maxsplit
,我们可以将门牌号与街道名称分开
>>> [re.split(":? ", entry, 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)
弗里德尔,杰弗里。《精通正则表达式》。 第三版,奥莱利媒体,2009 年。 该书的第三版不再涵盖 Python,但第一版详细介绍了如何编写良好的正则表达式模式。