文章目录
1. re模块简介
这个模块提供了与 Perl 语言类似的正则表达式匹配操作。
要搜索的模式和字符串可以是 Unicode 字符串 (str
) 以及8位字节串(bytes
)。 但是,Unicode 字符串与8位字节串不能混用:也就是说,你不能用一个字节串模式去匹配 Unicode 字符串,反之亦然;类似地,当进行替换操作时,替换字符串必须与所用的模式和搜索字符串都为相同类型。
正则表达式使用反斜框字符 ('\'
) 来提示特殊形式或是允许使用特殊字符而又不启用它们的特殊含义。 这会与 Python 在字符串字面值中出于相同目的而使用的相同字符发生冲突;例如,为了匹配一个反斜杠字面值,模式字符串就需要写成 '\\\\'
,因为正则表达式必须写成 \\
,而每个反斜杠在普通的Python 字符串字面值内又必须写成 \\
。
解决办法是对于正则表达式模式使用 Python 的原始字符串表示法;在带有'r'
前缀的字符串字面值中,反斜杠不必做任何特殊处理。 因此 r'\n'
表示包含 '\'
和 'n'
两个字符的字符串,而 '\n'
则表示只包含一个换行符的字符串。 模式在 Python 代码中通常都会使用这种原始字符串表示法来表示。
绝大部分正则表达式操作都提供为模块函数和方法,这些函数是一个捷径,不需要先编译一个正则对象,但是损失了一些优化参数。
参见: 第三方模块 regex
, 提供了与标准库 re
模块兼容的API接口,同时还提供了额外的功能和更全面的Unicode支持。
2. 正则表达式语法
一个正则表达式(或RE)指定了与之匹配的一个字符串集合;模块内的函数可以让你检查某个字符串是否跟给定的正则表达式匹配(或者一个正则表达式是否匹配到一个字符串,这两种说法含义相同)。
正则表达式可以拼接; 如果 A 和 B 都是正则表达式, 那么 AB 也是正则表达式。 通常, 如果字符串 p 匹配 A 并且另一个字符串 q 匹配B, 那么 pq 可以匹配 AB。除非 A 或者 B 包含低优先级操作,A 和 B 存在边界条件,或者命名组引用。所以,复杂表达式可以很容易的从这里描述的简单源语表达式构建。
以下是正则表达式格式的简要说明。更详细的信息和演示,参考 Regular Expression HOWTO。
正则表达式可以包含普通或者特殊字符。绝大部分普通字符,比如 A
,a
,或者 0
,都是最简单的正则表达式。它们就匹配自身。你可以拼接普通字符,所以 last
匹配字符串 'last'
。(在这一节的其他部分,我们将用 this special style
这种方式表示正则表达式,通常不带引号,要匹配的字符串用 'in single quotes'
,单引号形式。)
有些字符,比如 |
或者 (
,属于特殊字符。 特殊字符既可以表示它的普通含义, 也可以影响它旁边的正则表达式的解释。
重复修饰符 (*
, +
, ?
, {m,n}
, 等) 不能直接嵌套。这样避免了非贪婪后缀 ?
修饰符和其他实现中的修饰符产生的多义性。要应用一个内层重复嵌套,可以使用括号。 比如,表达式 (?:a{6})*
匹配6个 'a'
字符重复任意次数。
2.1 特殊字符
-
.
(点) 在默认模式中匹配除了换行的单个任意字符。如果指定了标签DOTALL
,它将匹配包括换行符的任意字符。 -
^
(插入符号) 匹配字符串的开头, 并且在MULTILINE
模式也匹配换行后的首个符号。 -
$
匹配字符串尾或者换行符的前一个字符,在
MULTILINE
模式匹配换行符的前一个字符。foo
匹配'foo'
和'foobar'
, 但正则foo$
只匹配'foo'
。更有趣的是, 在foo1\nfoo2\n
搜索foo.$
,通常匹配'foo2'
,但在MULTILINE
模式 ,可以匹配到'foo1'
;在'foo\n'
搜索$
会找到两个空串:一个在换行前,一个在字符串最后。 -
*
对它前面的正则式匹配0到任意次重复, 尽量多的匹配字符串。
ab*
会匹配'a'
,'ab'
, 或者'a'
后面跟随任 意个'b'
的字符串。 -
+
对它前面的正则式匹配1到任意次重复。
ab+
会匹配'a'
后面跟随1个以上的'b'
的字符串,它不会匹配'a'
。 -
?
对它前面的正则式匹配0到1次重复。ab?
会匹配'a'
或者'ab'
。 -
*?
,+?
,??
*
,+
,和?
修饰符都是 贪婪的 ;它们在字符串进行尽可能多的匹配。有时候并不需要这种行为。如果正则式<.*>
希望找到<a> b <c>
,它将会匹配整个字符串,而不仅是<a>
。在修饰符之后添加?
将使模式以 非贪婪 方式或者 :dfn: 最小 方式进行匹配; 尽量 少 的字符将会被匹配。 使用正则式<.*?>
将会仅仅匹配<a>
。 -
{m}
对其之前的正则式指定匹配 m 个重复;少于 m 的话就会导致匹配失败。比如,a{6}
将匹配6个'a'
, 但是不能是5个。 -
{m, n}
对正则式进行 m 到 n 次匹配,在 m 和 n 之间取尽量多。 比如,a{3,5}
将匹配 3 到 5个'a'
。忽略 m 意为指定下界为0,忽略 n 指定上界为无限次。 比如a{4,}b
将匹配'aaaab'
或者1000个'a'
尾随一个'b'
,但不能匹配'aaab'
。逗号不能省略,否则无法辨别修饰符应该忽略哪个边界。 -
{m,n}?
前一个修饰符的非贪婪模式,只匹配尽量少的字符次数。比如,对于'aaaaaa'
,a{3,5}
匹配 5个'a'
,而a{3,5}?
只匹配3个'a'
。 -
\
转义特殊字符(允许你匹配*
,?
, 或者此类其他),或者表示一个特殊序列;特殊序列之后进行讨论。如果你没有使用原始字符串(
r'raw'
)来表达模式,要牢记Python也使用反斜杠作为转义序列;如果转义序列不被Python的分析器识别,反斜杠和字符才能出现在字符串中。如果Python可以识别这个序列,那么反斜杠就应该重复两次。这将导致理解障碍,所以高度推荐,就算是最简单的表达式,也要使用原始字符串。 -
[]
用于表示一个字符集合。在一个集合中:-
字符可以单独列出,比如
[amk]
匹配'a'
,'m'
, 或者'k'
。 -
可以表示字符范围,通过用
-
将两个字符连起来。比如[a-z]
将匹配任何小写ASCII字符,[0-5][0-9]
将匹配从00
到59
的两位 数字,[0-9A-Fa-f]
将匹配任何十六进制数位。 如果-
进行了转义 (比如[a\-z]
)或者它的位置在首位或者末尾(如[-a]
或[a-]
),它就只表示普通字符-
。 -
特殊字符在集合中,失去它的特殊含义。比如
[(+*)]
只会匹配这几个 文法字符(
,+
,*
, or)
。 -
字符类如
\w
或者\S
(如下定义) 在集合内可以接受,它们可以匹配 的字符由ASCII
或者LOCALE
模式决定。 -
不在集合范围内的字符可以通过 取反 来进行匹配。如果集合首字符是
^
,所有 不 在集合内的字符将会被匹配,比如[^5]
将匹配除过5
的所有字符,[^^]
将匹配'^'
之外的其他所有字符,。^
如果 不在集合首位,就没有特殊含义。 -
在集合内要匹配一个字符
']'
,有两种方法,要么就在它之前加上反斜杠,要么就把它放到集合首位。比如,[()[\]{}]
和[]()[{}]
都可以匹配括号。 -
Unicode Technical Standard 里的嵌套集合和集合操作支持可能在未来添加。这将会改变语法,所以为了帮助这个改变,一个
FutureWarning
将会在有多义的情况里被raise
,包含以下几种情况,集合由[
开始,或者包含下列字符序列--
,&&
,~~
, 和||
。为了避免警告,需要将它们用反斜杠转义。在 3.7 版更改: 如果一个字符串构建的语义在未来会改变的话,一个
FutureWarning
会raise
。
-
-
|
A|B
, A 和 B 可以是任意正则表达式,创建一个正则表达式,匹配A 或者 B 。任意个正则表达式可以用|
连接。它也可以在组合(见下列)内使用。扫描目标字符串时,|
分隔开的正则模式从左到右进行匹配。当一个模式完全匹配时,这个分支就被接受。意思就是,一旦 A匹配成功, B 就不再进行匹配,即便它能产生一个更好的匹配。或者说,|
操作符绝不贪婪。 如果要匹配|
字符,使用\|
, 或者把它包含在字符集里,比如[|]
。 -
(...)
'('
和')'
组合,匹配括号内的任意正则表达式,并标识出组合的开始和结尾。匹配完成后,组合的内容可以被获取,并可以在之后用\number
转义序列进行再次匹配,之后进行详细说明。要匹配字符'('
或者')'
, 用\(
或\)
, 或者把它们包含在字符集合里:[(]
,[)]
。
2.2 拓展标记法
(?…)
这种形式是正则表达式的扩展标记法 (一个 '?'
跟随 '('
并无含义)。 ?
后面的第一个字符决定了这个构建采用什么样的语法。这种扩展通常并不创建新的组合; (?P<name>...)
是唯一的例外。 以下是目前支持的扩展。
-
(?aiLmsux)
(a
,i
,L
,m
,s
,u
,x
中的一个或多个) 这个组合匹配一个空字符串;这些字符对正则表达式设置以下标记:re.A
(只匹配ASCII字符)re.I
(忽略大小写)re.L
(语言依赖)re.M
(多行模式)re.S
(点dot匹配全部字符)re.U
(Unicode匹配)re.X
(冗长模式)
如果你想将这些标记包含在正则表达式中,这个方法就很有用,免去了在
re.compile()
中传递 flag 参数。标记应该在表达式字符串首位表示。 -
(?:…)
正则括号的非捕获版本。只识别,不取值。匹配在括号内的任何正则式,但匹配完成后,这个子串不做为结果被获取。比如(?:a)bc
指定了匹配模式bc
,但这个模式前面必须是字符a
,它可以匹配'abc'
,但不能匹配'bbc'
,匹配完成后只返回'bc'
作为匹配内容。 -
(?aiLmsux-imsx:…)
(a
,i
,L
,m
,s
,u
,x
中的0或者多个,之后可选跟随-
在后面跟随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匹配 (默认)。在bytes模式中(?L:...)
切换为语言依赖模式,(?a:...)
切换为只匹配ASCII(默认)。这种方式只覆盖组合内匹配,括号外的匹配模式不受影响。在 3.7 版更改: 符号
a
,L
和u
同样可以用在一个组合内。 -
(?P<name>…)
(命名组合)类似正则组合,但是匹配到的子串组在外部是通过定义的 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 的命名组匹配相同的字串。 -
(?#…)
注释;里面的内容会被忽略。 -
(?=…)
匹配…
的内容,但是并不消费模式的内容。这个叫: lookahead assertion (前视断言)。比如,Isaac (?=Asimov)
只有在Isaac
后面是Asimov
时才匹配Isaac
。 -
(?!…)
匹配…
不符合的情况。这个叫:negative lookahead assertion (负向前视断言)。比如说,Isaac (?!Asimov)
只有Isaac
后面 不 是Asimov
的时候才匹配Isaac
。 -
(?<=…)
匹配字符串的当前位置,它的前面匹配…
的内容到当前位置。这叫:positive lookbehind assertion (正向后视断言)。(?<=abc)def
会在abcdef
中找到一个匹配,因为后视会往后看3个字符并检查是否包含匹配的模式。包含的模式必须匹配定长的字符串,意思就是abc
或a|b
是允许的,但是a*
和a{3,4}
不可以。注意以正向后视断言开始的模式,如(?<=abc)def
,并不是从a
开始搜索,而是从d
往回看的。你可能更加愿意使用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 版更改: 添加定长组合引用的支持。
-
(?<!…)
匹配当前位置之前不是…
的模式。这个叫:negative lookbehind assertion (负向后视断言)。类似正向后视断言,包含的模式必须匹配定长的字符串。由负向后视断言开始的模式可以从字符串搜索开始的位置进行匹配。 -
(?(id/name)yes-pattern|no-pattern)
如果给定的组的 id 或 name 存在,将会尝试匹配yes-pattern
,否则就尝试匹配no-pattern
,no-pattern
可选,也可以被忽略。比如,(<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$)
是一个email模式匹配,将匹配<user@host.com>
或user@host.com
,但不会匹配<user@host.com
,也不会匹配user@host.com>
。
2.3 特殊序列
下面列出了由 '\'
和一个普通字符组成的特殊序列。 如果一个普通字符不是ASCII数字字符或者ASCII字母字符,那么正则模式将匹配第二个字符。比如,\$
匹配字符'$'
。
-
\number
匹配数字代表的组的内容。每个小括号是一个组,组从1开始编号。比如(.+) \1
匹配'the the'
或者'55 55'
, 但不会匹配'thethe'
(注意组后面的空格)。这个特殊序列只能用于匹配前面99个组中的一个。如果 number 的第一个数字是 0, 或者 number 是三位八进制数,它将不会被看作是一个组合,而是八进制的数字值。在[
和]
字符集合内,任何对数字的转义都被看作是反斜杠和字符的组合。 -
\A
只匹配字符串开始。 当不在MULTILINE
模式时,\A
和^
实际上是相同的。 在MULTILINE
模式中,它们是不同的:\A
仍然只在字符串的开头匹配,但^
可以匹配在换行符之后的字符串内的任何位置。>>> re.findall(r'\A\w+', 'hello\nworld', re.M) ['hello'] >>> re.findall(r'^\w+', 'hello\nworld', re.M) ['hello', 'world']
-
\b
是一个零宽断言,仅在单词的开头或结尾处匹配。一个单词被定义为一个单词字符的序列。注意,形式上,\b
被定义为\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
标志,则单词的边界由当前语言区域设置。>>> re.findall(r'\Blow\B', 'helloworld', re.M) ['low']
-
\d
- 对于 Unicode (str) 模式:
匹配任何Unicode十进制数(就是在Unicode字符目录[Nd]里的字符)。这包括了[0-9]
,和很多其他的数字字符。如果设置了ASCII
标志,就只匹配[0-9]
。 - 对于8位字节模式:
匹配任何十进制数,就是[0-9]
。
- 对于 Unicode (str) 模式:
-
\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]
。
- 对于 Unicode (str) 模式:
-
\S
匹配任何非空白字符。与\s
作用相反。如果设置了ASCII
标志,就相当于[^ \t\n\r\f\v]
。 -
\w
- 对于 Unicode (str) 模式:
匹配Unicode词语的字符,包含了可以构成词语的绝大部分字符,也包括数字和下划线。如果设置了ASCII
标志,就只匹配[a-zA-Z0-9_]
。 - 对于8位字节模式:
匹配ASCII字符中的数字和字母和下划线,就是[a-zA-Z0-9_]
。如果设置了LOCALE
标记,就匹配当前语言区域的数字和字母和下划线。
- 对于 Unicode (str) 模式:
-
\W
匹配任何非词语字符。与\w
作用相反。如果设置了ASCII
标记,就相当于[^a-zA-Z0-9_]
。如果设置了LOCALE
标志,就匹配当前语言区域的 非 词语字符。 -
\Z
只匹配字符串尾。>>> re.findall(r'\w+\Z', 'hello\nworld', re.M) ['world']
绝大部分Python的标准转义字符也被正则表达式分析器支持:
\a \b \f \n
\r \t \u \U
\v \x \\
(注意 \b
被用于表示词语的边界,它只在字符集合内表示退格,比如[\b]
。)
\u
和 \U
转义序列只在Unicode模式中支持。字节模式会显示错误。
八进制转义包含为一个有限形式。如果首位数字是 0, 或者有三个八进制数位,那么就认为它是八进制转义。其他的情况,就看作是组引用。对于字符串文本,八进制转义最多有三个数位长。
在 3.3 版更改: 增加了
\u
和\U
转义序列。
在 3.6 版更改: 由
\
和一个ASCII字符组成的未知转义会被看成错误。
3. 模块内容
模块定义了如下一些函数、常量和异常。有些函数是编译后的正则表达式方法的简化版本(少了一些特性)。绝大部分重要的应用,总是会先将正则表达式编译,之后再进行操作。
在 3.6 版更改: 标志常量现在是
RegexFlag
类的实例,这个类是enum.IntFlag
的子类。
3.1 标记常量
下面的这些常量可以用在正则表达式中,以更改正则表达式的默认行为。
-
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]
也会匹配小写字符。除非设置了re.ASCII
标记来禁用非ASCII匹配,否则Unicode匹配也支持这个标记(比如Ü
匹配ü
)。除非设置了re.LOCALE
标记,否则当前语言区域不会改变这个标记。该标记相当于内联标记
(?i)
。注意,当设置了
IGNORECASE
标记时,Unicode模式[a-z]
或[A-Z]
将会匹配52个ASCII字符和4个额外的非ASCII字符:'İ'
(U+0130,大写的拉丁字母I
上面带个点),'ı'
(U+0131,不带点的小写拉丁字母i
),ſ
(U+017F, 小写的长拉丁字母s
) 和'K'
(U+212A,开尔文符号)。如果使用ASCII
标记,就只匹配ASCII 字符中的 52 个小写字母 。 -
re.L
re.LOCALE根据当前语言区域决定
\w
,\W
,\b
,\B
和大小写敏感的匹配。这个标记只能对字节模式有效。这个标记不推荐使用,因为语言区域机制很不可靠,它一次只能处理一种 “culture”,而且只对8位字节有效。Unicode匹配在Python 3 里默认启用,并可以处理不同语言。该标记对应内联标记
(?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)
。
3.2 常用函数
-
re.compile(pattern, flags=0)
将正则表达式的模式编译为一个
re.Pattern
对象,可以通过这个对象的match()
,search()
等方法可以进行匹配。可以通过指定 flag 的值来改变正则表达式的行为。flag 的值可以是上面列出的标记常量中的一个或者多个,可以通过按位或 OR(
|
操作符)来组合多个变量。表达式语句
prog = re.compile(pattern) result = prog.match(string)
等价于
result = re.match(pattern, string)
如果需要多次使用这个正则表达式的话,可以使用
re.compile()
来编译这个正则表达式,然后保存生成的正则表达式对象以便复用,这样程序将会更加高效。注解:通过
re.compile()
编译后的模式,和模块级的函数会被缓存,所以少数的正则表达式使用无需考虑编译的问题。 -
re.search(pattern, string, flags=0)
扫描整个 string ,找到匹配模式的第一个位置,并返回一个相应的匹配对象。如果没有匹配,就返回 None ; 注意,这和找到一个零长度匹配是不同的。
-
re.match(pattern, string, flags=0)
如果 string 中开头的0或者多个字符匹配到了正则表达式模式,就返回一个相应的匹配对象 。 如果没有匹配到,就返回 None ;注意它跟零长度匹配是不同的。
注意即便是多行模式,
re.match()
也只匹配字符串的开始位置,而不匹配每行开始。如果你想定位 string 的任何位置,使用
search()
来替代。 -
re.fullmatch(pattern, string, flags=0)
如果整个 string 匹配到正则表达式模式,就返回一个相应的匹配对象。 否则就返回 None ;注意这跟零长度匹配是不同的。
3.4 新版功能.
-
re.split(pattern, string, maxsplit=0, flags=0)
用 pattern 分割 string 。 如果 pattern 中使用了括号,那么所有的组里的文本也会包含在列表里。如果 maxsplit 非零, 则最多进行 maxsplit 次分割, 剩下的字符全部返回到列表的最后一个元素。
>>> re.split(r'\W+', 'Words, words, words.') ['Words', 'words', 'words', ''] >>> re.split(r'(\W+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] >>> re.split(r'\W+', 'Words, words, words.', 1) ['Words', 'words, words.'] >>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE) ['0', '3', '9']
如果分隔符里有捕获组,并且匹配到字符串的开始,那么结果将会以一个空字符串开始。对于结尾也是一样。
>>> re.split(r'(\W+)', '...words, words...') >>> ['', '...', 'words', ', ', 'words', '...', '']
这样的话,分隔符将会出现在结果列表中相同的相对所引处。
模式的空匹配只在与前一个空匹配不相邻时才拆分字符串。
>>> re.split(r'\b', 'Words, words, words.') ['', 'Words', ', ', 'words', ', ', 'words', '.'] >>> re.split(r'\W*', '...words...') ['', '', 'w', 'o', 'r', 'd', 's', '', ''] >>> re.split(r'(\W*)', '...words...') ['', '...', '', '', 'w', '', 'o', '', 'r', '', 'd', '', 's', '...', '', '', '']
在 3.1 版更改: 增加了可选标记参数。
在 3.7 版更改: 增加了空字符串的模式分隔。
-
re.findall(pattern, string, flags=0)
将 string 中 pattern 的所有非重叠匹配作为一个字符串列表返回,从左到右扫描 string ,并按照找到的顺序返回匹配项。如果模式里存在一到多个组,就返回一个组列表;这个组列表是一个元组的列表(如果模式里有超过一个组合的话)。结果中包含空匹配。
在 3.7 版更改: 非空匹配现在可以在前一个空匹配之后出现了。
-
re.finditer(pattern, string, flags=0)
与
re.findall
类似,但结果是一个迭代器。在 3.7 版更改: 非空匹配现在可以在前一个空匹配之后出现了。
-
re.sub(pattern, repl, string, count=0, flags=0)
默认情况下,将在 string 中找到的第一个匹配 pattern 的子串替换为 repl,并返回整个字符串。如果 pattern 没有匹配到内容,就直接返回 string , repl 可以是字符串或者接受单个参数的可调用对象;如果是字符串,任何转义字符都会被处理。也就是说
'\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 是一个可调用对象,那么它会处理每个非重复的 pattern 匹配到的
re.Match
对象。这个可调用对象应该接受一个Match
对象作为参数,并返回一个替换后的字符串。比如>>> def dashrepl(matchobj): ... if matchobj.group(0) == '-': return ' ' ... else: return '-' >>> re.sub('-{1,2}', dashrepl, 'pro----gram-files') 'pro--gram files' >>> re.sub(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE) 'Baked Beans & Spam'
pattern 可以是一个字符串或者一个
re.Pattern
对象 。可选参数 count 是要替换的最大次数;count 必须是非负整数。如果忽略这个参数,或者设置为0,所有的匹配内容都会被替换。只有当与之前的空匹配不相邻时,才会替换模式的空匹配,所以
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>
把 pattern 作为一整个组进行引用。在 3.1 版更改: 增加了可选标记参数。
在 3.5 版更改: 不匹配的组合替换为空字符串。
在 3.6 版更改: pattern 中的未知转义(由
\
和一个 ASCII 字符组成)被视为错误。在 3.7 版更改: repl 中的未知转义(由
\
和一个 ASCII 字符组成)被视为错误。模式中的空匹配相邻接时会被替换。 -
re.subn(pattern, repl, string, count=0, flags=0)
行为与
sub()
相同,但是返回一个二元组(替换后的字符串, 替换次数)
。在 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()
清除正则表达式缓存。
3.3 异常
-
exception re.error(msg, pattern=None, pos=None)
当传递到函数的字符串不是一个有效正则表达式的时候(比如,包含一个不匹配的括号)或者在编译时或匹配时产生其他错误时引发一个异常。如果模式在字符串中未匹配到内容,是不会被视为错误的。
错误实例有以下附加属性:
-
msg
未格式化的错误消息。
-
pattern
正则表达式模式。
-
pos
编译失败的 pattern 的位置索引(可以是 None )。
-
lineno
对应 pos (可以是 None) 的行号。
-
colno
对应 pos (可以是 None) 的列号。
在 3.5 版更改: 添加了附加属性。
-
4. 正则表达式对象
编译后的正则表达式对象 re.Pattern
支持以下方法和属性:
-
Pattern.search(string[, pos[, endpos]])
扫描整个 string 寻找第一个匹配的位置, 并返回一个相应的匹配对象。如果没有匹配,就返回 None ;注意它和零长度匹配是不同的。 可选的第二个参数 pos 给出了字符串中开始搜索的位置索引;默认为
0
,它不完全等价于字符串切片;'^'
模式字符匹配字符串的实际起始位置和换行后的位置,但不一定匹配搜索开始的索引位置。可选参数 endpos 限定了字符串的搜索范围;它假定字符串只有 endpos 个字符 , 所以只有索引从 pos 到 endpos - 1 的字符会被匹配。
如果 endpos 小于 pos,就不会有匹配产生;另外,如果 rx 是一个编译后的正则对象,
rx.search(string, 0, 50)
等价于rx.search(string[:50], 0)
。>>> pattern = re.compile('d') >>> pattern.search('dog') # Match at index 0 <re.Match object; span=(0, 1), match='d'> >>> pattern.search('dog', 1) # No match; search doesn't include the 'd'
-
Pattern.match(string[, pos[, endpos]])
如果 string 开头的零个或多个字符与此正则表达式匹配,则返回相应的匹配对象。如果不匹配,就返回 None ;注意它与零长度匹配是不同的。
可选参数 pos 和 endpos 与
search()
含义相同。>>> 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'
如果希望在 string 中的任何位置查找匹配项,使用
search()
来替代(可以参考 [search() vs. match()](# 6.3 search() vs. match()))。 -
Pattern.fullmatch(string[, pos[, endpos]])
如果整个 string 匹配这个正则表达式,就返回一个相应的匹配对象 。否则就返回 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]])
类似函数
finiter()
,使用了编译后模式,但也可以接收可选参数 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()
函数的支持。编译后的正则表达式对象被认为是原子性的。
5. re.Match 对象
Match
对象总是对应于布尔值 True
。如果没有匹配的话, match()
和search()
返回 None, 所以你可以简单的用 if
语句来判断是否匹配:
match = re.search(pattern, string)
if match:
process(match)
Match
对象支持以下方法和属性:
-
Match.group([group1, …])
返回匹配项的一个或多个子组。如果只有一个参数,结果就是一个字符串,如果有多个参数,结果就是一个元组(每个参数对应一个项),如果没有参数,group1 默认为 0(整个匹配项都被返回)。 如果某个 groupN 参数为 0,相应的返回值就是匹配到的整个字符串;如果 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') >>> m.group(0, 1, 2) ('Isaac Newton', 'Isaac', 'Newton') >>> re.match(r'(\d+)*(\d+)(\d+)*', '324234').group(1, 2, 3) # 第三个子组未匹配到内容,返回 None ('32423', '4', None)
如果正则表达式使用了
(?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到模式中出现的任意多的子组。default 参数用于不参与匹配的情况,默认为 None。
例如:
>>> m = re.match(r'(\d+)\.(\d+)', '24.1632') >>> m.groups() ('24', '1632')
如果我们使小数点可选,那么不是所有的组都会参与到匹配当中。未参与匹配的组默认会返回一个 None ,当指定了 default 参数时,未参与匹配的组将返回 defaults 参数的值。
>>> 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'} >>> re.match(r'(?P<first>\d+)*(?P<second>\d+)*(?P<thrid>\d+)*', '324234').groupdict(0) {'first': '324234', 'second': 0, 'thrid': 0}
-
Match.start([group])
-
Match.end([group])
返回 group 匹配到的字串的开始和结束标号。group 默认为0(意思是整个匹配的子串)。如果 group 存在,但未产生匹配,就返回
-1
。对于一个匹配对象 m, 和一个未参与匹配的组 g ,组 g (等价于m.group(g)
)产生的匹配是:m.string[m.start(g):m.end(g)]
注意,如果 group 匹配一个空字符串的话,
m.start(group)
将会等于m.end(group)
。比如,对于
m = re.search('b(c?)', 'cba')
,m.start(0)
为 1,m.end(0)
为 2,m.start(1)
和m.end(1)
都是2,m.start(2)
则引发 一个IndexError
异常。这个例子会从email地址中移除掉 remove_this:
>>> email = 'tony@tiremove_thisger.net' >>> m = re.search('remove_this', email) >>> email[:m.start()] + email[m.end():] 'tony@tiger.net'
-
Match.expand(template)
对 template 进行反斜杠转义替换并且返回替换后的结果,就像
sub()
方法中所做的一样。'\n'
这样的转义字符将被转换成合适的字符,对子组的反向编号引用('\2'
)和反向命名引用(\g<1>
或\g<name>
) 将替换为相应子组匹配到的内容。如果子组未匹配到内容,则使用空字符串''
替代。在 3.5 版更改: 不匹配的组合替换为空字符串。
>>> m = re.match(r'(?P<first>\d+)*(?P<second>\d+)*(?P<third>\d+)*', '23432424') >>> m.groupdict() {'first': '23432424', 'second': None, 'third': None} >>> m.expand(r'\g<first>') # 反向命名引用 '23432424' >>> m.expand(r'\g<1>') # 反向数字引用 '23432424' >>> m.expand(r'\1') # 反向数字引用 '23432424' >>> m.expand(r'\2') # 第二个组未匹配到内容,所以为空字符串 ''
-
Match.span([group])
对于一个匹配对象 m , 返回一个二元组
(m.start(group), m.end(group))
。 注意如果 group 没有在这个匹配中,就返回(-1, -1)
。group 默认为0,就是整个匹配。 -
Match.pos
传递给正则对象的
search()
或match()
方法的 pos 参数的值 。这个值对应于字符串中某个字符的索引值,从这个字符开始,正则引擎开始查找匹配项。 -
Match.endpos
传递给正则对象的
search()
或match()
方法的 endpos 参数的值 。这个值是到字符串的索引,从这个索引位置开始,正则引擎停止查找匹配项。 -
Match.lastindex
捕获组的最后一个匹配的整数索引值,如果没有匹配到的话则为 None。比如,对于字符串
'ab'
,表达式(a)b
,((a)(b))
, 和((ab))
将得到lastindex == 1
, 而(a)(b)
会得到lastindex == 2
。 -
Match.lastgroup
最后一个匹配到内容的子组的名称,如果子组没有名称或者没有匹配到的话则为 None。
-
Match.re
返回产生这个实例的正则对象 , 这个实例是由正则对象的
match()
或search()
方法产生的。 -
Match.string
传递到
match()
或search()
的字符串。
在 3.7 版更改: 添加了对
copy.copy()
和copy.deepcopy()
的支持。匹配对象被看作是原子性的。
6. 正则表达式例子
6.1 检查对子
在这个例子里,我们使用以下辅助函数来更好的显示匹配对象:
def displaymatch(match):
if match is None:
return None
return '<Match: %r, groups=%r>' % (match.group(), match.groups())
假设你在写一个扑克程序,一个玩家的一手牌为五个字符的串,每个字符表示一张牌,“a” 就是 A, “k” 就是 K,“q” 就是 Q,“j” 就是 J, “t” 为 10,“2” 到 “9” 表示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'
6.2 模拟 scanf() 函数
Python 目前没有一个类似c函数 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
6.3 search() vs. 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'>
6.4 建立一个电话本
split()
将字符串用参数传递的模式分隔开。这个方法对于转换文本数据到易读而且容易修改的数据结构,是很有用的,如下面的例子证明。
首先,这里是输入。通常是一个文件,这里我们用三引号字符串语法
>>> text = '''Ross McFluff: 834.345.1254 155 Elm Street
...
... Ronald Heathmore: 892.345.3428 436 Finley Avenue
... Frank Burger: 925.541.7625 662 South Dogwood Way
...
...
... Heather Albrecht: 548.326.4584 919 Park Place'''
条目用一个或者多个换行符分开。现在我们将字符串转换为一个列表,每个非空行都有一个条目:
>>> entries = re.split('\n+', text)
>>> entries
['Ross McFluff: 834.345.1254 155 Elm Street',
'Ronald Heathmore: 892.345.3428 436 Finley Avenue',
'Frank Burger: 925.541.7625 662 South Dogwood Way',
'Heather Albrecht: 548.326.4584 919 Park Place']
最终,将每个条目分割为一个由名字、姓氏、电话号码和地址组成的列表。我们为 split()
使用了 maxsplit
形参,因为地址中包含有被我们作为分割模式的空格符:
>>> [re.split(':? ' , entry, 3) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155 Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436 Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662 South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919 Park Place']]
:?
模式匹配姓后面的冒号,它是分隔符的一部分,因此不出现在结果列表中。如果 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']]
6.5 文字整理
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.'
6.6 找到所有副词
findall()
匹配模式 所有 的出现位置,而不是像 search()
只匹配第一个。比如,如果一个作者希望找到文字中的所有副词,他可能会按照以下方法用findall()
>>> text = 'He was carefully disguised but captured quickly by police.'
>>> re.findall(r'\w+ly', text)
['carefully', 'quickly']
6.7 找到所有副词和位置
如果需要匹配模式的更多信息, 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
6.8 原始字符记法
原始字符串记法 (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='\\'>
6.9 写一个词法分析器
一个 词法器或词法分析器 分析字符串,并分类成目录组。 这是写一个编译器或解释器的第一步。
文字目录是由正则表达式指定的。这个技术是通过将这些模式合并为一个主正则式,并且循环匹配来实现的
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)