re ---正则表达式操作

源代码: Lib/re.py


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

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

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

解决方案是将python的原始字符串表示法用于正则表达式模式;反斜杠在前缀为 'r' . 所以 r"\n" 是包含两个字符的字符串 '\''n' ,同时 "\n" 是一个包含换行符的单字符字符串。通常,模式将使用这个原始字符串表示法在Python代码中表示。

需要注意的是,大多数正则表达式操作作为模块级函数和方法在 compiled regular expressions . 这些函数是快捷方式,不需要您首先编译regex对象,但会丢失一些微调参数。

参见

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

正则表达式语法

正则表达式(或re)指定一组与之匹配的字符串;通过此模块中的函数,可以检查特定的字符串是否与给定的正则表达式匹配(或者给定的正则表达式是否与某个特定的字符串匹配,最终结果相同)。

正则表达式可以连接成新的正则表达式;如果 AB 都是正则表达式,那么 AB 也是正则表达式。一般来说,如果字符串 p 比赛 A 还有另一根绳子 q 比赛 B 字符串 pq 与AB匹配。除非 AB 包含低优先级操作;介于 AB ;或具有编号的组引用。因此,复杂表达式可以很容易地从简单的原始表达式(如本文所述)构造。有关正则表达式的理论和实现的详细信息,请参阅Friedl书。 [Frie09], 或者几乎任何关于编译器构造的教科书。

下面简要说明正则表达式的格式。有关更多信息和更温和的介绍,请咨询 正则表达式Howto .

正则表达式可以包含特殊字符和普通字符。最普通的人物,比如 'A''a''0' ,是最简单的正则表达式;它们只是匹配自己。可以连接普通字符,因此 last 匹配字符串 'last' . (在这一部分的其余部分,我们会写上re's in this special style ,通常不带引号和要匹配的字符串 'in single quotes'

一些人物,比如 '|''(' ,是特殊的。特殊字符或者代表普通字符的类,或者影响它们周围的正则表达式的解释方式。

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

特殊字符包括:

.

(点)。在默认模式下,它匹配除换行符以外的任何字符。如果 DOTALL 已指定标志,此标志与任何字符(包括换行符)匹配。

^

(插入符号。)匹配字符串的开头,并在 MULTILINE 模式也会在每行换行后立即匹配。

$

匹配字符串结尾或字符串结尾处的换行符之前,以及 MULTILINE 模式也与换行符之前匹配。 foo 同时匹配“foo”和“foobar”,而正则表达式 foo$ 只匹配“foo”。更有趣的是,寻找 foo.$ 在里面 'foo1\nfoo2\n' 正常匹配“foo2”,但“foo1”在 MULTILINE 模式;搜索单个 $ 在里面 'foo\n' 将找到两个(空)匹配项:一个位于换行符之前,一个位于字符串末尾。

*

使结果的re与前面的re的0个或多个重复匹配,尽可能多的重复。 ab* 将匹配“A”、“AB”或“A”,后跟任意数量的“B”。

+

使结果的re与前面的re的1个或多个重复匹配。 ab+ 将匹配“a”,后跟任何非零数的“b”;它不会只匹配“a”。

?

使结果的re与前面的re的0或1次重复匹配。 ab? 将匹配“a”或“ab”。

*?, +?, ??

这个 '*''+''?' 限定符都是 greedy 它们匹配尽可能多的文本。有时这种行为是不可取的;如果 <.*> 与…匹配 '<a> b <c>' ,它将匹配整个字符串,而不仅仅是 '<a>' .正在添加 ? 在限定符使其在 non-greedyminimal 时尚;作为 few 将尽可能匹配字符。使用RE <.*?> 将只匹配 '<a>' .

{m}

准确地指定 m 应匹配上一个RE的副本;匹配较少会导致整个RE不匹配。例如, a{{6}} 正好六个匹配 'a' 字符,但不是五个。

{m,n}

使结果re与 mn 重复前面的重复,试图匹配尽可能多的重复。例如, a{{3,5}} 将从3到5匹配 'a' 字符。省略 m 指定零的下限,并省略 n 指定无限上限。作为一个例子, a{{4,}}b 将匹配 'aaaab' 或一千 'a' 字符后跟一个 'b' ,但不是 'aaab' .逗号不能省略,否则修饰语将与前面描述的形式混淆。

{m,n}?

使结果re与 mn 重复前面的re,尝试匹配为 few 尽可能重复。这是前一个限定符的非贪婪版本。例如,在6个字符的字符串上 'aaaaaa'a{{3,5}} 将匹配5 'a' 字符,而 a{{3,5}}? 只匹配3个字符。

\

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

如果不使用原始字符串来表示模式,请记住,python还使用反斜杠作为字符串文本中的转义序列;如果python的解析器无法识别转义序列,则反斜杠和后续字符将包含在生成的字符串中。但是,如果Python能够识别结果序列,则反斜杠应该重复两次。这很复杂,很难理解,因此强烈建议您对除最简单表达式之外的所有表达式使用原始字符串。

[]

用于指示一组字符。在一组中:

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

  • 字符范围可以通过给出两个字符并用 '-' ,例如 [a-z] 将匹配任何小写的ASCII字母, [0-5][0-9] 将匹配来自的所有两位数 0059[0-9A-Fa-f] 将匹配任何十六进制数字。如果 - 被逃脱(例如 [a\-z] )或者将其作为第一个或最后一个字符(例如 [-a][a-] ,它将与文本匹配 '-' .

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

  • 字符类,如 \w\S (在下面定义)也可以在集合内接受,尽管它们匹配的字符取决于 ASCIILOCALE 模式生效。

  • 不在范围内的字符可以通过 complementing 集合。如果集合的第一个字符是 '^' ,所有字符 not 将在集合中匹配。例如, [^5] 将匹配除 '5'[^^] 将匹配除 '^' . ^ 如果不是集合中的第一个字符,则没有特殊意义。

  • 匹配文字 ']' 在集合内,在它前面加一个反斜杠,或者把它放在集合的开头。例如,两者 [()[\]{{}}][]()[{{}}] 都将与括号匹配。

  • 支持嵌套集和集合操作,如 Unicode Technical Standard #18 将来可能会添加。这将更改语法,因此为了方便此更改 FutureWarning 暂时将在不明确的情况下提出。包括以文字开头的集合 '[' 或包含文字字符序列 '--''&&''~~''||' . 为了避免出现警告,请用反斜杠将其转义。

在 3.7 版更改: FutureWarning 如果字符集包含将来将发生语义更改的构造,则引发。

|

A|B 在哪里 AB 可以是任意的res,创建一个正则表达式,该表达式将与 AB . 任意数量的Res可以用 '|' 这样。这也可以在组内使用(见下文)。当扫描目标字符串时,res由 '|' 从左到右尝试。当一个模式完全匹配时,接受该分支。这意味着有一次 A 比赛, B 将不会进一步测试,即使它将产生更长的整体匹配。也就是说, '|' 接线员从不贪心。匹配文字 '|' 使用 \| 或将其包含在字符类中,如 [|] .

(...)

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

(?...)

这是一个扩展符号(A '?' 紧随其后 '(' 否则没有意义)。后面的第一个字符 '?' 确定构造的含义和进一步的语法。扩展通常不会创建新的组; (?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 (verbose),用于整个正则表达式。(标记在 模块内容 .)如果希望将标志作为正则表达式的一部分包括在内,而不是传递 flag 参数 re.compile() 功能。应首先在表达式字符串中使用标志。

(?:...)

常规括号的非捕获版本。匹配括号内的任何正则表达式,但与组匹配的子字符串 不能 在执行匹配后检索,或在模式中稍后引用。

(?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' 也可以在组中使用。

(?P<name>...)

类似于常规括号,但组匹配的子字符串可以通过符号组名称访问。 name . 组名必须是有效的python标识符,并且每个组名在正则表达式中只能定义一次。符号组也是一个编号组,就像组没有命名一样。

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

引用“报价”组的上下文

参考方法

以同样的模式

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

  • \1

处理匹配对象时 m

  • m.group('quote')

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

在传递给 repl 的参数 re.sub()

  • \g<quote>

  • \g<1>

  • \1

(?P=name)

对命名组的后向引用;它与先前命名组匹配的任何文本匹配 name .

(?#...)

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

(?=...)

匹配如果 ... 匹配next,但不使用任何字符串。这叫A lookahead assertion . 例如, Isaac (?=Asimov) 将匹配 'Isaac ' 只有在后面跟着 'Asimov' .

(?!...)

匹配如果 ... 与下一个不匹配。这是一个 negative lookahead assertion . 例如, Isaac (?!Asimov) 将匹配 'Isaac ' 只要它是 not 然后 'Asimov' .

(?<=...)

如果字符串中的当前位置前面有匹配项,则匹配 ... 在当前位置结束。这叫A positive lookbehind assertion . (?<=abc)def 将在中找到匹配项 'abcdef' ,因为lookback将备份3个字符并检查包含的模式是否匹配。包含的模式必须只匹配某个固定长度的字符串,这意味着 abca|b 是允许的,但是 a*a{{3,4}} 不是。请注意,以正的lookbehind断言开头的模式在被搜索的字符串的开头将不匹配;您很可能希望使用 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 版更改: 增加了对固定长度组引用的支持。

(?<!...)

如果字符串中的当前位置前面没有匹配项,则匹配 ... . 这叫A negative lookbehind assertion . 与正查找断言类似,包含的模式必须只匹配某些固定长度的字符串。以负的lookbehind断言开头的模式可能在要搜索的字符串的开头匹配。

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

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

特殊序列包括 '\' 以及下面列表中的一个字符。如果普通字符不是一个ASCII数字或一个ASCII字母,那么得到的RE将与第二个字符匹配。例如, \$ 与角色匹配 '$' .

\number

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

\A

仅在字符串的开头匹配。

\b

匹配空字符串,但仅在单词的开头或结尾。单词被定义为单词字符序列。注意到正式, \b 定义为 \w 和A \W 字符(或反之亦然),或介于 \w 以及字符串的开头/结尾。这意味着 r'\bfoo\b' 比赛 'foo''foo.''(foo)''bar foo baz' 但不是 'foobar''foo3' .

默认情况下,Unicode字母数字是Unicode模式中使用的字母数字,但可以通过使用 ASCII flag。单词边界由当前区域设置确定,如果 LOCALE 使用标志。在字符范围内, \b 表示退格字符,以便与Python的字符串文本兼容。

\B

匹配空字符串,但仅当它是 not 在单词的开头或结尾。这意味着 r'py\B' 比赛 'python''py3''py2' ,但不是 'py''py.''py!' . \B 正好相反 \b ,所以Unicode模式中的字字符是Unicode字母数字或下划线,尽管可以通过使用 ASCII flag。单词边界由当前区域设置确定,如果 LOCALE 使用标志。

\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] .

\S

匹配任何不是空白字符的字符。这与 \s . 如果 ASCII 使用了标志,这相当于 [^ \t\n\r\f\v] .

\w
对于Unicode(str)模式:

匹配unicode单词字符;这包括在任何语言中可以作为单词一部分的大多数字符,以及数字和下划线。如果 ASCII 仅使用标志 [a-zA-Z0-9_] 是匹配的。

对于8位(字节)模式:

匹配ASCII字符集中考虑的字母数字字符;这相当于 [a-zA-Z0-9_] . 如果 LOCALE 使用标志,匹配当前区域设置中视为字母数字的字符和下划线。

\W

匹配任何不是单词字符的字符。这与 \w . 如果 ASCII 使用了标志,这相当于 [^a-zA-Z0-9_] . 如果 LOCALE 使用标志,匹配当前区域设置中既不是字母数字也不是下划线的字符。

\Z

只在字符串的末尾匹配。

python字符串文本支持的大多数标准转义也被正则表达式解析器接受:

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

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

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

八进制逃逸包括在一个有限的形式。如果第一个数字是0,或者有三个八进制数字,则视为八进制转义。否则,它是一个组引用。对于字符串文字,八进制转义符的长度通常最多为三位数。

在 3.3 版更改: 这个 '\u''\U' 已添加转义序列。

在 3.6 版更改: 未知的转义符包括 '\' 一个ASCII字母现在是错误的。

在 3.8 版更改: 这个 '\N{{name}}' 已添加转义序列。与字符串文本一样,它扩展到指定的Unicode字符(例如 '\N{{EM DASH}}'

模块内容

该模块定义了几个函数、常量和一个异常。有些函数是已编译正则表达式的完整功能方法的简化版本。大多数重要的应用程序总是使用已编译的表单。

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

re.compile(pattern, flags=0)

将正则表达式模式编译为 regular expression object ,可用于匹配 match()search() 以及其他方法,如下所述。

表达式的行为可以通过指定 flags 值。值可以是下列变量中的任意一个,使用按位或组合 | 操作员)。

序列如下:

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

等于:

result = re.match(pattern, string)

但使用 re.compile() 当表达式在一个程序中被多次使用时,将生成的正则表达式对象保存起来以便重用更为有效。

注解

传递给 re.compile() 模块级匹配函数被缓存,因此一次只使用几个正则表达式的程序不需要担心编译正则表达式。

re.A
re.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.I
re.IGNORECASE

执行不区分大小写的匹配;表达式如下 [A-Z] 也将匹配小写字母。完全Unicode匹配(例如 Ü 匹配 ü )也有效,除非 re.ASCII 标志用于禁用非ASCII匹配。当前区域设置不会更改此标志的效果,除非 re.LOCALE 也使用标志。对应于内联标志 (?i) .

注意,当Unicode模式 [a-z][A-Z]IGNORECASE 标记,它们将匹配52个ASCII字母和4个附加的非ASCII字母:“_”(U+0130,上面带点的拉丁文大写字母I)、“_”(U+0131,拉丁文小写字母无点I)、“_”(U+017F,拉丁文小写字母长S)和“_”(U+212A,开尔文符号)。如果 ASCII 使用标志,只匹配字母“a”到“z”和“a”到“z”。

re.L
re.LOCALE

制作 \w\W\b\B 和不区分大小写的匹配取决于当前的区域设置。此标志只能用于字节模式。不鼓励使用此标志,因为区域设置机制非常不可靠,它一次只处理一个“区域性”,而且它只适用于8位区域设置。在python 3中,默认情况下已经为unicode(str)模式启用了unicode匹配,并且它能够处理不同的区域设置/语言。对应于内联标志 (?L) .

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

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

re.M
re.MULTILINE

指定时,模式字符 '^' 匹配字符串的开头和每行的开头(紧跟每行换行符);以及模式字符 '$' 匹配字符串的结尾和每行的结尾(紧接着每行换行符的前面)。默认情况下, '^' 仅在字符串的开头匹配,并且 '$' 只在字符串的末尾,在字符串末尾的换行符(如果有)之前。对应于内联标志 (?m) .

re.S
re.DOTALL

使 '.' 特殊字符完全匹配任何字符,包括换行符;如果没有此标志, '.' 会匹配任何东西 除了 换行符对应于内联标志 (?s) .

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.search(pattern, string, flags=0)

扫描通过 string 查找正则表达式的第一个位置 模式 生成匹配项,并返回相应的 match object . 返回 None 如果字符串中没有与模式匹配的位置,请注意,这与在字符串的某个点上查找零长度匹配不同。

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

如果在 string 匹配正则表达式 模式 ,返回相应的 match object . 返回 None 如果字符串与模式不匹配,请注意这与零长度匹配不同。

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

如果你想在 string 使用 search() 相反(另请参见 search()与match()的比较

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

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

3.4 新版功能.

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

分裂 string 通过以下事件 模式 . 如果捕获括号用于 模式 ,则模式中所有组的文本也作为结果列表的一部分返回。如果 最大分割 最多为非零 最大分割 发生拆分,字符串的其余部分作为列表的最后一个元素返回。::

>>> 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 版更改: 添加了可选标志参数。

在 3.7 版更改: 添加了对可以匹配空字符串的模式的拆分支持。

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

返回所有不重叠的匹配项 模式 在里面 string ,作为字符串列表。这个 string 从左到右扫描,并按找到的顺序返回匹配项。如果模式中存在一个或多个组,则返回组列表;如果模式有多个组,则这将是元组列表。结果中包含空匹配项。

在 3.7 版更改: 非空匹配现在可以在前一个空匹配之后开始。

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

返回一 iterator 顺从的 match objects 所有不重叠的比赛 模式 在里面 string . 这个 string 从左到右扫描,并按找到的顺序返回匹配项。结果中包含空匹配项。

在 3.7 版更改: 非空匹配现在可以在前一个空匹配之后开始。

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

返回通过替换最左边不重叠的 模式 在里面 string 通过替换 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 是一个函数,它被调用为 模式 . 函数只需要一个 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 .

可选参数 计数 是要替换的模式出现的最大数量; 计数 必须是非负整数。如果省略或为零,所有出现的情况都将被替换。模式的空匹配仅在不与前一个空匹配相邻时替换,因此 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> 与re匹配的整个子字符串中的替换项。

在 3.1 版更改: 添加了可选标志参数。

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

在 3.6 版更改: 未知逃逸 模式 组成的 '\' 一个ASCII字母现在是错误的。

在 3.7 版更改: 未知逃逸 repl 组成的 '\' 一个ASCII字母现在是错误的。

在 3.7 版更改: 模式的空匹配项在与前一个非空匹配项相邻时被替换。

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

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

在 3.1 版更改: 添加了可选标志参数。

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

re.escape(pattern)

在中转义特殊字符 模式 . 如果要匹配可能包含正则表达式元字符的任意文本字符串,则此选项非常有用。例如::

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

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

msg

未格式化的错误消息。

pattern

正则表达式模式。

pos

指数 模式 编译失败(可能 None

lineno

对应的行 pos (可能是 None

colno

对应于的列 pos (可能是 None

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

正则表达式对象

已编译的正则表达式对象支持以下方法和属性:

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

扫描通过 string 查找此正则表达式生成匹配项的第一个位置,并返回 match object . 返回 None 如果字符串中没有与模式匹配的位置,请注意,这与在字符串的某个点上查找零长度匹配不同。

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

可选参数 结束位置 限制搜索字符串的范围;它将类似于 结束位置 字符长,因此只有来自 posendpos - 1 将搜索匹配项。如果 结束位置 小于 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 object . 返回 None 如果字符串与模式不匹配,请注意这与零长度匹配不同。

可选的 pos结束位置 参数的含义与 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 如果字符串与模式不匹配,请注意这与零长度匹配不同。

可选的 pos结束位置 参数的含义与 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() 函数,使用已编译的模式,但也接受可选的 pos结束位置 限制搜索区域的参数 search() .

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

类似于 finditer() 函数,使用已编译的模式,但也接受可选的 pos结束位置 限制搜索区域的参数 search() .

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

sub() 函数,使用已编译的模式。

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

subn() 函数,使用已编译的模式。

Pattern.flags

regex匹配标志。这是给定的标志的组合 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)

Match对象支持以下方法和属性:

Match.expand(template)

返回通过对模板字符串执行反斜杠替换获得的字符串 模板 ,由 sub() 方法。逃逸如 \n 转换为适当的字符,并返回数字引用 (\1\2 )和命名的backreferences (\g<1>\g<name> )替换为相应组的内容。

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

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

返回匹配的一个或多个子组。如果只有一个参数,则结果是一个字符串;如果有多个参数,则结果是一个元组,每个参数有一个项。没有参数, 组1 默认为零(返回整个匹配)。如果A 群PN 参数为零,则对应的返回值是整个匹配字符串;如果它在包含范围内 [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>...) 语法 群PN 参数也可以是通过组名标识组的字符串。如果字符串参数未在模式中用作组名,则 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'

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

返回与匹配的子字符串的开始和结束的索引 groupgroup 默认为零(表示整个匹配的子字符串)。返回 -1 如果 group 存在,但对匹配没有贡献。对于匹配对象 m 和一组 g 对匹配有贡献的子字符串 g (相当于 m.group(g) )是::

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

注意 m.start(group) 意志平等 m.end(group) 如果 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

价值 pos 传递给 search()match() A方法 regex object . 这是重新引擎开始寻找匹配项的字符串中的索引。

Match.endpos

价值 结束位置 传递给 search()match() A方法 regex object . 这是字符串中的索引,超出该索引,重新引擎将无法运行。

Match.lastindex

上次匹配的捕获组的整数索引,或 None 如果没有匹配的组。例如,表达式 (a)b((a)(b))((ab)) 将有 lastindex == 1 如果应用于字符串 'ab' ,而表达式 (a)(b) 将有 lastindex == 2 ,如果应用于同一字符串。

Match.lastgroup

上次匹配的捕获组的名称,或 None 如果组没有名称,或者根本没有匹配的组。

Match.re

这个 regular expression object 谁的 match()search() 方法生成了此匹配实例。

Match.string

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

在 3.7 版更改: 增加了对 copy.copy()copy.deepcopy() . 匹配对象被视为原子对象。

正则表达式示例

正在检查一对

在本例中,我们将使用下面的helper函数更优雅地显示匹配对象:

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

假设您正在编写一个扑克程序,其中玩家的手表示为一个5个字符的字符串,每个字符代表一张牌,“A”代表王牌,“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" 包含一对或两张相同价值的卡。要将其与正则表达式匹配,可以使用backreferences,例如:

>>> 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.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轻松读取和修改这些数据结构,如下面创建调用簿的示例所示。

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

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

最后,将每个条目分成一个列表,其中包含名字、姓氏、调用号码和地址。我们使用 maxsplit 参数 split() 因为地址中有空格,所以我们的拆分模式:

>>> [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']]

这个 :? 模式匹配姓氏后面的冒号,这样它就不会出现在结果列表中。用一个 maxsplit 属于 4 ,我们可以从街道名称中分离出房屋编号:

>>> [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() 比赛 all 模式的出现,而不仅仅是第一个 search() 做。例如,如果一个作家想在某些文本中找到所有副词,他们可能会使用 findall() 按以下方式:

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

找到所有副词及其位置

如果一个人想要更多关于模式所有匹配的信息,而不是匹配的文本, finditer() 是有用的,因为它提供 match objects 而不是字符串。继续前面的例子,如果一个作家想找到所有副词 以及他们的位置 在某些文本中,他们会使用 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" )保持正则表达式的健全。没有它,每一个反斜杠 ('\' )在正则表达式中,必须使用另一个前缀来转义它。例如,以下两行代码在功能上是相同的:

>>> 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='\\'>

写标记器

A tokenizer or scanner 分析字符串以对字符组进行分类。这是编写编译器或解释器的第一步。

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

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

弗里德,杰弗里。掌握正则表达式。第3版,O'Reilly Media,2009年。这本书的第三版已经不再涉及python了,但第一版则详细介绍了如何编写好的正则表达式模式。