零、引言
在《Dive into Python》(深入python)中,第七章介绍正則表達式,开篇非常好的引出了正則表達式,以下借用一下:我们都知道python中字符串也有比較简单的方法,比方能够进行搜索(index,find和count),替换(replace)和解析(split),这在本系列前篇数据结构篇中有所涉及,可是有种种限制。比方要进行大写和小写不敏感的搜索时,可能就须要先对字符串进行str.lower()或str.upper()将字符串先统一转换成小写或者大写在进行搜索。
那么,本篇的主角正則表達式呢?正則表達式本身是一种小型的、高度专业化的编程语言,它内嵌在Python中,并通过 re 模块实现。使用这个小型语言,你能够为想要匹配的对应字符串集指定规则;该字符串集可能包括英文语句、e-mail地址、TeX命令或不论什么你想搞定的东西。
以下借用《Dive into Python》中的样例比較一下正則表達式和字符串方法:
>>> str = "1000 NORTH MAIN ROAD"
>>> str.replace("ROAD","RD.") -- 通常使用时可能会使用RD.取代ROAD
'1000 NORTH MAIN RD.'
>>> str = "1000 NORTH BROAD ROAD" -- 对于BROAD中的ROAD也进行了替换,这并非我们想要的
>>> str.replace("ROAD","RD.")
'1000 NORTH BRD. RD.'
>>> str[:-4]+str[-4:].replace("ROAD","RD.") -- 通过切片仅仅对最后四个字符的ROAD进行替换
'1000 NORTH BROAD RD.'
>>> import re
>>> re.sub('ROAD$',"RD.",str) -- 使用正則表達式re中的sub函数对str中的"ROAD$"使用"RD."进行替换,而$在正則表達式中表示行末,所以此句仅仅对最后的ROAD进行了替换,使用起来比上面使用字符串切片再匹配要简单的多
'1000 NORTH BROAD RD.'
上面仅仅是举了简单的小样例介绍Python中的正則表達式,在这样的小样例中python的正則表達式要比字符串方法好用方便很多,更何况它还有更强大的功能?
一、简单介绍
上文已经提及,正則表達式(RE)自身是一种小型的、高度专业化的编程语言,所以,在提供了正則表達式的语言里,正則表達式的语法都是一样的,差别仅仅在于不同的编程语言实现支持的语法数量不同;但不用操心,不被支持的语法一般是不经常使用的部分。假设已经在其它语言里使用过正則表達式,仅仅须要简单看一看就能够s上手了。这里给大家推荐一个学习正則表達式的好资源《正則表達式30分钟新手教程》。
上文显示了正則表達式的工作流程,首先语言中的正則表達式引擎会将用户使用的正則表達式文本编程成正則表達式对象,然后依次拿出表达式对象和文本中的字符比較,假设每个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。假设表达式中有量词或边界,这个过程会略微有一些不同,但也是非常好理解的,看下图中的演示样例以及自己多使用几次就能明确。
二、正則表達式元字符语法
这里偷个小懒,直接将《Python正則表達式指南》[2]中大神总结的正則表達式语法图拿过来用了:
注: 正則表達式之所以复杂难懂,除了它有上面如此多的元字符外,这些元字符之间的组合模式使得RE变得很难懂。比方: \S 匹配非空白字符,而加上 + 后 \S+ 则表示不包括空白字符的字符串!
此外, ^ 字符在上面图片中解释为字符串開始,事实上并不完好,在[ a ] 这种匹配中,^ 表示 “非” ,比方 [ a ]匹配字符 a,而[ ^a ] 则匹配除 a 以外的随意字符。
对于上面图文中没有列出来的,再扩展几条:
2.1 反斜杠!!
与大多数编程语言同样,正則表達式里使用"\"作为转义字符,这就可能造成反斜杠困扰。比方你想要在匹配文本中匹配制表符"\t",你须要写成"\\t",直接匹配反斜杠需写成"\\",很多其它时会显得尤为复杂。
在Python中,原生字符串非常好地攻克了这个问题,方法为在匹配文本前加r,如r'\'表示匹配'\'本身。相同,匹配一个数字的"\\d"能够写成r"\d"。有了原生字符串,你再也不用操心是不是漏写了反斜杠,写出来的表达式也更直观。
2.2 贪婪与懒惰
当正則表達式中包括能接受反复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。比如: a.*b
,它将会匹配 最长的以 a 開始,以 b 结束的字符串 。假设用它来搜索 aabab 的话,它会匹配整个字符串 aabab 。这被称为 贪婪 匹配。
有时,我们更须要 懒惰 匹配,也就是匹配尽可能少的字符。前面给出的限定符都能够被转化为懒惰匹配模式,仅仅要在它后面加上一个问号 ? 。这样 .*? 就意味着 匹配随意数量的反复, 可是在能使整个匹配成功的前提下使用最少的反复 。比方前面提到的 a.*b ,它的懒惰版为: a.*?b
匹配 最短的,以 a 開始,以 b 结束的字符串 。假设把它应用于 aabab 的话,它会匹配 aab (第一到第三个字符)和 ab (第四到第五个字符)。
>>> str = "aabab"
>>> pattern = "a.*b"
>>> print re.match(pattern,str).group(0)
aabab
>>> pattern = "a.*?b"
>>> print re.match(pattern,str).group(0)
注: 你可能发问了:既然是懒惰匹配,为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,由于正則表達式有还有一条规则,且比懒惰 / 贪婪规则的优先级更高:最先開始的匹配拥有最高的优先权。
——The match that begins earliest wins。
三、一些Python 的RE方法及其应用
在此我们能够使用本系列前篇 -- Python自省中的apihelper模块来查看一下python的 re 模块中有哪些方法:
>>> import re
>>> import apihelper as ah
>>> ah.info(re)
Scanner None
_compile None
...
compile Compile a regular expression pattern, returning a pattern object.
error None
escape Escape all non-alphanumeric characters in pattern.
findall Return a list of all non-overlapping matches in the string. If one or more groups are present in the pattern, return a list of groups; this will be a list of tuples if the pattern has more than one group. Empty
matches are included in the result.
finditer Return an iterator over all non-overlapping matches in the string. For each match, the iterator returns a match object. Empty matches are included in the result.
match Try to apply the pattern at the start of the string, returning a match object, or None if no match was found.
purge Clear the regular expression cache
search Scan through string looking for a match to the pattern, returning a match object, or None if no match was found.
split Split the source string by the occurrences of the pattern, returning a list containing the resulting substrings.
sub Return the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in string by the replacement repl. repl can be either a string or a callable; if a string, backslash escapes in it
are processed. If it is a callable, it's passed the match object and must return a replacement string to be used.
subn Return a 2-tuple containing (new_string, number). new_string is the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in the source string by the replacement repl. number is the
number of substitutions that were made. repl can be either a string or a callable; if a string, backslash escapes in it are processed. If it is a callable, it's passed the match object and must return a replacement string to be used.
template Compile a template pattern, returning a pattern object
所以,我们能够大概知道,RE模块主要包含,compile, escape, findall, ...等方法,而详细怎样使用,我们能够使用 “
>>> help(re.match) ” 这样查看每一个方法的帮助文档。
闲话不多说,以下将对这些函数一一进行简介:
3.1 python的 re 模块简单使用
前文以及提及,Python通过 re 模块提供对正則表達式的支持。使用 re 的一般步骤是先将正則表達式的字符串形式编译为Pattern实例,然后使用Pattern实例处理文
本并获得匹配结果(一个Match实例),最后使用Match实例获得信息,进行其它的操作。
>>> import re
>>> pattern = re.compile(r'hello') # 将正則表達式编译成Pattern对象
>>> match = pattern.match('hello world!') # 使用Pattern匹配文本,获得匹配结果,无法匹配时将返回None
>>> if match:
... print match.group() # 假设匹配,打印匹配信息
...
hello
RE的编译方法:
re.compile(strPattern[, flag])
这种方法是Pattern类的工厂方法,用于将字符串形式的正則表達式编译为Pattern对象。 其第二个參数flag是匹配模式,取值能够使用按位或运算符 '|' 表示同一时候生效,比方 re.I | re.M。编译标志让你能够改动正則表達式的一些执行方式。在 re 模块中标志能够使用两个名字,一个是全名如 IGNORECASE,一个是缩写,一字母形式如 I。(标志及其相应的flag及缩写都在下表中清晰的显示)
另外,你也能够在regex字符串中指定模式,比方re.compile('pattern', re.I | re.M)与re.compile('(?im)pattern')是等价的。
对于flag 可选值有:
名称 | 修饰符 | 说明 |
IGNORECASE(忽略大写和小写) | re.I re.IGNORECASE |
忽略大写和小写,使匹配对大写和小写不敏感 |
MULTILINE (多行模式) | re.M re.MULTILINE |
多行模式,改变'^'和'$'的行为 |
DOTALL(点随意匹配模式) | re.S re.DOTALL |
点随意匹配模式,改变'.'的行为,使 '.' 匹配包含换行在内的全部字符;没有这个标志, "." 匹配除了换行外的不论什么字符。 |
LOCALE(使预定字符类) | re.L re.LOCALE |
做本地化识别(locale-aware)匹配,使预定字符类 \w \W \b \B \s \S 取决于当前区域设定。locales 是 C 语言库中的一项功能,是用来为须要考虑不同语言的编程提供帮助的。举个样例,假设你正在处理法文文本,你想用 "w+ 来匹配文字,但 "w 仅仅匹配字符类 [A-Za-z];它并不能匹配 "é" 或 "?"。假设你的系统配置适当且本地化设置为法语,那么内部的 C 函数将告诉程序 "é" 也应该被觉得是一个字母。 |
UNICODE(Unicode模式) | re.U re.UNICODE |
依据Unicode字符集解析字符,使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性 |
VERBOSE(具体模式) | re.X re.VERBOSE |
这个模式下正則表達式能够是多行,忽略空白字符,并能够增加凝视。详见4.1 松散正則表達式 |
re 模块还提供了一些其它方法能够不用先编译pattern实例,直接用法匹配文本,如上面这个样例能够简写为:(使用compile 编译的优点是可重用)
m = re.match(r'hello', 'hello world!')
print m.group()
re模块还提供了一个方法escape(string),用于将string中的正則表達式元字符如*/+/?等之前加上转义符再返回>,在须要大量匹配元字符时有那么一点用。
>>> re.escape('.')
'\\.'
>>> re.escape('+')
'\\+'
>>> re.escape('?')
'\\?'
3.2 python RE模块之match 方法
对于 re 的match 方法,首先我们使用help 查看其介绍:
match(pattern, string, flags=0)
Try to apply the pattern at the start of the string, returning a match object, or None if no match was found.
解释: match()函数仅仅检測RE是不是在string的開始位置匹配, 也就是说match()仅仅有在0位置匹配成功的话才有返回,假设不是開始位置匹配成功的话,match()就返回none。
eg.
>>> print re.match(r'hello', 'hello world!').group()
hello
>>> print re.match(r'hello', 'My name is zhou, hello world!').group()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'
事实上Match对象是一次匹配的结果,包括了非常多关于此次匹配的信息,能够使用Match提供的可读属性或方法来获取这些信息。
属性:
string: 匹配时使用的文本。
re: 匹配时使用的Pattern对象。
pos: 文本中正則表達式開始搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名參数同样。
endpos: 文本中正則表達式结束搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名參数同样。
lastindex: 最后一个被捕获的分组在文本中的索引。假设没有被捕获的分组,将为None。
lastgroup: 最后一个被捕获的分组的别名。假设这个分组没有别名或者没有被捕获的分组,将为None。
方法:
group([group1, …]):
获得一个或多个分组截获的字符串;指定多个參数时将以元组形式返回。group1能够使用编号也能够使用别名;编号0代表整个匹配的子串;>不填写參数时,默认返回group(0);没有截获字符串的组返回None;截获了多次的组返回最后一次截获的子串。
groups([default]):
以元组形式返回所有分组截获的字符串。相当于调用group(1,2,…last)。default表示没有截获字符串的组以这个值替代,默觉得None。
groupdict([default]):
返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包括在内。default含义同上。
start([group]):
返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0。
end([group]):
返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0。
span([group]):
返回(start(group), end(group))。
如:
>>> match = re.match(r'hello', 'hello world!')
>>> print match.string -- 打印匹配使用的文本
hello world!
>>> print match.re -- 匹配使用的Pattern对象
<_sre.SRE_Pattern object at 0xb7522520>
>>> print match.pos -- 文本中正則表達式開始搜索的索引
0
>>> print match.endpos -- 文本中正則表達式结束搜索的索引,一般与len(string)相等
12
>>> print match.lastindex
None
>>> print match.lastgroup
None
>>> print match.groups()
()
>>> print match.group() -- 不带參数,默觉得group(0),打印整个匹配的子串
hello
>>> print match.groupdict()
{}
>>> print match.start() -- 匹配到的字符串在string中的起始索引
0
>>> print match.end() -- 匹配到的字符串在string中的结束索引
5
>>> print match.span() -- 打印(start,end)
(0, 5)
3.3 python RE模块之search 方法
相同,我们首先使用help 查看其介绍:
Scan through string looking for a match to the pattern, returning a match object, or None if no match was found.
re.search()会扫描整个字符串并返回第一个成功的匹配,假设匹配不上返回 None。
3.4 python RE模块之两种匹配方法比較(match VS search)
从上面两小节的介绍中我们也能够知道 re.match 从字符串的開始处開始匹配,假设字符串開始不符合正則表達式,则匹配失败,函数返回None;而re.search 查找整个字符串,直到找到一个匹配;若无匹配返回None。
从样例中我们能够轻易的看出差别:
>>> match = re.match(r'hello', 'my name is zhou, hello world!')
>>> search = re.search(r'hello', 'my name is zhou, hello world!')
>>> match.group()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'
>>> search.group()
'hello'
>>> search.start()
17
>>> search.end()
22
3.5 python RE模块之sub 方法
照旧,我们使用help:
sub(pattern, repl, string, count=0, flags=0)
Return the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in string by the replacement repl. repl can be either a string or a callable; if a string, backslash escapes in it are processed. If it is a callable, it's
passed the match object and must return a replacement string to be used.
解释:
使用repl替换string中每个匹配的子串后返回替换后的字符串。
当repl是一个字符串时,能够使用\id或\g<id>、\g<name>引用分组,但不能使用编号0。
当repl是一个方法时,这种方法应当仅仅接受一个參数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。
count用于指定最多替换次数,不指定时所有替换。
eg.
>>> import re
>>> p = re.compile(r'(\w+) (\w+)') -- 匹配p为以空格连接的两个单词
>>> s = 'i say, hello world!'
>>> print p.sub(r'\2 \1', s) -- 以\id引用分组,将每一个匹配中的两个单词掉转位置
say i, world hello!
>>> def func(m): -- 一个函数,将匹配单词的首字母改为大写
... return m.group(1).title() + ' ' + m.group(2).title()
...
>>> print p.sub(func, s) -- 使用sub调用func 函数
I Say, Hello World!
3.6 python RE模块之subn 方法
照旧,我们使用help:
subn(pattern, repl, string, count=0, flags=0)
Return a 2-tuple containing (new_string, number).
new_string is the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in the source string by the replacement repl. number is the number of substitutions that were made. repl can be either a string or a callable; if a string,
backslash escapes in it are processed. If it is a callable, it's passed the match object and must return a replacement string to be used.
解释:
此函数的參数与 sub 函数全然一致,仅仅只是其返回值是包含两个元素的元组:(new_string, number);第一个返回值 new_string 为sub 函数的结果,第二个 number 为匹配及替换的次数。
样例我们能够直接在上面 sub 的样例上測试:
>>> import re
>>> p = re.compile(r'(\w+) (\w+)')
>>> s = 'i say, hello world!'
>>> print p.subn(r'\2 \1', s)
('say i, world hello!', 2)
>>> def func(m):
... return m.group(1).title() + ' ' + m.group(2).title()
...
>>> print p.subn(func, s)
('I Say, Hello World!', 2)
3.7 python RE模块之findall 方法
照旧,我们使用help:
findall(pattern, string, flags=0)
Return a list of all non-overlapping matches in the string.
If one or more groups are present in the pattern, return a list of groups; this will be a list of tuples if the pattern has more than one group.
Empty matches are included in the result.
解释: 搜索string,以列表形式返回所有能匹配的子串。返回值格式为一个列表
eg.
>>> re.findall(r'\d','one1two2three3four4')
['1', '2', '3', '4']
>>> re.findall(r'one','one1two2three3four4')
['one']
>>> re.findall(r'one2','one1two2three3four4')
[]
3.8 python RE模块之finditer 方法
照旧,我们使用help:
finditer(pattern, string, flags=0)
Return an iterator over all non-overlapping matches in the string. For each match, the iterator returns a match object.
Empty matches are included in the result.
解释:找到 RE 匹配的全部子串,并把它们作为一个迭代器返回。这个匹配是从左到右有序地返回。假设无匹配,返回空列表。
eg.
>>> re.finditer(r'\d','one1two2three3four4').group() -- python的迭代器并不能像findall那样直接打印全部匹配
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'callable-iterator' object has no attribute 'group'
<callable-iterator object at 0xb744496c> -- 迭代器仅仅能一个接着一个的打印
>>> for m in re.finditer(r'\d','one1two2three3four4'):
... print m.group()
...
1
2
3
4
3.9 python RE模块之purge 方法
照旧,我们使用help:
purge()
Clear the regular expression cache
解释:清空缓存中的正則表達式。
eg.
>>> p = re.compile(r'(\w+) (\w+)')
>>> p.search("hello world 123 zhou write").group() -- 匹配字符串中的两个单词正常
'hello world'
>>> p = re.purge() -- 清空RE缓存
>>> p.search("hello world 123 zhou write").group() -- 再次使用search,报错!此时p 变成了'NoneType'对象,而不是Pattern对象
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'search'
3.10 python RE模块之split 方法
照旧,我们使用help:
split(pattern, string, maxsplit=0, flags=0)
Split the source string by the occurrences of the pattern, returning a list containing the resulting substrings.
解释:依照可以匹配的子串将string切割后返回列表。maxsplit用于指定最大切割次数,不指定使用默认值0将所有切割。如设定好切割次数后,最后未切割部分将作为列表中的一个元素返回。
eg.
>>> re.split(r'\d','one1two2three3four4')
['one', 'two', 'three', 'four', '']
>>> re.split(r'\d','one1two2three3four4',2)
['one', 'two', 'three3four4']
>>> re.split(r'\d','one1two2three3four4',0)
['one', 'two', 'three', 'four', '']
四、About : python 中的正則表達式
4.1 松散正則表達式
对于一个正則表達式,可能过一段时间你就须要又一次分析这个表达式的实际意思,由于它本身就是由多个元字符组成,可能你还须要比較着上面的元字符的意思再分析你的表达式文本,那么有什么好的方法可以解决问题?比方说以下这个表达式:(dive into python ,P129)
pattern = "^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"
Python支持使用所谓的松散正則表達式来解决问题:
>>> import re
>>> pattern = """
# 本正則表達式为匹配一个罗马数字,而罗马数字中MDCCCLXXXVIII表示1888,所以须要找到其规律写成对应的匹配文本,使用该表达式匹配通过的为罗马数字,否则不是!
# 罗马数字中: I = 1; V = 5; X = 10; L = 50; C = 100; D = 500; M = 1000
# 罗马数字的规则:
# 1. 字符是叠加的,如I表示1,而II表示2,III表示3
# 2. 含十字符(I,X,C,M)至多能够反复三次,而应该利用下一个最大的含五字符进行减操作,如:对于4不能使用IIII而应该是IV,40不是XXXX而是XL,41为XLI,而44为XLIV
# 3. 相同,对于9须要使用下一个十字符减操作,如9表示为IX,90为XC,900为CM
# 4. 含五字符(V,L,D)不能反复,如:10不应该表示为VV而是X
# 5. 罗马数字从高位到低位书写,从左到右阅读,因此不同顺序的字符意义不同,如,DC为600而CD为400.CI 表示101 而IC 为不合法的罗马数字
^ # 字符串开头
M{0,3}
# 千位,支持0到3个M
(CM|CD|D?C{0,3})
# 百位,900(CM),400(CD),0~300(0~3个C),500~800(D后接0~3个C)
(XC|XL|L?X{0,3})
# 十位,90(XC),40(XL),0~30(0~3个X),50~80(L后接0~3个X)
(IX|IV|V?I{0,3})
# 个位,9(IX),4(IV),0~3(0~3个I),5~8(V后接0~3个I)
$ # 字符串结尾
"""
>>> re.search(pattern,"M",re.VERBOSE)
-- 匹配M成功。注:使用松散正則表達式必须加入re.VERBOSE(或者re.X)作为其第三个參数flag。
<_sre.SRE_Match object at 0xb7557e30>
>>> re.match(pattern,"M",re.VERBOSE).group()
-- 打印匹配文本
'M'
>>> re.match(pattern,"MMMDD",re.VERBOSE).group()
-- 五字符D反复2次,不符合罗马数字规范,不匹配
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'
>>> re.match(pattern,"MMMDCCCLXXXVIII",re.VERBOSE).group()
-- MMMDCCCLXXXVIII匹配成功
'MMMDCCCLXXXVIII'
所以,使用松散正則表達式,我们能够加入足够详尽的凝视内容,能够让你在N年后打开代码,非常快读懂。这就是松散正則表達式的作用!
===================================
引用:
[1] 《Dive into Python》-- Chapter 7 正則表達式:免费下载通道(罗嗦一句,深入Python,新手必看,以实例入手介绍python,好书)
[2] 神文:《Python正則表達式指南》http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html
[3] 《正則表達式30分钟新手教程》:免费下载通道
[4] Python 正則表達式 http://www.w3cschool.cc/python/python-reg-expressions.html
===================================
拓展阅读: