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

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

正则表达式使用反斜杠字符 ( '\') 来指示特殊形式或允许使用特殊字符而不调用其特殊含义。这与 Python 在字符串文字中出于相同目的使用相同字符相冲突;例如,要匹配文字反斜杠,可能必须写'\\\\'为模式字符串,因为正则表达式必须是\\,并且每个反斜杠必须\\在常规 Python 字符串文字中表示。

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

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

也可以看看, 第三方regex模块,其 API 与标准库re模块兼容,但提供附加功能和更全面的 Unicode 支持。

正则表达式语法

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

正则表达式可以串联起来形成新的正则表达式;如果A 和B都是正则表达式,那么AB也是正则表达式。一般来说,如果一个字符串p匹配A并且另一个字符串q匹配B,则字符串pq将匹配 AB。除非AB包含低优先级操作,否则这将成立;AB之间的边界条件; 或有编号的组参考。因此,复杂的表达式可以很容易地从这里描述的更简单的原始表达式构造出来。

下面简要说明正则表达式的格式。

正则表达式可以包含特殊字符和普通字符。大多数普通字符,如'A''a''0',都是最简单的正则表达式;他们只是匹配自己。您可以连接普通字符,因此last匹配字符串'last'。(在本节的其余部分中,我们将在 中编写 RE ,通常不带引号和要匹配的字符串。)this special style'in single quotes'

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

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

特殊字符是:

.(点)在默认模式下,这匹配除换行符以外的任何字符。如果DOTALL已指定标志,则匹配任何字符,包括换行符。
^(插入符号)匹配字符串的开头,并且在MULTILINE模式下也立即匹配每个换行符之后。
$匹配字符串的结尾或字符串末尾的换行符之前,并且在MULTILINE模式下也匹配换行符之前。 foo 匹配“foo”和“foobar”,而正则表达式foo$只匹配“foo”。更有趣的是,搜索foo.$in'foo1\nfoo2\n' 通常匹配 ‘foo2’,但在MULTILINE模式下搜索 ‘foo1’;搜索单个$in'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 <.*>与 匹配,它将匹配整个字符串,而不仅仅是. 在限定符之后添加使其以非贪婪最小方式执行匹配; 将匹配尽可能少的字符。使用 RE将只匹配.'<a> b <c>''<a>'?<.*?>'<a>'
{m}指定恰好匹配前一个 RE 的m个副本;较少的匹配会导致整个 RE 不匹配。例如,a{6}将完全匹配六个'a'字符,但不是五个。
{m,n}使生成的 RE 匹配前一个 RE 的mn 个重复,尝试匹配尽可能多的重复。例如, a{3,5}将匹配 3 到 5 个'a'字符。省略m指定下限为零,省略n指定无限上限。例如,a{4,}b将匹配'aaaab'或一千个'a'字符后跟一个'b',但不是'aaab'。逗号不能省略,否则修饰符会与前面描述的形式混淆。
{m,n}?使生成的 RE 匹配前一个 RE 的mn次重复,尝试匹配尽可能少的重复。这是先前限定符的非贪婪版本。例如,在 6 个字符的字符串上'aaaaaa'a{3,5}会匹配 5 个'a'字符,而a{3,5}?只会匹配 3 个字符。
\转义特殊字符(允许您匹配 , 等字符 '*''?',或发出特殊序列的信号;下面讨论特殊序列。
如果您不使用原始字符串来表达模式,请记住 Python 还使用反斜杠作为字符串文字中的转义序列;如果 Python 的解析器无法识别转义序列,则反斜杠和后续字符将包含在结果字符串中。但是,如果 Python 能够识别结果序列,则反斜杠应该重复两次。这很复杂且难以理解,因此强烈建议您对除最简单的表达式之外的所有表达式都使用原始字符串。
[]用于表示一组字符。在一组中:
  • 字符可以单独列出,例如[amk]将匹配'a'、 'm''k'
  • 字符范围可以通过给出两个字符并用 a 分隔来表示'-',例如[a-z]将匹配任何小写 ASCII 字母, 将匹配从to[0-5][0-9]的所有两位数字, 并将匹配任何十六进制数字。如果被转义(例如 )或者如果它被放置为第一个或最后一个字符(例如或),它将匹配一个文字。0059[0-9A-Fa-f]-[a\-z][-a][a-]'-'
  • 特殊字符在集合中失去其特殊含义。例如, [(+*)]将匹配任何文字字符'(''+'、 '*'')'.
  • 集合中也接受诸如\wor \S(定义如下)之类的字符类,尽管它们匹配的字符取决于 ASCIIorLOCALE模式是否有效。
  • 不在一个范围内的字符可以通过 集来匹配。如果集合的第一个字符是,则将匹配'^'所有不在集合中的字符。例如,[^5]将匹配除 之外的任何字符'5'[^^]并将匹配除 之外的任何字符 '^'。 ^如果它不是集合中的第一个字符,则没有特殊含义。
  • 要匹配']'集合内的文字,请在其前面加上反斜杠,或将其放在集合的开头。例如,两者都[()[\]{}]将 []()[{}]匹配括号。
  • 将来可能会添加对Unicode Technical Standard #18中嵌套集合和集合操作的支持。这将改变语法,因此为了促进这种改变,FutureWarning暂时将在模棱两可的情况下提出。这包括以文字开头'['或包含文字字符序列'--''&&''~~'和的集合'||'。为了避免警告,用反斜杠转义它们。

在 3.7 版更改:FutureWarning如果字符集包含将来会在语义上发生变化的构造,则会引发此问题。

|A|B,其中AB可以是任意 RE,创建将匹配AB的正则表达式。以这种方式可以分隔任意数量的RE '|'。这也可以在组内使用(见下文)。'|'在扫描目标字符串时,从左到右尝试分隔的 RE 。当一个模式完全匹配时,该分支被接受。这意味着一旦A匹配,B将不会被进一步测试,即使它会产生更长的整体匹配。换句话说,'|'运营商从不贪婪。要匹配文字'|',请使用\|或将其包含在字符类中,如[|].
(...)匹配括号内的任何正则表达式,并指示组的开始和结束;可以在执行匹配后检索组的内容,并且可以稍后在字符串中使用\number 特殊序列进行匹配,如下所述。要匹配文字'('or ')',请使用\(or \),或将它们括在字符类中:[(][)].
(?...)这是一个扩展符号(a'?'后面的 a'('否则没有意义)。之后的第一个字符'?'决定了构造的含义和进一步的语法。扩展通常不会创建新组;(?P<name>...)是这条规则的唯一例外。以下是当前支持的扩展。
(?aiLmsux)(集合中的一个或多个字母'a''i''L''m''s''u''x'。)该组匹配空字符串;字母设置相应的标志:re.A(ASCII-only 匹配), re.I(忽略大小写),re.L(区域设置依赖), re.M(多行),re.S(点匹配所有), re.U(Unicode 匹配)和re.X(详细),用于整个常规表达。(标志在模块内容中描述。)如果您希望将标志包含在正则表达式中,而不是将标志参数 传递给函数,这将很有用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的组匹配的任何文本。
(?#...)
一条评论; 简单地忽略括号的内容。
(?=...)
匹配如果...匹配下一个,但不消耗任何字符串。这称为先行断言。例如,只有在其后跟时才匹配 。Isaac (?=Asimov)'Isaac ''Asimov'
(?!...)
匹配如果...下次不匹配。这是一个负面的先行断言。例如,只有在没有 后跟的情况下才会匹配。Isaac(?!Asimov)'Isaac ''Asimov'
(?<=...)

如果字符串中的当前位置前面有匹配,则匹配,... 该匹配结束于当前位置。这被称为积极的外观断言(?<=abc)def将找到一个匹配'abcdef',因为lookbehind将备份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中已更改:添加了对固定长度的组引用的支持。

(?<!...)
如果字符串中的当前位置前面没有匹配,则匹配 ...。这被称为负面的后观断言。与正向lookbehind断言类似,包含的模式必须仅匹配某些固定长度的字符串。以负反向断言开始的模式可以在被搜索的字符串的开头匹配。
(?(id/name)yes-pattern|no-pattern)
yes-pattern如果存在具有给定id或 名称的组,则尝试匹配,如果no-pattern不存在,则尝试匹配。no-pattern是可选的,可以省略。例如, (<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$)是一个贫穷的电子邮件匹配模式,这将匹配'<[email protected]>'以及'[email protected]',但不与'<[email protected]''[email protected]>'

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

\number
匹配相同编号的组的内容。组从1开始编号。例如,匹配或,但不是(注意组后面的空格)。此特殊序列只能用于匹配前99个组中的一个。如果第一个数字 是0,或为3个八进制数字长,也不会被解释为一组匹配,但与八进制值的字符。里面 和一个字符类,所有的数字逃逸被视为字符。(.+) \1'the the''5555''thethe''['']'
\A
仅匹配字符串的开头。
\b

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

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

\B
匹配空字符串,但仅当它不在单词的开头或结尾时。这意味着,r'py\B'比赛'python''py3', 'py2',而不是'py''py.''py!'。 \B与此相反\b,因此Unicode模式中的单词字符是Unicode字母数字或下划线,尽管可以通过使用ASCII标志来更改。如果使用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
\r      \t      \u      \U
\v      \x      \\

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

'\u''\U'转义序列仅在Unicode模式中识别。在字节模式中,它们是错误。

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

改变在3.3版本:'\u''\U'转义序列已被添加。

在版本3.6中更改:'\'现在包含和ASCII字母的未知转义是错误。

模块内容

re.compile(patternflags=0)

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

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

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
执行不区分大小写的匹配;like 的表达式[A-Z]也将匹配小写字母。除非该标志用于禁用非 ASCII 匹配,否则完整的 Unicode 匹配(例如Ümatching )也可以工作。当前语言环境不会更改此标志的效果,除非也使用该标志。对应于内联标志。üre.ASCIIre.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匹配。此标志只能与字节模式一起使用。不鼓励使用此标志,因为语言环境机制非常不可靠,它一次只能处理一种“文化”,并且仅适用于 8 位语言环境。默认情况下,在 Python 3 中,Unicode 匹配已经为 Unicode (str) 模式启用,它能够处理不同的语言环境/语言。对应于内联标志。\b\B(?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 并且前面没有未转义的反斜杠时,从最左边 #到行尾的所有字符都将被忽略。

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

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(patternstringflags=0)
扫描字符串以查找正则表达式 模式产生匹配的第一个位置,并返回相应的匹配对象None如果字符串中没有位置与模式匹配,则返回;请注意,这与在字符串中的某个点找到零长度匹配不同。
re.match(patternstringflags=0)
如果字符串开头的零个或多个字符与正则表达式模式匹配,则返回相应的匹配对象None如果字符串与模式不匹配则返回;请注意,这与零长度匹配不同。

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

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

re.fullmatch(patternstringflags=0)
如果整个字符串与正则表达式模式匹配,则返回相应的匹配对象None如果字符串与模式不匹配则返回;请注意,这与零长度匹配不同。

3.4 版中的新功能。

re.split(patternstringmaxsplit=0flags=0)
按出现的模式拆分字符串。如果在pattern中使用了捕获括号,那么 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(patternstringflags=0)
返回string中所有不重叠的模式匹配,作为字符串列表。从左到右扫描字符串,并按找到的顺序返回匹配项。如果模式中存在一个或多个组,则返回组列表;如果模式有多个组,这将是一个元组列表。结果中包含空匹配项。

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

re.finditer(patternstringflags=0)
返回一个迭代器,该迭代器在string中的 RE模式的所有非重叠匹配中产生匹配对象。 从左到右扫描字符串,并按找到的顺序返回匹配项。结果中包含空匹配项。

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

re.sub(patternreplstringcount=0flags=0)
返回通过替换repl替换 string中最左边的不重叠出现的模式获得的字符串。如果未找到该模式,则 字符串原封不动地返回。 repl可以是字符串或函数;如果它是一个字符串,则处理其中的任何反斜杠转义。也就是说,转换为单个换行符,转换为回车符,等等。ASCII 字母的未知转义保留供将来使用并视为错误。其他不为人知的逃跑方式,例如被单独留下。反向引用,例如,被替换为模式中第 6 组匹配的子字符串。例如:\n\r\&\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是一个函数,则会为每个非重叠的模式调用调用它 。该函数接受单个匹配对象 参数,并返回替换字符串。例如:

>>>
>>> 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'
模式可以是字符串或模式对象

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

在字符串类型的repl参数中,除了上述字符转义和反向引用之外, \g<name>将使用由名为 的组匹配的子字符串,如语法name所定义。使用对应的组号;因此等价于,但在诸如. 将被解释为对第 20 组的引用,而不是对第 2 组后跟文字字符的引用。反向引用替换了 RE 匹配的整个子字符串。

(?P<name>...)\g<number>\g<2>\2\g<2>0\20'0'\g<0>

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

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

在 3.6 版更改:由 和 ASCII 字母组成的模式中的未知转义'\'现在是错误。

在 3.7 版更改: repl中的未知转义'\'和一个 ASCII 字母现在是错误。

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

re.subn(patternreplstringcount=0flags=0)
执行与 相同的操作sub(),但返回一个元组。(new_string, number_of_subs_made)

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

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

re.escape(pattern)
转义pattern中的特殊字符。如果您想匹配其中可能包含正则表达式元字符的任意文字字符串,这很有用。例如:
>>>
>>> print(re.escape('python.exe'))
python\.exe

>>> 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(msgpattern=Nonepos=None)
传递给此处其中一个函数的字符串不是有效的正则表达式(例如,它可能包含不匹配的括号)或在编译或匹配期间发生其他错误时引发的异常。如果字符串不包含模式匹配,则永远不会出错。错误实例具有以下附加属性:

msg
未格式化的错误消息。
pattern
正则表达式模式。
pos
编译失败的模式中的索引(可能是None)。
lineno
对应于pos的行(可能是None)。
colno
对应于pos的列(可以是None)。

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

正则表达式对象

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

Pattern.searchstring [pos [endpos 
扫描字符串,查找此正则表达式生成匹配项的第一个位置,并返回相应的匹配对象None如果字符串中没有位置与模式匹配则返回; 请注意,这与在字符串中的某个点找到零长度匹配不同。

可选的第二个参数pos给出了搜索开始的字符串中的索引; 它默认为0。这并不完全等同于切割字符串; 该'^'模式字符在字符串的真正开始,并在仅仅一个换行符后的位置相匹配,但不一定,其中搜索是启动索引。

可选参数endpos限制字符串的搜索范围; 它就好像字符串是endpos字符长,所以只搜索pos中的字符才能匹配。如果endpos小于pos,则不会找到匹配项; 否则,如果rx是一个已编译的正则表达式对象,则相当于 。endpos - 1rx.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]])
如果字符串开头的零个或多个字符匹配此正则表达式,则返回相应的匹配对象。 如果字符串与模式不匹配,则返回 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'>

如果要在字符串中的任何位置找到匹配项,search()请改为使用 (另请参阅search()与match())。

Pattern.fullmatch(string[pos[endpos]])
如果整个字符串与此正则表达式匹配,则返回相应的 匹配对象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(stringmaxsplit=0)
split()函数相同,使用编译的模式。
Pattern.findall(string[pos[endpos]])
findall()函数类似,使用已编译的模式,但也接受可选的posendpos参数,这些参数限制了搜索区域search()
Pattern.finditer(string[pos[endpos]])
finditer()函数类似,使用已编译的模式,但也接受可选的posendpos参数,这些参数限制了搜索区域search()
Pattern.sub(replstringcount=0)
sub()函数相同,使用编译的模式。
Pattern.subn(replstringcount=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)

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

Match.expand(template)
返回通过在模板字符串模板上执行反斜杠替换获得的字符串,如sub()方法所做。诸如转义为转义为\n适当字符的转义,数字反向引用(\1\2)和命名反向引用(\g<1>\g<name>)将被相应组的内容替换。

版本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'

版本3.6中的新功能。

Match.groups(default=None)
返回包含匹配的所有子组的元组,从1到多个组都在模式中。该默认参数用于那些没有参加比赛组; 它默认为None

例如:

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

如果我们将小数位和其后的所有内容都设为可选,则并非所有组都可以参与匹配。None除非给出默认参数,否则这些组将默认为:

>>>
>>> m = re.match(r"(\d+)\.?(\d+)?", "24")
>>> m.groups()      # Second group defaults to None.
('24', None)
>>> m.groups('0')   # Now, the second group defaults to '0'.
('24', '0')
Match.groupdict(default=None)
返回包含匹配的所有已命名子组的字典,由子组名称键入。该默认参数用于那些没有参加比赛组; 它默认为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默认为零(表示整个匹配的子字符串)。返回-1如果存在,但无助于比赛。对于匹配对象,和一组这并有助于匹配,则子组由匹配 (相当于m.group(g))是

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

请注意,如果匹配空字符串,m.start(group)则相等。例如,之后, 是1,是2,并且都是2,并引发异常。m.end(group)m = re.search('b(c?)', 'cba')m.start(0)m.end(0)m.start(1)m.end(1)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))(-1, -1)
Match.pos
传递给正则表达式对象或 方法的pos的值。这是RE引擎开始寻找匹配项的字符串索引。search()match()
Match.endpos
的值endpos被传递到search()或 match()一个的方法regex对象。这是RE引擎不会超出的字符串索引。
Match.lastindex
最后匹配的捕获组的整数索引,或者None根本没有匹配的组。例如,表述(a)b((a)(b))以及 ((ab))将具有如果施加到串,而表达将有,如果施加到相同的字符串。lastindex == 1'ab'(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”代表王牌,“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 = 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.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']]

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

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

>>>
>>> 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()那么它是有用的,因为它提供匹配对象而不是字符串。继续前面的例子,如果作家想要在某些文本中找到所有副词及其位置,他们将按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='\\'>

编写一个标记

甲标记生成器或扫描仪 分析的字符串进行分类字符组。这是编写编译器或解释器的有用的第一步。

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

import collections import re 
Token = collections.namedtuple('Token', ['type', 'value', 'line', 'column']) 
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)