re
正则表达式(regular expression)这玩意儿多nb就不用说了,python用re模块来支持正则
首先是一些正则表达式的概念
1. 通配符 .
2. 多字符选择 [...]
[abc]表示匹配abc中任何一个,而比如[A-Za-z]这种表示也是许可的。此外还有[^...]表示除了...外的字符
3. 选择符
...|...|...表示多选一,比如"a|b|c"可以匹配"a"或"b"或"c"。可以看到简写一下其实是[abc]。当然可选对象可以更复杂一些而不一定是一个一个的字符。
4. 对特殊字符的转义
如"python\.org" 可以匹配 "python.org" 而不匹配"pythonAorg"
5. 子模式
对一部分字符串加小括号以进行不同于整串的操作,比如"P(ython|erl)"同时匹配 "Python"和"Perl"。用法和数学运算中的括号类似。另外子模式的一个作用就是可以框出一部分有用的正则部分,在匹配完成之后可以用到类似于match_object.group(n)的方法来单独获得这部分的信息。
6. 可选项
(...)? 表示不管带不带这个子模式都会匹配,只识别问号前一个的字符or子模式。注意区别这个和贪婪模式的问号
7. 可重复(也都是识别标识符前一个的字符or子模式)
(...)* 重复任意次(也可不重复)
(...)+ 重复至少一次
(...){m[,n]} 重复m(到n)次
8. 字符串开头和结尾
'^...' 表示从开头开始匹配,注意区别这个和反向匹配多字符(那个有中括号)
'...$' 表示只在结尾匹配
9. 一些预定义字符
\b 表示单词边界。可能会以为单词边界就是空格,但是实际上这个\b比空格更好用一点。单词指的是数字和字母的任意序列,而单词边界指的就是非字母数字的字符或者无字符(行首或行尾)。另外\b比较糟糕的是python字符串中对\b会有转义成退格。所以在模式中添加\b如果整个模式前不加r的话就得写"\\b"了。
\d 相当于 [0-9] 所有数字字符
\D 相当于 [^0-9] 所有非数字字符
\s 相当于 [\t\r\n...] 所有空白字符
\S 相当于 所有非空白字符
\w 相当于 [a-zA-Z0-9_] 所有在单词中可能用到的字符(大小写字母,数字,下划线)
\W 相当于 [^\w] 所有非单词书写字符
■ re模块的一些方法
re的这些方法,基本上都是通过pattern去匹配相应字符串中的一些内容,然后对这些内容进行一些处理。这里提醒一下,在处理中文字串的时候要注意unicode和str类之间,以及不同编码的str类之间的兼容。即string是一个unicode类的话最好pattern前面也加上u,反之,如果string是一个unicode的话那么pattern最好也是一个string,且保证他们编码格式相同。
compile(pattern)
根据正则表达式pattern返回一个相应的模式对象,用这个对象可以调用其他所有re模块下的方法使得使用起来更加方便,比如原来要re.search(pattern,string),现在只要pattern.search(string)
search(pattern,string) 或 pattern.search(string) 下同,不再复述
从字符串中寻找相关pattern,找到则返回一个object(可以视为True),没找到则返回None(可以视为False)
match(pattern,string)
从字符串开头开始匹配,若成功匹配,返回object,否则返回None。从开头开始匹配的意思是说pattern中的第一个字符必须匹配字符串的第一个字符,第二个必须匹配第二个等等。换句话说match("ell","hello")是None,但是search("ell","hello")是有matchObject的。
split(pattern,string)
用string中任何与pattern匹配的部分作为分隔符分隔string,返回的是一个列表
findall(pattern,string)
找出string中所有和pattern匹配的部分,并将其组成一个列表返回
finditer(pattern,string)
和findall类似,只不过返回的是个findall结果列表中内容组成的迭代器。
sub(pattern,replace,string)
将string中所有pattern匹配的部分用replace串替换,string本身值不变,sub方法返回更改的字符串
replace中可以带 \g<1>,\g<2>..等参数,用来表示pattern中的相应的group。相当于是把原来字符串里匹配到一些group的内容动态地提取出来并把 其整合到replace里面
如:
originStr = "Hello, Frank"pattern = "Hello, (\w+)"replace = "Mein Name ist \g<1>"replacedStr = re.sub(pattern,replace,originStr)#得到relacedStr是"Mein Name ist Frank"
escape(string)
用于把stirng中所有实义字符(在正则表达式引擎的层面上)反转义为带'\'的形式
比如escape('www.baidu.com') ==> 'www\.baidu\.com' 这样把这个字符串去当pattern时就不会有把"."当成是通配符了
■ 匹配对象和组
之前提到了,像search这种方法会返回一个object,这个object是个MatchedObject,即匹配对象。那么这个对象中有哪些信息呢
首先要提一下group的概念。在一个pattern中,常会出现小括号括起来的子模式,根据这些子模式和总模式的关系,可以将pattern以及最终匹配出来的字符串分成若干group
比如pattern这样的话:"I (am (Kazuya) Takanashi). This (is) a test" 这里面有四个group
0: 整个字符串
1:am Kazuya Takanashi
2:Kazuya
3:is
看是第几组只要看左边出现了几个左括号
对于匹配完成后得到的MatchedObject,它有:
m.group(组号) 获取相应组的内容,若不写组号默认为0,下同
m.start(组号) 获取相应组在原串中的起始位置
m.end(组号) 获取结束位置
m.span(组号) 以元组的形式返回相应组的开始和结束位置,相当于m.span() == (m.start(),m.end())
■ 正则解析过程浅析
要匹配一个逻辑上的反斜杠,即匹配的字符串写"\\"(不能写r"\",以r开头的原始字符串的定义是“除了在单双引号前的反斜杠,所有反斜杠都不做转义解释”,然而这里反斜杠在双引号前所以仍然会转义),pattern就得要写 "\\\\" 或 r"\\" (对于那些反斜杠和它之后跟的东西并不组成一个转义字符的情况,写不写r都一样,因为python看到反斜杠会自动把它escape掉,比如s="a\.b"在内存中自动是"a\\.b"。而对于反斜杠和它之后那个字符组合起来可能引起歧义的情况就有差别了,比如s="a\tb"和k=r"a\tb",内存中两者分别是"a\tb"和"a\\tb",print出来后前者有个制表符空格,后者则是a\tb)。
可以看到,re.search("\\\\","\\")是有返回object的,说明匹配成功。这就表明其实这个方法不是直接拿pattern在内存中的形式(四条杠)和目标在内存中的形式(两条杠)比较,而是拿到前者之后先对它做一个属于系统层面的转义,再对它做一个专属于正则的转义,之后再拿去和经过正则转义的目标对比。因为在匹配一个反斜杠时的正则表达式如此麻烦,所以应该尽量写r"\\"而不是"\\\\"。
■ 关于贪婪和懒惰
重复匹配符如*,+这些默认都是贪婪的。
所谓贪婪就是说当可以匹配多种可能时,匹配最长的。比如:
pattern = "<.*>" 而 string = "<a>bcd>ef>"时,re会匹配到"<a>bcd>ef>"而不是"<a>"或"<a>bcd>"
向重复匹配符后加上?可以使其进行懒惰匹配,也就是匹配最短的,如上例中匹配到的应该是"<a>"而不是"<a>bcd>ef>"或"<a>bcd>"。(注意区分这个和可选项匹配的问号:(...)?表示可有可无的子模式或字符)
■ 一些积累
* 碰到中文的时候要注意编码,比如在utf8中每个中文字符其实占三个字节。我刚刚就踩了个坑:以为"星期."可以匹配到从星期一到星期日所有,但是一个都匹配不到。。原因就是“一”,“二”这些是中文字符,占三个字节,所以要"星期..."才能匹配得到
*正则表达式的特殊字符有* . ? + $ ^ [ ] ( ) { } | \ / 碰到这些字符时记得前面加上一个反斜杠以转义
● 关于?!,?=,?:三者的用法与区别
今天因为需要在网上百度出了这三个正则式中的“关键字”。感到不安的是之前好像对它们没什么映像,花了点时间学习了一下这三个表达各是什么意思。首先这三者的基本用法格式是:
(?!pattern)、(?=pattern)、(?:pattern)。需要注意的是,小括号在这里并不是分组的作用,而是作为固定搭配和这三个符号在一起。所以千万不能有去掉括号看看会怎么样的想法。。固定搭配固定搭配~
然后来看看前两者的作用,首先有一段复制来的比较官方的话:
(?=pattern) | 正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,’Windows (?=95|98|NT|2000)’ 能匹配 “Windows 2000″ 中的 “Windows” ,但不能匹配 “Windows 3.1″ 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。 |
(?!pattern) | 负向预查,在任何不匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如’Windows (?!95|98|NT|2000)’ 能匹配 “Windows 3.1″ 中的 “Windows”,但不能匹配 “Windows 2000″ 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始 |
首先可以看出,?=和?!基本属于互补的两个操作,前者进行正向匹配(碰到符合条件的作为结果输出),后者是逆向匹配(碰到符合条件的不作为结果输出,输出不符合条件的)。作为简单的应用上面说明中的Windows的例子已经很清楚了,然而面对更加复杂一点的情景时,就又有点迷糊了。下面是我基于(?!pattern)总结出的几条我自己的方法论,?=的话可以反过来想。这些总结不一定对,不过可以作为琢磨时的参考:
1. (?!pattern)这东西往往更在一定正则模式的后面,且解析时紧跟着前面的模式匹配。比如tomcat-(?!test)返回的结果中,不会匹配tomcat-test,但会匹配tomcat-batch;不会匹配tomcat-test-batch(test后面还有东西),但会匹配tomcat-batch-test(test远离了tomcat,没紧跟着所以也没用)。
2.(?!pattern)的运作机制是:首先把这部分之前的部分匹配,匹配完成后将被匹配串中剩余的部分与这里面的pattern进行从头匹配(请注意,这里的pattern并不需要写^。写了^的话就变成从整个被匹配串开头开始匹配,必然导致匹配是失败的,因为此时拿来和pattern作比较的已经是被匹配串被削掉开头的一部分了),如果匹配成功,则表示整一个被匹配串是不需要的,所以总的结果是不匹配(好绕啊。。。)在有了这条总结之后,第一条似乎就理所当然了,在匹配完tomcat-之后,剩余部分比如batch-test是没有办法匹配test的,所以tomcat-batch-test不被计入,所以最终tomcat-batch-test是匹配的。(还是好绕啊【笑哭】)
3.当(?!pattern)之前的部分中存在*或者+这种,不确定性的东西时,可以预见,前面的部分可能要根据具体的取值进行好几次可能的检查,在这些检查中只要有一次可以逃脱pattern的匹配,那么认为整个被匹配串仍然是有效的。比如说上例中把pattern改成tom(.*)(?!test)的话,子模式中如果匹配cat-,那么tomcat-test这个串还是会中招,在去掉tomcat-之后的test是匹配(?!test)中的test的。但是cat-并不是唯一的选择,比如实际上这个操作匹配的子模式是cat-test,即剩余全部(因为没有问号,是贪婪模式)。如果子模式是cat-test,那么留下给(?!test)匹配的只有空串,自然不匹配,所以tomcat-test在和tom(.*)(?!test)匹配的时候是返回匹配成功的,如果打印一下group(1)这个子模式,看到的也是cat-test
4.正如上面的表格所说,这两者都是不消耗字符的。比如上面的例子中,tomcat-(?!test)匹配tomcat-batch得出的MatchObject,把它的group(0)打印出来,得到的是tomcat-而不是tomcat-batch或者其他什么的。本应对应(?!test)的部分,其实只是一个拿来判断最终匹配成功与否的判断式,跟具体的正则匹配结果没有关系。
讲了一大堆,下面来看看我遇到的情景:我想从tomcat-aaa,tomcat-bbb,tomcat-aaa-test,tomcat-bbb-test中剔除所有带test的(实际上中间的不是aaa和bbb,这里是为了信息脱敏)。最后我写出的正则模式是这样的:'tomcat-(?!.*-test$)'。可以看到在pattern里面加上了.*之后让pattern的匹配力度大了不少,相当于是去除了最终正则匹配结果中所有以-test结尾的串了。
至于?:,和前两个不太一样,虽然它也必须出现在括号里面,但是(?:pattern)是指在得到正则匹配结果之后,不要把括号括起来的这部分作为一个子模式看待。也就是说再?:之后,这个括号就不能通过MatchObject.group(n)的方式调用得到了。这个操作经常在JS中使用,其作用是仅仅标记子模式而不缓存它,这样可以节约一点资源。