正则表达式和python的re模块
9月 4 2014更新日期:9月 4 2014
什么是正则表达式
在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。正则表达式就是记录文本规则的代码,换句话说,正则表达式是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”)。模式描述在搜索文本时要匹配的一个或多个字符串。
字符是计算机软件处理文字时最基本的单位,可能是字母,数字,标点符号,空格,换行符,汉字等等。字符串是0个或更多个字符的序列。文本也就是文字,字符串。说某个字符串匹配某个正则表达式,通常是指这个字符串里有一部分(或几部分分别)能满足表达式给出的条件。
假设你要在英文小说中查找Hi,那么使用的正则表达式就是Hi
,这个很简单吧,不过,通常处理正则表达式的工具(例如后面会提到的python的re模块)会提供忽略大小写的选项。
不幸的是,很多单词里都包含了很多单词里包含hi这两个连续的字符,比如him,history,high等等。用hi来查找的话,这里边的hi也会被找出来。如果要精确地查找hi这个单词的话,我们应该使用\bhi\b
。
\b是正则表达式规定的一个特殊代码(这里称为元字符,Metacharacter),代表着单词的开头或结尾,也就是单词的分界处。
假设你要找的Hi后面不远处有一个ZH,那么可以用\bhi\b.*\bZH\b
.
这里,.是另一个元字符,匹配除了换行符以外的任意字符。*同样是元字符,不过它代表的不是字符,也不是位置,而是数量——它指定*前边的内容可以连续重复使用任意次以使整个表达式得到匹配,例如,zo* 能匹配 "z" 以及 "zoo"等 。
如果同时使用其它元字符,我们就能构造出功能更强大的正则表达式。比如下面这个例子:
0\d\d-\d\d\d\d\d\d\d\d
匹配这样的字符串:以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字(也就是中国的电话号码。当然,这个例子只能匹配区号为3位的情形)。
现在你已经知道几个很有用的元字符了,如\b,.,*,还有\d.正则表达式里还有更多的元字符,比如\s匹配任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等。\w匹配字母或数字或下划线或汉字等。
下面来看看更多的例子:
\ba\w*\b
匹配以字母a开头的单词——先是某个单词开始处(\b
),然后是字母a,然后是任意数量的字母或数字(\w*
),最后是单词结束处(\b
)。
好吧,现在我们说说正则表达式里的单词是什么意思吧:就是不少于一个的连续的\w
。不错,这与学习英文时要背的成千上万个同名的东西的确关系不大 :)
\d+
匹配1个或更多连续的数字。这里的+
是和*
类似的元字符,不同的是*匹配重复任意次(可能是0次),而+
则匹配重复1次或更多次。
\b\w{6}\b
匹配刚好6个字符的单词。
元字符使用一览表:
上面介绍了部分的Metacharacter,下面给出元字符一览表,使用时可以查找。
字符 |
说明 |
---|---|
字符转义
如果你想查找元字符本身的话,比如你查找.,或者,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用\来取消这些字符的特殊意义。因此,你应该使用.和\。当然,要查找\本身,你也得用\.
例如:deerchao\.net
匹配deerchao.net
,C:\\Windows
匹配C:\Windows
。
重复
正则表达式第一件能做的事是能够匹配不定长的字符集,而这是其它能作用在字符串上的方法所不能做到的。 不过,如果那是正则表达式唯一的附加功能的话,那么它们也就不那么优秀了。它们的另一个功能就是你可以指定正则表达式的一部分的重复次数。
就像前面介绍的元字符*
.*
并不匹配字母字符 “*”;相反,它指定前一个字符可以被匹配零次或更多次,而不是只有一次。
上面我们的元字符表把大部分元字符都说了,这里我们抽取出重复限定符。
代码/语法 | 说明 |
---|---|
再举个例子,ca?t 将匹配 “ct” (0 个 “a” 字符) 或 “cat” (1 个 “a”);
有了上面的这些限定元字符,可以很好得处理重复情况,只要运用得当。
字符类
要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办?
很简单,你只需要在方括号里列出它们就行了,像[aeIoU]就匹配任何一个英文元音字母,[.?!]匹配标点符号(.或?或!)。
我们也可以轻松地指定一个字符范围,像[0-9]代表的含意与\d就是完全一致的:一位数字;同理[a-z0-9A-Z_]也完全等同于\w(如果只考虑英文的话)。
下面是一个更复杂的表达式:(?0\d{2}[) -]?\d{8}。
“(”和“)”也是元字符,后面的分组节里会提到,所以在这里需要使用转义。
这个表达式可以匹配几种格式的电话号码,像(010)88886666,或022-22334455,或02912345678等。我们对它进行一些分析吧:首先是一个转义字符(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字(\d{2}),然后是)或-或空格中的一个,它出现1次或不出现(?),最后是8个数字(\d{8})。
分支条件
不幸的是,刚才那个表达式也能匹配010)12345678或(022-87654321这样的“不正确”的格式。要解决这个问题,我们需要用到分枝条件。正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。听不明白?没关系,看例子:
0\d{2}-\d{8}|0\d{3}-\d{7}
这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)。
\(?0\d{2}\)?[- ]?\d{8}|0\d{2}[- ]?\d{8}
这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。你可以试试用分枝条件把这个表达式扩展成也支持4位区号的。
\d{5}-\d{4}|\d{5}
这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。如果你把它改成\d{5}|\d{5}-\d{4}
的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。
分组
我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。
(\d{1,3}.){3}\d{1,3}是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字,(\d{1,3}.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。
IP地址中每个数字都不能大于255. 经常有人问我,01.02.03.04 这样前面带有0的数字,是不是正确的IP地址呢? 答案是: 是的,IP 地址里的数字可以包含有前导 0 (leading zeroes).
不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?).){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)。
理解这个表达式的关键是理解2[0-4]\d|25[0-5]|[01]?\d\d?,这里我就不细说了,你自己应该能分析得出来它的意义。
re模块
Python中得正则表达式(regular expression)模块,即re模块,功能还是很强大的。在介绍re之前,先看下面这部分。
正则表达式使用反斜杠” \ “来代表特殊形式或用作转义字符,这里跟Python的语法冲突,因此,Python用” \\ “表示正则表达式中的” \ “,因为正则表达式中如果要匹配” \ “,需要用\来转义,变成” \ “,而Python语法中又需要对字符串中每一个\进行转义,所以就变成了” \\ “。
上面的写法是不是觉得很麻烦,为了使正则表达式具有更好的可读性,Python特别设计了原始字符串(raw string),需要提醒你的是,在写文件路径的时候就不要使用raw string了,这里存在陷阱。raw string就是用’r’作为字符串的前缀,如 r”\n”:表示两个字符”\”和”n”,而不是换行符了。Python中写正则表达式时推荐使用这种形式。
下面来看一下re模块的几个函数:
compile
re.compile(strPattern[,flag]):
这个方法是Pattern类的工厂方法,用于将字符串形式的正则表达式编译为Pattern对象。
第二个参数flag是匹配模式,取值可以使用按位或运算符’|’表示同时生效,比如re.I | re.M。
另外,你也可以在regex字符串中指定模式,
比如re.compile(‘pattern’,re.I | re.M)与re.compile(‘(?im)pattern’)是等价的。
可选值有:
re.I(IGNORECASE): 忽略大小写(括号内是完整写法,下同)re.M(MULTILINE): 多行模式,改变'^'和'$'的行为(参见上图) re.S(DOTALL): 点任意匹配模式,改变'.'的行为 re.L(LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定 re.U(UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性 re.X(VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。
其实compile方法的作用,不是很明显,因为下面的两个得到的结果等价。调用compile后,返回RegexObject对象,可以用该对象调用macth()和search()等匹配方法。
import re pattern = r"hi"; string = "hi,jack"; prog = re.compile(pattern); result1 = prog.match(string); print "result1: ",result1.group(); result2 = re.match(pattern,string); print "result2: ",result2.group();
输出的结果是一样的。这里match下面会介绍,它是一个匹配的方法,group方法后面也会介绍,它这里输出的时匹配的内容。可以试验一下,就明白他两是等效的。
match和search
Python提供了两种不同的原始操作:match和search。match是从字符串的起点开始做匹配,而search(perl默认)是从字符串做任意匹配。
例子1
import re result1 = re.match("c","abcde"); if(result1): print("result1:"+result1.group()); else: print("nothing"); result2 = re.search("c","abcde"); if(result2): print("result2:"+result2.group());
输出:
nothing
result2:c
例子2
import re result1 = re.match("a","abcde"); if(result1): print("result1:"+result1.group()); else: print("nothing"); result2 = re.search("a","abcde"); if(result2): print("result2:"+result2.group());
输出:
result1:a
result2:a
例子3
match函数可以设置匹配开始的位置,下面分别从0,1,2位置开始匹配。当然也可以设置终止的位置,具体可以查API文档。
import re pattern = re.compile("c"); result1 = pattern.match("abcde",0); if(result1): print("result1:"+result1.group()); else: print("result1: nothing"); result2 = pattern.match("abcde",1); if(result2): print("result2:"+result2.group()); else: print("result2: nothing"); result3 = pattern.match("abcde",2); if(result3): print("result3:"+result3.group()); else: print("result3: nothing");
输出:
result1: nothing
result2: nothing
result3:c
split
re.split(pattern,string,maxsplit=0)
通过正则表达式将字符串分离。如果用括号将正则表达式括起来,那么匹配的字符串也会被列入到list中返回。maxsplit是分离的次数,maxsplit=1分离一次,默认为0,不限制次数。
看一下例子:
import re print re.split('\W+','Words,words,words.')
输出:
[‘Words’,‘words’,‘’]
思考一下为什么输出会是这个? 查看一下\W
的作用,注意W是大写的。
findall
re.findall(pattern,flags=0)
找到 RE 匹配的所有子串,并把它们作为一个列表返回。这个匹配是从左到右有序地返回。如果无匹配,返回空列表。
import re print re.findall("\d","1a2b3c4d");
输出:
[‘1’,‘2’,‘3’,‘4’]
finditer
re.finditer(pattern,flags=0)
找到 RE 匹配的所有子串,并把它们作为一个迭代器返回。这个匹配是从左到右有序地返回。如果无匹配,返回空列表。
import re it = re.finditer(r"\d+","123abc456efg789hij") for match in it: print match.group()
输出:
123
456
789
sub
sub(pattern,repl,count=0,flags=0)
其用途是用来替换匹配成功的字串,被替换成repl。值得一提的时,这里的repl不仅仅可以是字符串,也可以是方法。
首先看下字符串的时候,被匹配的字符串就会被替换成为repl。
import re print re.sub(r'\sAND\s',' & ','Baked Beans And Spam',flags=re.IGNORECASE)
输出:
Baked Beans & Spam
可以使用\id或\g<id>、\g<name>引用分组
.
当repl是方法的时候。这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。
import re def dashrepl(matchobj): if matchobj.group(0) == '-': return ' ' else: return '-' print re.sub('-{1,2}',dashrepl,'pro----gram-files') print re.sub('-{1,'pro----gram-files')
输出:
pro—gram files
subn
subn(repl,string[,count]) |re.sub(pattern,count]):
多返回 (sub(repl,count]),替换次数)。
import re print re.subn(r'\sAND\s',flags=re.IGNORECASE)
参考资料
- 正则表达式30分钟入门教程(推荐)
- 正则表达式语法
- Python-re模块
- 深入浅出之正则表达式(推荐)
- 正则表达式re模块详解(推荐)
- Python正则表达式操作指南(推荐)
- python正则表达式教程