re-正则表达式语法及操作详解(11)Python语言(必读进阶学习教程)(参考资料)
此模块提供与Perl中类似的正则表达式匹配操作。
要搜索的模式和字符串都可以是Unicode字符串(str
)以及8位字符串(bytes
)。但是,不能混合Unicode字符串和8位字符串:也就是说,您不能将Unicode字符串与字节模式匹配,反之亦然; 类似地,当要求替换时,替换字符串必须与模式和搜索字符串具有相同的类型。
正则表达式使用反斜杠字符('\'
)来表示特殊形式或允许使用特殊字符而不调用它们的特殊含义。这与Python在字符串文字中用于相同目的的相同字符的使用相冲突; 例如,要匹配文字反斜杠,可能必须写'\\\\'
为模式字符串,因为正则表达式必须是\\
,并且每个反斜杠必须表示为\\
常规Python字符串文字。
解决方案是使用Python的原始字符串表示法来表示正则表达式模式; 在前缀的字符串文字中,不以任何特殊方式处理反斜杠'r'
。所以r"\n"
是一个包含'\'
和的双字符字符串 'n'
,"\n"
而是包含换行符的单字符字符串。通常,模式将使用此原始字符串表示法在Python代码中表示。
值得注意的是,大多数正则表达式操作都可用作编译正则表达式的模块级函数和方法 。这些函数是快捷方式,不需要先编译正则表达式对象,但会遗漏一些微调参数。
也可以看看
第三方正则表达式模块,它具有与标准库re
模块兼容的API ,但提供了额外的功能和更全面的Unicode支持。
正则表达式语法
正则表达式(或RE)指定一组与之匹配的字符串; 通过此模块中的函数,您可以检查特定字符串是否与给定的正则表达式匹配(或者如果给定的正则表达式与特定字符串匹配,则归结为同一个字符串)。
正则表达式可以连接起来形成新的正则表达式; 如果A 和B都是正则表达式,那么AB也是正则表达式。通常,如果字符串p匹配A而另一个字符串q匹配B,则字符串pq将匹配AB。除非A或B包含低优先级操作,否则这是成立的; A和B之间的边界条件; 或编号组参考。因此,复杂的表达式可以很容易地用比这里描述的更简单的原始表达式构造。有关正则表达式的理论和实现的详细信息,请参阅Friedl一书[Frie09],或几乎任何有关编译器构造的教科书。
下面是正则表达式格式的简要说明。有关更多信息和更温和的演示文稿,请参阅正则表达式HOWTO。
正则表达式可以包含特殊字符和普通字符。大多数普通字符,如'A'
,, 'a'
或者'0'
是最简单的正则表达式; 他们只是匹配自己。您可以连接普通字符,因此last
匹配字符串'last'
。(在本节的其余部分,我们将编写RE ,通常没有引号,以及要匹配的字符串。)this special style
'in single quotes'
有些字符,如'|'
或'('
,是特殊的。特殊字符或者代表普通字符的类,或者影响它们周围的正则表达式的解释方式。
重复限定符(*
,+
,?
,{m,n}
,等等)不能直接嵌套。这避免了非贪婪修饰符后缀的模糊性 ?
,以及其他实现中的其他修饰符。为了对内部重复应用第二次重复,可以使用括号。例如,表达式(?:a{6})*
匹配六个'a'
字符的任意倍数。
特殊字符是:
.
- (点。)在默认模式下,它匹配除换行符之外的任何字符。如果
DOTALL
已指定标志,则匹配包括换行符在内的任何字符。
^
- (Caret。)匹配字符串的开头,并且在
MULTILINE
模式下也会在每个换行符后立即匹配。
$
- 匹配字符串的结尾或在字符串末尾的换行符之前,并且在
MULTILINE
模式中也匹配换行符之前。foo
匹配’foo’和’foobar’,而正则表达式foo$
只匹配’foo’。更有趣的是, 通常foo.$
在'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的m到n次重复,尝试匹配尽可能多的重复。例如,
a{3,5}
将匹配3到5个'a'
字符。省略m指定零的下限,省略n指定无限上限。例如,a{4,}b
将匹配'aaaab'
或一千个'a'
字符后跟一个'b'
,但不是'aaab'
。可以不省略逗号,或者将修饰符与先前描述的形式混淆。 {m,n}?
- 使得到的RE匹配前一个RE的m到n次重复,尝试匹配尽可能少的重复。这是前一个限定符的非贪婪版本。例如,在6个字符的字符串上
'aaaaaa'
,a{3,5}
将匹配5个'a'
字符,而a{3,5}?
只匹配3个字符。
\
-
要么转义特殊字符(允许你匹配像
'*'
,'?'
等等那样的字符 ),要么发出特殊序列的信号; 下面讨论特殊序列。如果你没有使用原始字符串来表达模式,请记住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
(在下面定义)之 类的字符类。ASCII
LOCALE
- 可以通过补充 该组来匹配不在范围内的字符。如果该组的第一个字符是
'^'
,则将匹配该组中不存在的所有字符。例如,[^5]
将匹配除了之外的任何字符'5'
,并且[^^]
将匹配除了之外的任何字符'^'
。^
如果它不是集合中的第一个字符,则没有特殊含义。 - 要匹配
']'
集合中的文字,请在其前面加上反斜杠,或将其放在集合的开头。例如,无论是[()[\]{}]
和[]()[{}]
都将匹配一个括号。
- 将来可能会添加对Unicode技术标准#18中的嵌套集和集合操作的支持。这将改变语法,因此为了促进这种改变,
FutureWarning
暂时会在模棱两可的情况下提出。这包括开始文字集合'['
或含有文字字符序列'--'
,'&&'
,'~~'
,和'||'
。为了避免警告,请使用反斜杠来避开它们。
FutureWarning
如果字符集包含将来会在语义上更改的构造,则会引发版本3.7:更新。 - 人物可以单独上市,如
|
A|B
,其中A和B可以是任意RE,创建一个与A或B匹配的正则表达式。通过'|'
这种方式可以分开任意数量的RE 。这也可以在组内使用(见下文)。扫描目标字符串时,'|'
从左到右尝试分隔的RE 。当一个模式完全匹配时,接受该分支。这意味着一旦A匹配,B将不会被进一步测试,即使它会产生更长的整体匹配。换句话说,'|'
操作员从不贪心。要匹配文字'|'
,请使用\|
或将其括在字符类中,如[|]
。
(...)
- 匹配括号内的正则表达式,并指示组的开始和结束; 可以在执行匹配后检索组的内容,并且可以在字符串中稍后与
\number
特殊序列匹配,如下所述。要匹配的文字'('
或')'
使用\(
或\)
,或将它们括字符类中:[(]
,[)]
。
(?...)
- 这是一种扩展符号(
'?'
以下a'('
除此之外没有意义)。'?'
确定构造的含义和进一步语法之后的第一个字符。扩展通常不会创建新组;(?P<name>...)
是这个规则的唯一例外。以下是当前支持的扩展。 (?aiLmsux)
- (从所述一组一个或多个字母
'a'
,'i'
,'L'
,'m'
,'s'
,'u'
,'x'
。)该组匹配空字符串; 这些字母为整个常规设置了相应的标志:(re.A
ASCII-only matching),re.I
(ignore case),re.L
(locale dependent),re.M
(multi-line),re.S
(dot match all),re.U
(Unicode匹配)和re.X
(verbose)表达。(标志在模块内容中描述。)如果您希望将标志包含在正则表达式的一部分中,而不是将标志参数传递给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>...)
-
与常规括号类似,但组匹配的子字符串可通过符号组名称名称访问。组名必须是有效的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)>|$)
是一个贫穷的电子邮件匹配模式,这将匹配'<user@host.com>'
以及'user@host.com'
,但不与'<user@host.com'
也'user@host.com>'
。
特殊序列由'\'
下面列表中的一个字符组成。如果普通字符不是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字母的未知转义是错误。
模块内容
该模块定义了几个函数,常量和异常。一些函数是编译正则表达式的全功能方法的简化版本。大多数非平凡的应用程序总是使用编译的表单。
在版本3.6中更改:标志常量现在是实例RegexFlag
,它是的子类 enum.IntFlag
。
re.
compile
(pattern,flags = 0 )- 编译一个正则表达式模式为正则表达式对象,其可用于使用它的匹配
match()
,search()
以及其他方法,说明如下。可以通过指定标志值来修改表达式的行为。值可以是以下任何变量,使用按位OR(
|
运算符)组合。序列
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]
也会匹配小写字母。除非使用该标志禁用非ASCII匹配,否则完全Unicode匹配(例如Ü
匹配ü
)也有效re.ASCII
。除非re.LOCALE
也使用该标志,否则当前区域设置不会更改此标志的效果。对应于内联标志(?i)
。请注意,当Unicode模式
[a-z]
或[A-Z]
与IGNORECASE
标志结合使用时,它们将匹配52个ASCII字母和4个额外的非ASCII字母:’İ’(U + 0130,拉丁大写字母I带上面的点),’ı’ (U + 0131,拉丁文小写无字母i),’s’(U + 017F,拉丁文小写长s)和’K’(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 )- 扫描字符串,查找正则表达式模式产生匹配的第一个位置 ,并返回相应的匹配对象。
None
如果字符串中没有位置与模式匹配则返回; 请注意,这与在字符串中的某个点找到零长度匹配不同。
re.
match
(pattern,string,flags = 0 )- 如果字符串开头的零个或多个字符与正则表达式模式匹配,则返回相应的匹配对象。
None
如果字符串与模式不匹配则返回; 请注意,这与零长度匹配不同。请注意,即使在
MULTILINE
模式下,re.match()
也只会匹配字符串的开头而不是每行的开头。如果要在字符串中的任何位置找到匹配项,
search()
请改为使用(另请参阅search()与match())。
re.
fullmatch
(pattern,string,flags = 0 )- 如果整个字符串与正则表达式模式匹配,则返回相应的匹配对象。
None
如果字符串与模式不匹配则返回; 请注意,这与零长度匹配不同。版本3.4中的新功能。
re.
split
(pattern,string,maxsplit = 0,flags = 0 )- 按模式的出现拆分字符串。如果在模式中使用捕获括号,则模式中所有组的文本也将作为结果列表的一部分返回。如果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 )- 返回的所有非重叠的匹配模式的字符串,如字符串列表。该字符串进行扫描左到右,并匹配以发现的顺序返回。如果模式中存在一个或多个组,则返回组列表; 如果模式有多个组,这将是一个元组列表。结果中包含空匹配。
在版本3.7中更改:非空匹配现在可以在上一个空匹配之后立即开始。
re.
finditer
(pattern,string,flags = 0 )- 返回一个迭代器,在字符串中的RE 模式的所有非重叠匹配上产生匹配对象。该字符串 进行扫描左到右,并匹配以发现的顺序返回。结果中包含空匹配。
在版本3.7中更改:非空匹配现在可以在上一个空匹配之后立即开始。
re.
sub
(pattern,repl,string,count = 0,flags = 0 )- 返回通过替换repl替换字符串中最左边的非重叠模式而获得的字符串。如果未找到模式, 则返回字符串不变。repl可以是字符串或函数; 如果它是一个字符串,则处理其中的任何反斜杠转义。也就是说,转换为单个换行符,转换为回车符,依此类推。未知的逃脱,如独自留下。反向引用(例如)将替换为模式中第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
由(?P<name>...)
语法定义的命名组匹配的子字符串。\g<number>
使用相应的组号;\g<2>
因此,等同于\2
,但在替代品中并不含糊\g<2>0
。\20
将被解释为对组20的引用,而不是对组2的引用,后跟文字字符'0'
。反向引用\g<0>
替代了RE匹配的整个子字符串。在3.1版中更改:添加了可选的flags参数。
版本3.5中已更改:不匹配的组将替换为空字符串。
在版本3.6中更改:包含和ASCII字母的模式中的未知转义
'\'
现在是错误。在3.7版本的改变:在未知逃逸REPL包括
'\'
与ASCII字母现在的错误。当与先前的非空匹配相邻时,替换模式的空匹配。
re.
subn
(pattern,repl,string,count = 0,flags = 0 )- 执行相同的操作
sub()
,但返回元组。(new_string, number_of_subs_made)
在3.1版中更改:添加了可选的flags参数。
版本3.5中已更改:不匹配的组将替换为空字符串。
re.
escape
(模式)- 逃避模式中的特殊字符。如果要匹配可能包含正则表达式元字符的任意文字字符串,这将非常有用。例如:
>>> >>> 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
()- 清除正则表达式缓存。
- 异常
re.
error
(msg,pattern = None,pos = None ) - 传递给此处其中一个函数的字符串不是有效的正则表达式(例如,它可能包含不匹配的括号)或在编译或匹配期间发生其他错误时引发的异常。如果字符串不包含模式匹配,则永远不会出错。错误实例具有以下附加属性:
msg
- 未格式化的错误消息。
pattern
- 正则表达式模式。
pos
- 编译失败的模式中的索引(可能是
None
)。
lineno
- 对应于pos的行(可能是
None
)。
colno
- 对应于pos的列(可以是
None
)。
版本3.5中已更改:添加了其他属性。
正则表达式对象
编译的正则表达式对象支持以下方法和属性:
Pattern.
search
(string [,pos [,endpos ] ] )- 扫描字符串,查找此正则表达式生成匹配项的第一个位置,并返回相应的匹配对象。
None
如果字符串中没有位置与模式匹配则返回; 请注意,这与在字符串中的某个点找到零长度匹配不同。可选的第二个参数pos给出了搜索开始的字符串中的索引; 它默认为
0
。这并不完全等同于切割字符串; 该'^'
模式字符在字符串的真正开始,并在仅仅一个换行符后的位置相匹配,但不一定,其中搜索是启动索引。可选参数endpos限制字符串的搜索范围; 它就好像字符串是endpos字符长,所以只搜索pos中的字符才能匹配。如果endpos小于pos,则不会找到匹配项; 否则,如果rx是一个已编译的正则表达式对象,则相当于 。
endpos - 1
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 ] ] )- 如果字符串开头的零个或多个字符与此正则表达式匹配,则返回相应的匹配对象。如果字符串与模式不匹配则返回; 请注意,这与零长度匹配不同。
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'>
如果要在字符串中的任何位置找到匹配项,
search()
请改为使用 (另请参阅search()与match())。
Pattern.
fullmatch
(string [,pos [,endpos ] ] )- 如果整个字符串与此正则表达式匹配,则返回相应的 匹配对象。
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.
split
(string,maxsplit = 0 )- 与
split()
函数相同,使用编译的模式。
Pattern.
findall
(string [,pos [,endpos ] ] )- 与
findall()
函数类似,使用已编译的模式,但也接受可选的pos和endpos参数,这些参数限制了搜索区域search()
。
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字符串。
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
(模板)- 返回通过在模板字符串模板上执行反斜杠替换获得的字符串,如
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
(默认=无)- 返回包含匹配的所有子组的元组,从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
(默认=无)- 返回包含匹配的所有已命名子组的字典,由子组名称键入。该默认参数用于那些没有参加比赛组; 它默认为
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
([ 组] )Match.
end
([ 组] )- 返回由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():] 'tony@tiger.net'
Match.
span
([ 组] )- 对于匹配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']]
该:?
模式匹配姓氏后的冒号,因此它不会出现在结果列表中。随着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()
函数“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)