On this page
re —正则表达式运算
源代码: Lib/re.py
该模块提供与 Perl 中类似的正则表达式匹配操作。
模式和要搜索的字符串都可以是 Unicode 字符串(str)以及 8 位字符串(bytes)。但是,不能将 Unicode 字符串和 8 位字符串混合使用:也就是说,您不能将 Unicode 字符串与字节模式匹配,反之亦然;类似地,当要求替换时,替换字符串必须与模式和搜索字符串具有相同的类型。
正则表达式使用反斜杠字符('\')表示特殊形式或允许使用特殊字符而无需调用特殊含义。这与 Python 在字符串 Literals 中出于相同目的使用相同字符的目的相冲突。例如,要匹配 Literals 反斜杠,可能必须写'\\\\'作为模式字符串,因为正则表达式必须为\\,并且每个反斜杠必须在常规 Python 字符串 Literals 中表示为\\。另外,请注意,Python 在字符串 Literals 中使用反斜杠的任何无效转义序列现在都会生成DeprecationWarning,将来将变为SyntaxError。即使它是正则表达式的有效转义序列,也会发生此行为。
解决方案是将 Python 的原始字符串表示法用于正则表达式模式。反斜杠不会以任何特殊方式处理以'r'开头的字符串 Literals。因此r"\n"是一个包含'\'和'n'的两个字符的字符串,而"\n"是一个包含换行符的一个单字符的字符串。通常,模式将使用此原始字符串表示法在 Python 代码中表示。
重要的是要注意,大多数正则表达式操作都可以在编译正则表达式上用作模块级函数和方法。这些函数是快捷方式,不需要您首先编译一个 regex 对象,但是会错过一些微调参数。
正则表达式语法
正则表达式(或 RE)指定一组与之匹配的字符串;该模块中的函数可让您检查特定字符串是否与给定的正则表达式匹配(或给定的正则表达式是否与特定的字符串匹配,这取决于同一件事)。
可以将正则表达式连接起来以形成新的正则表达式。如果* A 和 B 都是正则表达式,则 AB 也是正则表达式。通常,如果字符串 p 匹配 A ,而另一个字符串 q 匹配 B ,则字符串 pq 将匹配 AB。除非 A 或 B *包含低优先级运算,否则这将成立。 * A 和 B *之间的边界条件;或具有编号的组引用。因此,可以从简单的原始表达式(如此处所述的表达式)轻松构造复杂的表达式。有关正则表达式的理论和实现的详细信息,请参阅 Friedl 书[Frie09]或几乎所有有关编译器构造的教科书。
下面是对正则表达式格式的简要说明。有关更多信息和更柔和的介绍,请查阅正则表达式操作方法。
正则表达式可以包含特殊字符和普通字符。大多数普通字符(例如'A','a'或'0')是最简单的正则表达式;他们只是匹配自己。您可以连接普通字符,因此last与字符串'last'匹配。 (在本节的其余部分中,我们将在this special style中编写 RE,通常不带引号,并在'in single quotes'处匹配字符串.)
某些字符(例如'|'或'(')很特殊。特殊字符要么代表普通字符类,要么影响它们周围正则表达式的解释方式。
重复限定符(*,+,?,{m,n}等)不能直接嵌套。这样可以避免与非贪婪修饰符后缀?以及其他实现中的其他修饰符产生歧义。为了将第二次重复应用于内部重复,可以使用括号。例如,表达式(?:a{6})*匹配六个'a'字符的任何倍数。
特殊字符为:
.- (点)在默认模式下,它匹配除换行符以外的任何字符。如果已指定DOTALL标志,则它匹配包括换行符在内的任何字符。
^- (Caret.)匹配字符串的开头,并且在MULTILINE模式下,每个换行符之后也立即匹配。
$
*- 使结果 RE 与前面的 RE 的 0 个或多个重复匹配,并尽可能多地重复。
ab*将匹配“ a”,“ ab”或“ a”,后跟任意数量的“ b”。
- 使结果 RE 与前面的 RE 的 0 个或多个重复匹配,并尽可能多地重复。
+- 使结果 RE 匹配先前 RE 的 1 个或多个重复。
ab+将匹配“ a”,后跟任意非零数目的“ b”;它不会仅匹配“ a”。
- 使结果 RE 匹配先前 RE 的 1 个或多个重复。
?- 使结果 RE 匹配先前 RE 的 0 或 1 个重复。
ab?将匹配“ a”或“ ab”。
- 使结果 RE 匹配先前 RE 的 0 或 1 个重复。
*?,+?,??'*','+'和'?'限定词都是贪婪;它们匹配尽可能多的文本。有时,这种行为是不希望的。如果 RE<.*>与'<a> b <c>'匹配,它将匹配整个字符串,而不仅仅是'<a>'。在限定符之后加上?使其以非贪婪或最小的方式执行匹配;因为最少个字符将被匹配。使用 RE<.*?>仅匹配'<a>'。
{m}- 指定应该完全匹配先前 RE 的* m *个副本;较少的匹配会导致整个 RE 不匹配。例如,
a{6}将完全匹配六个'a'字符,但不能完全匹配五个。
- 指定应该完全匹配先前 RE 的* m *个副本;较少的匹配会导致整个 RE 不匹配。例如,
{m,n}- 使结果 RE 匹配先前 RE 的* m 到 n 个重复,并try匹配尽可能多的重复。例如,
a{3,5}将匹配 3 到 5'a'个字符。Ellipsis m 指定下限为零,Ellipsis n *指定无限上限。例如,a{4,}b将匹配'aaaab'或一千个'a'字符,后跟'b',但不匹配'aaab'。逗号不能Ellipsis,否则修饰符会与前面描述的形式混淆。
- 使结果 RE 匹配先前 RE 的* m 到 n 个重复,并try匹配尽可能多的重复。例如,
{m,n}?- 使结果 RE 匹配先前 RE 的* m 至 n 个重复,并try尽可能少地匹配*。这是前一个限定词的非贪婪版本。例如,在 6 个字符的字符串
'aaaaaa'上,a{3,5}将匹配 5 个'a'字符,而a{3,5}?将仅匹配 3 个字符。
- 使结果 RE 匹配先前 RE 的* m 至 n 个重复,并try尽可能少地匹配*。这是前一个限定词的非贪婪版本。例如,在 6 个字符的字符串
\- 要么转义特殊字符(允许您匹配
'*','?'等字符),要么发出特殊序列的 signal;或者特殊序列将在下面讨论。
- 要么转义特殊字符(允许您匹配
如果您不使用原始字符串来表达模式,请记住 Python 还使用反斜杠作为字符串 Literals 中的转义序列。如果 Python 的解析器无法识别转义序列,则结果字符串中将包含反斜杠和后续字符。但是,如果 Python 将识别出结果序列,则应将反斜杠重复两次。这是复杂且难以理解的,因此强烈建议您对除最简单的表达式以外的所有表达式使用原始字符串。
[]- 用于指示一组字符。在一组中:
字符可以单独列出,例如
[amk]将匹配'a','m'或'k'。可以pass给出两个字符并用
'-'隔开来表示字符范围,例如[a-z]将匹配任何小写 ASCII 字母,[0-5][0-9]将匹配从00到59的所有两位数字,而[0-9A-Fa-f]将匹配任何十六进制数字。如果-被转义了(例如[a\-z]),或者被放置为第一个或最后一个字符(例如[-a]或[a-]),它将与 Literals'-'匹配。特殊字符在集合内失去其特殊含义。例如,
[(+*)]将匹配任何 Literals 字符'(','+','*'或')'。
可以pass对集合进行“补充”来匹配不在范围内的字符。如果集合的第一个字符是
'^',则集合中所有* not *的字符都将匹配。例如,[^5]将匹配'5'以外的任何字符,而[^^]将匹配'^'以外的任何字符。^不是集合中的第一个字符,则没有特殊含义。要匹配集合中的 Literals
']',请在其前面加上反斜杠,或将其放在集合的开头。例如,[()[\]{}]和[]()[{}]都将匹配括号。
- 将来可能会添加对Unicode 技术标准#18中的嵌套集和集操作的支持。这将更改语法,因此为了便于进行此更改,目前在歧义情况下会引发FutureWarning。这包括以 Literals
'['开头或包含 Literals 字符序列'--','&&','~~'和'||'的集合。为避免警告,请使用反斜杠将其转义。
在版本 3.7 中更改:如果字符集包含将来会在语义上更改的构造,则会引发FutureWarning。
|A|B(* A 和 B 可以是任意 RE)可以创建匹配 A 或 B 的正则表达式。'|'可以用这种方式分隔任意数量的 RE。也可以在组内使用(请参阅下文)。扫描目标字符串时,从左到右try用'|'分隔的 RE。当一个模式完全匹配时,该分支被接受。这意味着,一旦 A 匹配,即使将产生更长的整体匹配,也不会进一步测试 B *。换句话说,'|'运算符从不贪婪。要匹配 Literals'|',请使用\|或将其包含在字符类中,如[|]所示。
(...)- 匹配括号内的任何正则表达式,并指示组的开始和结束;可以在执行匹配后检索组的内容,并且以后可以在字符串中使用
\number特殊序列进行匹配,如下所述。要匹配 Literals'('或')',请使用\(或\),或将它们括在字符类[(],[)]中。
- 匹配括号内的任何正则表达式,并指示组的开始和结束;可以在执行匹配后检索组的内容,并且以后可以在字符串中使用
(?...)- 这是一个扩展符号(在
'('之后的'?'否则没有意义)。'?'之后的第一个字符确定该构造的含义和其他语法。扩展通常不会创建新的组。(?P<name>...)是此规则的唯一 exception。以下是当前支持的扩展。
- 这是一个扩展符号(在
(?aiLmsux)- (一组
'a','i','L','m','s','u','x'中的一个或多个字母.)该组匹配空字符串;字母设置相应的标志:re.A(仅 ASCII 匹配),re.I(忽略大小写),re.L(取决于语言环境),re.M(多行),re.S(所有点都匹配),re.U(Unicode 匹配)和re.X(详细信息),用于整个正则表达式。 (标志在Module Contents中描述。)如果您希望将标志包括在正则表达式中,而不是向re.compile()函数传递* flag *参数,则这很有用。标志应首先在表达式字符串中使用。
- (一组
(?:...)- 非捕获版本的常规括号。匹配括号内的任何正则表达式,但执行匹配后无法检索由组*匹配的子字符串,或稍后在模式中引用。
(?aiLmsux-imsx:...)
字母'a','L'和'u'用作内联标志时是互斥的,因此它们不能组合或跟随'-'。而是,当其中一个出现在内联组中时,它将覆盖封闭组中的匹配模式。在 Unicode 模式中,(?a:...)切换到仅 ASCII 匹配,(?u:...)切换到 Unicode 匹配(默认)。在字节模式中,(?L:...)切换到与语言环境有关的匹配,而(?a:...)切换到仅 ASCII 匹配(默认)。此替代仅对狭窄的嵌入式组有效,并且原始匹配模式在该组之外恢复。
3.6 版的新Function。
在版本 3.7 中更改:字母'a','L'和'u'也可以在组中使用。
(?P<name>...)- 与常规括号类似,但是可以pass符号组名* name *访问组匹配的子字符串。组名必须是有效的 Python 标识符,并且每个组名必须在正则表达式中仅定义一次。符号组也是带编号的组,就像未命名该组一样。
可以在三个上下文中引用命名组。如果格式为(?P<quote>['"]).*?(?P=quote)(即匹配用单引号或双引号引起来的字符串):
| 引用组“ quote”的上下文 | 引用方式 |
|---|---|
| 以相同的模式 | (?P=quote)(如图所示) |
\1 |
|
| 当处理匹配对象* m * | 时m.group('quote')m.end('quote')(等) |
在传递给re.sub()的* repl *参数的字符串中 |
\g<quote>\g<1>\1 |
(?P=name)- 对命名组的反向引用;它匹配早期名为* name *的组匹配的任何文本。
(?#...)- 一条 Comment;括号中的内容将被忽略。
(?=...)- 如果
...下一个匹配,则匹配,但不使用任何字符串。这称为超前 assert。例如,Isaac (?=Asimov)仅在其后跟'Asimov'时才匹配'Isaac '。
- 如果
(?!...)- 如果下一个
...不匹配则匹配。这是否定超前 assert。例如,Isaac (?!Asimov)仅当* not *后跟'Asimov'时才匹配'Isaac '。
- 如果下一个
(?<=...)
>>> 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 中进行了更改:添加了对固定长度的组引用的支持。
(?<!...)- 如果字符串的当前位置之前没有
...的匹配项,则匹配。这称为assert 后 assert。类似于肯定的后置 assert,所包含的模式必须仅匹配某个固定长度的字符串。以否定式后向 assert 开头的模式可能与要搜索的字符串的开头匹配。
- 如果字符串的当前位置之前没有
(?(id/name)yes-pattern|no-pattern)- 如果存在具有给定* id 或 name *的组,则try与
yes-pattern匹配;如果不存在,则try与no-pattern匹配。no-pattern是可选的,可以Ellipsis。例如,(<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$)是较差的电子邮件匹配模式,它将与'<user@host.com>'以及'user@host.com'匹配,但不会与'<user@host.com'或'user@host.com>'匹配。
- 如果存在具有给定* id 或 name *的组,则try与
特殊序列由'\'和下面列表中的字符组成。如果普通字符不是 ASCII 数字或 ASCII 字母,则结果 RE 将与第二个字符匹配。例如,\$匹配字符'$'。
\number- 匹配相同编号组的内容。组从 1 开始编号。例如,
(.+) \1匹配'the the'或'55 55',但不匹配'thethe'(请注意组后面的空格)。此特殊序列只能用于匹配前 99 个组之一。如果* number 的第一位数字为 0,或者 number 的长度为 3 个八进制数字,则不会将其解释为组匹配,而是将其解释为具有八进制值 number *的字符。在字符类的'['和']'内,所有数字转义符均视为字符。
- 匹配相同编号组的内容。组从 1 开始编号。例如,
\A- 仅在字符串开头匹配。
\b- 匹配空字符串,但仅匹配单词的开头或结尾。单词定义为单词字符序列。请注意,在形式上,
\b被定义为\w和\W字符之间的边界(反之亦然)或\w与字符串的开头/结尾之间的边界。这意味着r'\bfoo\b'与'foo','foo.','(foo)','bar foo baz'匹配,但与'foobar'或'foo3'不匹配。
- 匹配空字符串,但仅匹配单词的开头或结尾。单词定义为单词字符序列。请注意,在形式上,
默认情况下,Unicode 字母数字是 Unicode 模式中使用的字母数字,但是可以使用ASCII标志进行更改。如果使用LOCALE标志,则单词边界由当前语言环境确定。在字符范围内,\b表示退格字符,以与 Python 的字符串 Literals 兼容。
\B
\d-
对于 Unicode(str)模式:
匹配任何 Unicode 十进制数字(即 Unicode 字符类别[Nd]中的任何字符)。这包括
[0-9],以及许多其他数字字符。如果使用ASCII标志,则仅[0-9]被匹配。
对于 8 位(字节)模式:
- 匹配任何十进制数字;这等效于
[0-9]。
- 匹配任何十进制数字;这等效于
-
\D- 匹配任何非十进制数字的字符。这与
\d相反。如果使用ASCII标志,则等效于[^0-9]。
- 匹配任何非十进制数字的字符。这与
\s-
对于 Unicode(str)模式:
匹配 Unicode 空格字符(包括
[ \t\n\r\f\v],以及许多其他字符,例如,多种语言的排版规则所规定的不间断空格)。如果使用ASCII标志,则仅[ \t\n\r\f\v]被匹配。
对于 8 位(字节)模式:
- 匹配 ASCII 字符集中被视为空格的字符;这等效于
[ \t\n\r\f\v]。
- 匹配 ASCII 字符集中被视为空格的字符;这等效于
-
\S- 匹配不是空格字符的任何字符。这与
\s相反。如果使用ASCII标志,则等效于[^ \t\n\r\f\v]。
- 匹配不是空格字符的任何字符。这与
\w
\Z- 仅在字符串末尾匹配。
正则表达式解析器也接受 Python 字符串 Literals 支持的大多数标准转义:
\a \b \f \n
\N \r \t \u
\U \v \x \\
(请注意,\b用于表示单词边界,仅在字符类内部表示“退格”.)
'\u','\U'和'\N'转义序列仅以 Unicode 模式识别。在字节模式中,它们是错误。 ASCII 字母的未知转义符保留供将来使用,并被视为错误。
八进制转义符以有限形式提供。如果第一个数字为 0,或者有三个八进制数字,则被视为八进制转义。否则,它是一个组引用。对于字符串 Literals,八进制转义符的长度始终最多为三位数。
在版本 3.3 中进行了更改:已添加'\u'和'\U'转义序列。
在版本 3.6 中更改:由'\'和 ASCII 字母组成的未知转义现在是错误。
在版本 3.8 中更改:已添加'\N{name}'转义序列。与字符串 Literals 一样,它扩展为命名的 Unicode 字符(例如'\N{EM DASH}')。
Module Contents
该模块定义了几个函数,常量和一个异常。其中一些Function是用于编译正则表达式的全Function方法的简化版本。大多数非平凡的应用程序始终使用已编译的表单。
在版本 3.6 中更改:标志常量现在是RegexFlag的实例,而RegexFlag是enum.IntFlag的子类。
可以pass指定* flags *值来修改表达式的行为。值可以是以下任何变量,可以使用按位或(|运算符)进行组合。
The sequence
prog = re.compile(pattern)
result = prog.match(string)
相当于
result = re.match(pattern, string)
但是,当在单个程序中多次使用表达式时,使用re.compile()并保存生成的正则表达式对象以供重用会更有效。
Note
传递给re.compile()的最新模式的已编译版本和模块级匹配函数将被缓存,因此一次仅使用几个正则表达式的程序不必担心会编译正则表达式。
re.Are.ASCII- 使
\w,\W,\b,\B,\d,\D,\s和\S执行仅 ASCII 匹配,而不是完全 Unicode 匹配。这仅对 Unicode 模式有意义,而对于字节模式则忽略。对应于内联标志(?a)。
- 使
请注意,为了向后兼容,re.U标志(以及其同义词re.UNICODE和其嵌入式对应项(?u))仍然存在,但是在 Python 3 中它们是多余的,因为默认情况下字符串的匹配项是 Unicode(并且 Unicode 匹配项不允许用于字节) )。
re.DEBUG- 显示有关已编译表达式的调试信息。没有相应的内联标志。
re.Ire.IGNORECASE
请注意,当 Unicode 模式[a-z]或[A-Z]与IGNORECASE标志结合使用时,它们将匹配 52 个 ASCII 字母和 4 个其他非 ASCII 字母:'İ'(U 0130,拉丁大写字母 I,带点号),' ı”(U 0131,拉丁小写字母无点 i),“ ſ”(U 017F,拉丁小写字母 long s)和“ K”(U 212A,开尔文符号)。如果使用ASCII标志,则仅匹配字母“ a”至“ z”和“ A”至“ Z”。
re.Lre.LOCALE- 取决于当前语言环境,进行
\w,\W,\b,\B和不区分大小写的匹配。该标志只能与字节模式一起使用。不建议使用此标志,因为语言环境机制非常不可靠,它一次仅处理一种“区域性”,并且仅适用于 8 位语言环境。默认情况下,Python 3 中已经为 Unicode(str)模式启用了 Unicode 匹配,并且能够处理不同的语言环境/语言。对应于内联标志(?L)。
- 取决于当前语言环境,进行
在版本 3.6 中更改:re.LOCALE只能与字节模式一起使用,并且与re.ASCII不兼容。
在版本 3.7 中进行了更改:具有re.LOCALE标志的已编译正则表达式对象不再依赖于编译时的语言环境。只有匹配时的语言环境会影响匹配结果。
re.Mre.MULTILINE- 指定时,模式字符
'^'在字符串的开头和每行的开头(紧随每个换行符之后)匹配;模式字符'$'在字符串的末尾和每行的末尾(紧接在每个换行符之前)匹配。默认情况下,'^'仅在字符串的开头匹配,而'$'仅在字符串的末尾匹配,并且在字符串的末尾紧接换行符(如果有)。对应于内联标志(?m)。
- 指定时,模式字符
re.Sre.DOTALL- 使
'.'特殊字符完全匹配任何字符,包括换行符;如果没有此标志,则'.'将匹配*除换行符之外的所有内容。对应于内联标志(?s)。
- 使
re.Xre.VERBOSE- 该标志允许您直观地分隔模式的逻辑部分并添加 Comments,从而使您可以编写看起来更美观,更易读的正则表达式。模式中的空格将被忽略,除非在字符类中,在未转义的反斜杠之前或在
*?,(?:或(?P<...>之类的标记中。当一行中包含不是字符类中的#且没有未转义的反斜杠时,则从最左边的#到该行末尾的所有字符都将被忽略。
- 该标志允许您直观地分隔模式的逻辑部分并添加 Comments,从而使您可以编写看起来更美观,更易读的正则表达式。模式中的空格将被忽略,除非在字符类中,在未转义的反斜杠之前或在
这意味着以下两个与十进制数匹配的正则表达式对象在Function上相等:
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.search(* pattern , string , flags = 0 *)- 遍历* string 寻找正则表达式 pattern *产生匹配的第一个位置,并返回对应的match object。如果字符串中没有位置与模式匹配,则返回
None;请注意,这不同于在字符串中的某个点找到零长度匹配。
- 遍历* string 寻找正则表达式 pattern *产生匹配的第一个位置,并返回对应的match object。如果字符串中没有位置与模式匹配,则返回
re.match(* pattern , string , flags = 0 *)- 如果* string 开头的零个或多个字符与正则表达式 pattern *匹配,则返回相应的match object。如果字符串与模式不匹配,则返回
None;请注意,这与零长度匹配不同。
- 如果* string 开头的零个或多个字符与正则表达式 pattern *匹配,则返回相应的match object。如果字符串与模式不匹配,则返回
请注意,即使在MULTILINE模式下,re.match()也只会在字符串的开头而不是每行的开头匹配。
如果要在* string *中的任何位置找到匹配项,请改用search()(另请参见search()与 match())。
re.fullmatch(* pattern , string , flags = 0 *)- 如果整个* string 与正则表达式 pattern *匹配,则返回相应的match object。如果字符串与模式不匹配,则返回
None;请注意,这与零长度匹配不同。
- 如果整个* string 与正则表达式 pattern *匹配,则返回相应的match object。如果字符串与模式不匹配,则返回
3.4 版的新Function。
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', '...', '', '', '']
在版本 3.1 中更改:添加了可选的 flags 参数。
在版本 3.7 中进行了更改:添加了对可能与空字符串匹配的模式进行拆分的支持。
re.findall(* pattern , string , flags = 0 *)- 以字符串列表形式返回* string 中所有 pattern 的非重叠匹配。从左到右扫描 string *,并以找到的 Sequences 返回匹配项。如果模式中存在一个或多个组,则返回一个组列表;否则,返回一个列表。如果模式包含多个组,则这将是一个 Tuples 列表。空匹配项包含在结果中。
在版本 3.7 中进行了更改:现在可以在上一个空匹配之后立即开始非空匹配。
re.finditer(* pattern , string , flags = 0 *)- 对于* string *中的 RE * pattern ,在所有不重叠的匹配中返回iterator产生match objects。从左到右扫描 string *,并以找到的 Sequences 返回匹配项。空匹配项包含在结果中。
在版本 3.7 中进行了更改:现在可以在上一个空匹配之后立即开始非空匹配。
re.sub(* pattern , repl , string , count = 0 , flags = 0 *)- 返回pass用替换* repl 替换 string 中最左边的 pattern 发生的字符串。如果找不到该模式,则 string *保持不变。 * repl *可以是字符串或函数;如果是字符串,则处理其中的任何反斜杠转义。也就是说,
\n被转换为单个换行符,\r被转换为回车,依此类推。 ASCII 字母的未知转义符保留供将来使用,并被视为错误。其他未知的转义字符,例如\&,任其保留。反向引用(例如\6)被模式中第 6 组匹配的子字符串替换。例如:
- 返回pass用替换* repl 替换 string 中最左边的 pattern 发生的字符串。如果找不到该模式,则 string *保持不变。 * repl *可以是字符串或函数;如果是字符串,则处理其中的任何反斜杠转义。也就是说,
>>> 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 object参数,并返回替换字符串。例如:
>>> 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 object。
可选参数* count *是要替换的最大模式出现次数; * count *必须为非负整数。如果Ellipsis或为零,将替换所有出现的事件。模式的空匹配仅在不与先前的空匹配相邻时才被替换,因此sub('x*', '-', 'abxd')返回'-a-b--d-'。
在字符串类型* repl *参数中,除了上述的字符转义和反向引用外,\g<name>将使用由(?P<name>...)语法定义的,与名为name的组匹配的子字符串。 \g<number>使用相应的组号;因此,\g<2>等效于\2,但在诸如\g<2>0之类的替换中并没有歧义。 \20将被解释为对组 20 的引用,而不是对后跟 Literals 字符'0'的组 2 的引用。反向引用\g<0>替换 RE 匹配的整个子字符串。
在版本 3.1 中更改:添加了可选的 flags 参数。
在版本 3.5 中进行了更改:不匹配的组将替换为空字符串。
在版本 3.6 中进行了更改:现在,由'\'和 ASCII 字母组成的* pattern *中的未知转义符是错误。
在版本 3.7 中进行了更改:* repl *中由'\'和 ASCII 字母组成的未知转义符现在是错误。
在版本 3.7 中进行了更改:与先前的非空匹配项相邻时,将替换模式的空匹配项。
re.subn(* pattern , repl , string , count = 0 , flags = 0 *)- 执行与sub()相同的操作,但返回 Tuples
(new_string, number_of_subs_made)。
- 执行与sub()相同的操作,但返回 Tuples
在版本 3.1 中更改:添加了可选的 flags 参数。
在版本 3.5 中进行了更改:不匹配的组将替换为空字符串。
re.escape(模式)- 在* pattern *中转义特殊字符。如果要匹配可能包含正则表达式元字符的任意 Literals 字符串,这将很有用。例如:
>>> print(re.escape('http://www.python.org'))
http://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.error(* msg , pattern = None , pos = None *)
- 当传递给此处的一个函数的字符串不是有效的正则表达式(例如,它可能包含不匹配的括号)时,或者在编译或匹配过程中发生其他错误时,将引发异常。如果字符串不包含任何模式匹配,则永远不会出错。错误实例具有以下附加属性:
- exception *
msg- 未格式化的错误消息。
pattern- 正则表达式模式。
pos-
- pattern *中编译失败的索引(可能是
None)。
- pattern *中编译失败的索引(可能是
-
lineno- 对应于* pos *的行(可以是
None)。
- 对应于* pos *的行(可以是
colno- 对应于* pos *的列(可以是
None)。
- 对应于* pos *的列(可以是
在版本 3.5 中进行了更改:添加了其他属性。
正则表达式对象
编译的正则表达式对象支持以下方法和属性:
Pattern.search(* string * [,* pos * [,* endpos *]])- 扫描* string *以查找此正则表达式产生匹配项的第一个位置,然后返回相应的match object。如果字符串中没有位置与模式匹配,则返回
None;请注意,这不同于在字符串中的某个点找到零长度匹配。
- 扫描* string *以查找此正则表达式产生匹配项的第一个位置,然后返回相应的match object。如果字符串中没有位置与模式匹配,则返回
可选的第二个参数* 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 的 beginning *处的零个或多个字符与此正则表达式匹配,则返回相应的match object。如果字符串与模式不匹配,则返回
None;请注意,这与零长度匹配不同。
- 如果* string 的 beginning *处的零个或多个字符与此正则表达式匹配,则返回相应的match object。如果字符串与模式不匹配,则返回
可选的* 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 object。如果字符串与模式不匹配,则返回
None;请注意,这与零长度匹配不同。
- 如果整个* string *匹配此正则表达式,则返回相应的match object。如果字符串与模式不匹配,则返回
可选的* 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 版的新Function。
Pattern.split(* string , maxsplit = 0 *)- 与split()函数相同,使用已编译的模式。
Pattern.findall(* string * [,* pos * [,* endpos *]])Pattern.finditer(* string * [,* pos * [,* endpos *]])- 与finditer()函数类似,使用已编译的模式,但也接受可选的* pos 和 endpos *参数,它们限制了搜索区域,例如search()。
Pattern.sub(* repl , string , count = 0 *)- 与sub()函数相同,使用已编译的模式。
Pattern.subn(* repl , string , count = 0 *)- 与subn()函数相同,使用已编译的模式。
Pattern.flags- 正则表达式匹配标志。这是给compile()的标志,模式中的任何
(?...)内联标志和隐式标志(如UNICODE)的组合,如果该模式是 Unicode 字符串。
- 正则表达式匹配标志。这是给compile()的标志,模式中的任何
Pattern.groups- 模式中的捕获组数。
Pattern.groupindex- 字典将
(?P<id>)定义的任何符号组名 Map 到组号。如果模式中未使用任何符号组,则词典为空。
- 字典将
Pattern.pattern- 从其编译模式对象的模式字符串。
在 3.7 版中进行了更改:添加了对copy.copy()和copy.deepcopy()的支持。编译后的正则表达式对象被认为是原子的。
Match Objects
匹配对象的布尔值始终为True。由于match()和search()在没有匹配项时返回None,因此您可以使用简单的if语句来测试是否存在匹配项:
match = re.search(pattern, string)
if match:
process(match)
匹配对象支持以下方法和属性:
Match.expand(* template *)- 返回pass对模板字符串* template *进行反斜杠替换而获得的字符串,就像sub()方法一样。诸如
\n的转义符将转换为适当的字符,并且数字后向引用(\1,\2)和命名后向引用(\g<1>,\g<name>)被相应组的内容替换。
- 返回pass对模板字符串* template *进行反斜杠替换而获得的字符串,就像sub()方法一样。诸如
在版本 3.5 中进行了更改:不匹配的组将替换为空字符串。
Match.group([* group1 , ... *])- 返回匹配项的一个或多个子组。如果有单个参数,则结果为单个字符串;否则,结果为单个字符串。如果有多个参数,则结果是一个 Tuples,每个参数有一个项目。没有参数,* group1 默认为零(返回整个匹配项)。如果 groupN *参数为零,则对应的返回值为整个匹配字符串;否则,返回值为 0.如果它在包含范围[1..99]中,则它是与相应括号组匹配的字符串。如果组号为负或大于模式中定义的组数,则会引发IndexError异常。如果在不匹配的模式的一部分中包含组,则对应的结果为
None。如果在多次匹配的模式的一部分中包含一个组,则返回最后一个匹配项。
- 返回匹配项的一个或多个子组。如果有单个参数,则结果为单个字符串;否则,结果为单个字符串。如果有多个参数,则结果是一个 Tuples,每个参数有一个项目。没有参数,* group1 默认为零(返回整个匹配项)。如果 groupN *参数为零,则对应的返回值为整个匹配字符串;否则,返回值为 0.如果它在包含范围[1..99]中,则它是与相应括号组匹配的字符串。如果组号为负或大于模式中定义的组数,则会引发IndexError异常。如果在不匹配的模式的一部分中包含组,则对应的结果为
>>> 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 *参数也可以是pass组名标识组的字符串。如果在模式中未将字符串参数用作组名,则会引发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'
命名组也可以pass其索引来引用:
>>> 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'
3.6 版的新Function。
Match.groups(默认=无)- 返回一个包含匹配项所有子组的 Tuples,从 1 到模式中的许多组。 * default *参数用于未参加 match 的组;默认为
None。
- 返回一个包含匹配项所有子组的 Tuples,从 1 到模式中的许多组。 * default *参数用于未参加 match 的组;默认为
For example:
>>> m = re.match(r"(\d+)\.(\d+)", "24.1632")
>>> m.groups()
('24', '1632')
如果我们将小数位及其后的所有内容设为可选,则并非所有组都可以参加 match。除非给出* 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(默认=无)- 返回一个字典,其中包含该匹配项的所有* named *子组,并以子组名称为关键字。 * default *参数用于未参加 match 的组;默认为
None。例如:
- 返回一个字典,其中包含该匹配项的所有* named *子组,并以子组名称为关键字。 * default *参数用于未参加 match 的组;默认为
>>> 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))为
- 返回与* group *匹配的子字符串的开始和结束的索引; * group 默认为零(表示整个匹配的子字符串)。如果 group 存在但不参与匹配,则返回
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 ,返回 2Tuples
(m.start(group), m.end(group))。请注意,如果 group *没有参与 match,则为(-1, -1)。 * group *默认为零,即整个匹配。
- 对于匹配* m ,返回 2Tuples
Match.pos- 传递给regex object的search()或match()方法的* pos *值。这是 RE 引擎开始寻找匹配项的字符串索引。
Match.endpos- 传递给regex object的search()或match()方法的* endpos *的值。这是字符串的索引,RE 引擎将超出该索引。
Match.lastindex- 最后一个匹配的捕获组的整数索引;如果根本没有匹配的组,则返回
None。例如,表达式(a)b,((a)(b))和((ab))如果应用于字符串'ab'将具有lastindex == 1,而表达式(a)(b)如果应用于同一字符串将具有lastindex == 2。
- 最后一个匹配的捕获组的整数索引;如果根本没有匹配的组,则返回
Match.lastgroup- 最后匹配的捕获组的名称;如果该组没有名称,或者根本没有匹配的组,则返回
None。
- 最后匹配的捕获组的名称;如果该组没有名称,或者根本没有匹配的组,则返回
Match.reMatch.string
在 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”代表国王,“ q”代表皇后,“ 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或两个相同值的卡。为了将其与正则表达式匹配,可以使用如下反向引用:
>>> 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',)>"
要找出两人组成的卡,可以pass以下方式使用 match 对象的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'
Simulating scanf()
Python 当前没有等效于scanf()。正则表达式通常比scanf()格式字符串更强大,但也更冗长。下表提供了scanf()格式标记和正则表达式之间的等效 Map。
scanf()令牌 |
Regular Expression |
|---|---|
%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()vs. match()
Python 根据正则表达式提供了两种不同的基本操作:re.match()仅在字符串的开头检查匹配项,而re.search()在字符串的任何位置检查匹配项(这是 Perl 的默认设置)。
For example:
>>> re.match("c", "abcdef") # No match
>>> re.search("c", "abcdef") # Match
<re.Match object; span=(2, 3), match='c'>
以'^'开头的正则表达式可以与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 轻松读取和修改的数据结构非常有用,如以下创建电话簿的示例所示。
首先,这是 Importing。通常它可能来自文件,这里我们使用三引号字符串语法
>>> 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']
最后,将每个条目分成一个包含名字,姓氏,电话 Numbers 和地址的列表。我们使用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']]
:?模式与姓氏后的冒号匹配,因此它不会出现在结果列表中。使用_3 的maxsplit,我们可以将门牌 Numbers 与街道名称分开:
>>> [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']]
Text Munging
sub()用字符串或函数结果替换每次出现的模式。本示例演示使用带有函数sub()的“ munge”文本或随机化句子中每个单词中除第一个和最后一个字符外的所有字符的 Sequences:
>>> 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", text)
['carefully', 'quickly']
查找所有副词及其位置
如果要获取有关模式所有匹配项的信息多于匹配文本,则finditer()很有用,因为它提供match objects而不是字符串。continue 前面的示例,如果作者希望在某些文本中找到所有副词及其位置,则可以按以下方式使用finditer():
>>> text = "He was carefully disguised but captured quickly by police."
>>> for m in re.finditer(r"\w+ly", text):
... print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))
07-16: carefully
40-47: quickly
原始字符串表示法
原始字符串符号(r"text")使正则表达式保持理智。如果没有它,则正则表达式中的每个反斜杠('\')都必须加上另一个前缀,以使其转义。例如,以下两行代码在Function上相同:
>>> 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 '>
当要匹配 Literals 反斜杠时,必须在正则表达式中将其转义。如果使用原始字符串符号,则表示r"\\"。如果没有原始字符串符号,则必须使用"\\\\",从而使以下代码行在Function上相同:
>>> 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]
- 杰弗里·弗里 del。掌握正则表达式。第三版,O'Reilly Media,2009 年。该书的第三版不再涵盖 Python,但是第一版涵盖了编写良好的正则表达式模式的详细信息。