1. 正则表达式基础、
入门:
正则表达式的大致匹配过程是:依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。如果表达式中有量词或边界,这个过程会稍微有一些不同,但也是很好理解的。
下图列出了Python支持的正则表达式元字符和语法:
出了不少简单的例子,并对它们作了详细的说明。
1)、假设你在一篇英文小说里查找 hi ,你可以使用正则表达式 hi 。这几乎是最简单的正则表达式了,它可以精确匹配这样的字符串: 由两个字符组成,前一个字符是h,后一个是i 。但是很多单词里包含 hi 这两个连续的字符,比如 him,history等。用 hi 来查找的话,这里边的 hi 也会被找出来。如果要 精确地查找hi这个单词 的话,我们应该使用 \ bhi \ b 。\ b 是正则表达式规定的一个特殊代码(好吧,某些人叫它 元字符,Metacharacter ),代表着单词的开头或结尾,也就是单词的分界处 。虽然通常英文的单词是由空格,标点符号或者换行来分隔的,但是 \ b 并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置。假如你要找的是 hi后面不远处跟着一个Lucy ,你应该用
此, .* 连在一起就意味着任意数量的不包含换行的字符 。 现在 \ bhi \ b.*\ bLucy\ b 的意思就很明显了: 先 是一个单词hi,然后是任意个任意字符(但不能是换行),最后是Lucy这个单词 。
2)\ d+ 匹配 1个或更多连续的数字 。这里的 + 是和 * 类似的元字符,不同的是 * 匹配 重复任意次(可能
是0次) ,而 + 则匹配 重复1次或更多次 。
3)、\ b\w{6}\ b 匹配 刚好6个字符的单词
2)、<a[^>]+> 匹配用尖括号括起来的以a开头的字符串 。
如果想查找元字符本身的话,比如你查找 .,或者 *,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用 \ 来取消这些字符的特殊意义。因此,你应该使用 \ . 和 \ * 。当然,查找 \ 本身,得用 \ \
下面是一个更复杂的表达式: \ (?0\ d{2}[)-]?\ d{8} 。这个表达式可以匹配 几种格式的电话号码 ,像(010)88886666 ,或 022-22334455 ,或 02912345678 等。
1)、0\ d{2}-\ d{8}|0\ d{3}-\ d{7} 这个表达式能 匹配两种以连字号分隔的数字。
2)、\ (?0\ d{2}\ )?[-]?\ d{8}|0\ d{2}[-]?\ d{8} 这个表达式 匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔 。
3)、\ 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} )。 不幸的是,它也将匹配 256.300.888.999 这种不可能 存在的IP地址。如果能使用算术比较的话,或许能简单 地解决这个问题,但是正则表达式中并不提供关于数学 的任何功能,所以只能使用冗长的分组,选择,字符类 来描述一个正确的IP地址: ((2[04]\ d|25[05]|[01]? \ d\ d?)\ .){3}(2[04]\ d|25[05]|[01]?\ d\ d?) 。
的部分) ,如查找 I' m si ngi ng whi l e you' re danci ng. 时,它会匹配 si ng 和 danc 。
(?<=exp) 也叫 零宽度正回顾后发断言 ,它 断言自身出现的位置的前面能匹配表达式exp 。
假如你想要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要
在前面和里面添加逗号的部分: ((?<=\ d)\ d{3})+\ b ,用它对 1234567890 进行查找时结果是 234567890 。
下面这个例子同时使用了这两种断言: (?<=\ s)\ d+(?=\ s) 匹配 以空白符间隔的数字(再次强调,不包
括这些空白符) 。
\ b\w*q[^u]\w*\ b 匹配 包含后面不是字母u的字母q的单词 。但是如果多做测试(或者你思维足够敏锐,直接就观察出来了),你会发现,如果q出现在单词的结尾的话,像Iraq,Benq,这个表达式就会出错。这是因为 [^u] 总要匹配一个字符,所以如果q是单词的最后一个字符的话,后面的 [^u] 将会匹配q后面的单词分隔符(可能是空格,或者是句号或其它的什么),后面的 \w*\ b 将会匹配下一个单词,于是 \ b\w*q[^u]\w*\ b 就能匹配整个 Iraq f i ght i ng 。 负向零宽断言 能解决这样的问题,因为它只匹配一个位置,并不消费任何字符。现在,我们可以这样来解决这个问题:
零宽度负预测先行断言 (?!exp) , 断言此位置的后面不能匹配表达式exp 。
< ( \ w + ) > # 查找尖括号括起来的字母或数字( 即H T M L / X M L 标签)
) # 前缀结束
. * # 匹配任意文本
( ? = # 断言要匹配的文本的后缀
< \ / \ 1 > # 查找尖括号括起来的内容:前面是一个" / " ,后面是先前捕获的标签
) # 后缀结束
有时,我们更需要 懒惰 匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号 ?。这样 .*? 就意味着 匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复 。
a.*?b 匹配 最短的,以a开始,以b结束的字符串 。如果把它应用于 aabab 的话,它会匹配 aab(第一到第三个字符) 和 ab(第四到第五个字符) 。
注: 你可能发问了:既然是懒惰匹配,为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,且比懒惰 / 贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权。
最左边的左括号和最右边的右括号之间的内容(这里我们讨论的是贪婪模式,懒惰模式也有下面的问题)。假
如原来的字符串里的左括号和右括号出现的次数不相等,比如 ( 5 / ( 3 + 2 ) ) ) ,那我们的匹配结果里两者的个数也不会相等。有没有办法在这样的字符串里匹配到最长的,配对的括号之间的内容呢?
为了避免 ( 和 \ ( 把你的大脑彻底搞糊涂,我们还是用尖括号代替圆括号吧。现在我们的问题变成了如何把
这里需要用到以下的语法构造:
(?'group') 把捕获的内容命名为group,并压入 堆栈(Stack)
(?'-group') 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
(?(group)yes|no) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分
(?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败
我们需要做的是每碰到了左括号,就在压入一个"Open",每碰到一个右括号,就弹出一个,到了最后就看看堆栈是否为空--如果不为空那就证明左括号比右括号多,那匹配就应该失败。正则表达式引擎会进行回溯(放弃最前面或最后面的一些字符),尽量使整个表达式得到匹配。
< # 最外层的左括号
[ ^ < > ] * # 最外层的左括号后面的不是括号的内容
(
(
( ? ' O p e n ' < ) # 碰到了左括号,在黑板上写一个" O p e n "
[ ^ < > ] * # 匹配左括号后面的不是括号的内容
) +
(
( ? ' - O p e n ' > ) # 碰到了右括号,擦掉一个" O p e n "
[ ^ < > ] * # 匹配右括号后面不是括号的内容
) +
) *
( ? ( O p e n ) ( ? ! ) ) # 在遇到最外层的右括号前面,判断黑板上还有没有没擦掉的" O p e n " ;如果 还有,则匹配失败
> # 最外层的右括号
平衡组的一个最常见的应用就是匹配HTML,下面这个例子可以匹配 嵌套的<di v>标签 :
简单且容易阅读,而快速、简单、可读性强的代码可以说出很多好处。但是,
如果你发现你使用了许多不同的字符串函数和 if 语句来处理一个特殊情况,
或者你组合使用了 split、join 等函数而导致用一种奇怪的甚至读不下去的方式
理解列表,此时,你也许需要转到正则表达式了。
<span style="font-size:18px;">>>> S = "100 north main road" >>> S.replace('road','rd') #使用字符串方法 '100 north main rd' >>> S = "100 north broad road" #这里使用字符串方法出现问题,我们的目的是替换road,但是broad 中的子字符串也被替换掉 >>> S.replace('road','rd') '100 north brd rd' >>> S[:-4]+S[-4:].replace('road','rd.') #使用字符串中的索引和分片方法解决,但是代码可读性差 '100 north broad rd.' >>> import re #使用正则表达式,re为Python为支持正则表达式提供的模块 >>> re.sub('road$','rd',S) #这个正则表达式十分简单,road$:只有road出现在一个字符串尾部时才匹配,$表示“字符串的末尾”,利用re.sub对字符串进行搜索,满足正则表达式road$的用rd替换 '100 north broad rd'</span>
<span style="font-size:18px;"></span><span style="font-family: monospace; white-space: pre; "><span style="font-size:18px;">>>> S = '100BROAD'</span></span>
<span style="font-size:18px;"><span style="font-family: monospace; white-space: pre; "></span>>>> re.sub('ROAD$','RD',S) #只有当ROAD出现在字符串末尾时替换 '100BRD' >>> re.sub('\\bROAD$',S)#只有当ROAD出现在字符串尾部并且作为一个独立的单词时替换,\b表示单词的边界,第一个\为转移字符</span>
<span style="font-size:18px;"> >>> re.sub(r'\bROAD$',S) #这是上面的改良版,使用了Python的Raw字符 '100BROAD'</span></span>
<span style="font-size:18px;">>>> S = '100BROAD200' >>> re.sub(r'\bROAD\b',S) #当ROAD不在末尾时,上面的匹配就会失败,所以在这里给ROAD的前后都加上\b(单词边界),这样就能匹配整个字符串中的所有的ROAD了。神奇有木有 '100BROAD200' >>> S = '100 BROAD 200' >>> re.sub(r'\bROAD\b',S) '100 BROAD 200' >>> S = '100 BROAD ROAD 200' >>> re.sub(r'\bROAD\b',S) '100 BROAD RD 200'</span>
一些Python 的RE方法及其应用(https://docs.python.org/2/library/re.html)
<span style="font-size:18px;">>>> import re >>> help (re) This module exports the following functions:</span>
<span style="font-size:18px;"> match Match a regular expression pattern to the beginning of a string. search Search a string for the presence of a pattern. sub Substitute occurrences of a pattern found in a string. subn Same as sub,but also return the number of substitutions made. split Split a string by the occurrences of a pattern. findall Find all occurrences of a pattern in a string. finditer Return an iterator yielding a match object for each match. compile Compile a pattern into a RegexObject. purge Clear the regular expression cache. escape Backslash all non-alphanumerics in a string. Some of the functions in this module takes flags as optional parameters: I IGNORECASE Perform case-insensitive matching. L LOCALE Make \w,\W,\b,\B,dependent on the current locale. M MULTILINE "^" matches the beginning of lines (after a newline) as well as the string. "$" matches the end of lines (before a newline) as well as the end of the string. S DOTALL "." matches any character at all,including the newline. X VERBOSE Ignore whitespace and comments for nicer looking RE's. U UNICODE Make \w,dependent on the Unicode locale. </span>
<span style="font-size:18px;">>>> help (re.match) 这样查看每个方法的帮助文档,可以看到每一种方法的参数 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. (END)</span>
<span style="font-size:18px;">>>> pattern = re.compile(r'hello') # 将正则表达式编译成Pattern对象 >>> match = pattern.match('hello world!') #使用Pattern匹配文本,获得匹配结果,无法匹配时将返回None >>> if match: ... print match.group() #获得一个或多个分组截获的字符串;</span></span><span style="font-size:18px;"> ... hello</span>
re.compile(strPattern[,flag]):
这个方法是Pattern类的工厂方法,用于将字符串形式的正则表达式编译为Pattern对象。 第二个参数flag是匹配模式,取值可以使用按位或运算符'|'表示同时生效,比如re.I | re.M。另外,你也可以在regex字符串中指定模式,比如re.compile('pattern',re.I | re.M)与re.compile('(?im)pattern')是等价的。
可选值有:
名称 | 修饰符 | 说明 |
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 松散正则表达式 |
<span style="font-size:18px;">>>> m = re.match(r'hello','hello world') >>> print m.group() hello </span>
2)、Match
<span style="font-size:18px;">>>> help(re.match) Help on function match in module re: match(pattern,flags=0) Try to apply the pattern at the start of the string,returning a match object,or None if no match was found.</span>
match()函数只检测pattern是不是在string的开始位置匹配, 也就是说match()只有在0位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返回none。
eg:
<span style="font-size:18px;">>>> print re.match(r'hello','hello world').group() hello >>> print re.match(r'hello','Python hello world').group() Traceback (most recent call last): File "<stdin>",line 1,in <module> AttributeError: 'NoneType' object has no attribute 'group'</span>
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))。 - expand(template):
将匹配到的分组代入template中然后返回。template中可以使用\id或\g<id>、\g<name>引用分组,但不能使用编号0。\id与\g<id>是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符'0',只能使用\g<1>0。
<span style="font-size:18px;">>>> m = re.match(r'python','python hello') >>> print m.group() #获取分组截获的字符串 python >>> print m.string #获取匹配时使用的文本 python hello >>> print m.re #匹配时使用的Pattern对象(先将正则表达式的字符串形式编译为Pattern实例, 然后使用Pattern实例处理文本并获得匹配结果) <_sre.SRE_Pattern object at 0xb7458bd0> >>> print m.pos #文本中正则表达式开始搜索的索引</span> 0 >>> print m.endpos #文本中正则表达式结束搜索的索引,与len(string)相同</span> 12 >>> print m.lastindex #最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None</span> None >>> print m.lastgroup #最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将 为None None >>> print m.groups() 以元组形式返回全部分组截获的字符串</span> () >>> print m.groupdict() {} >>> print m.start() 0 >>> print m.end() 6 >>> print m.span() (0,6)</span>
3)、 Pattern
Pattern对象是一个编译好的正则表达式,通过Pattern提供的一系列方法可以对文本进行匹配查找。
Pattern不能直接实例化,必须使用re.compile()进行构造。
- pattern: 编译时用的表达式字符串。
- flags: 编译时用的匹配模式。数字形式。
- groups: 表达式中分组的数量。
- groupindex: 以表达式中有别名的组的别名为键、以该组对应的编号为值的字典,没有别名的组不包含在内。
match VS search:re.match 从字符串的开始处开始匹配,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search 查找整个字符串,直到找到一个匹配;若无匹配返回None。
<span style="font-size:18px;">>>> print re.match(r'hello','python hello world').group() Traceback (most recent call last): File "<stdin>",in <module> AttributeError: 'NoneType' object has no attribute 'group' >>> print re.search(r'hello','python hello world').group() hello </span>
<span style="font-size:18px;">>>> p = re.compile(r'(\w+) (\w+)') #‘(\w+) (\w+)’空格连接的两个单词 >>> 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) I Say,Hello World!</span>
subn
subn(pattern,count=0,flags=0):此函数的参数与 sub 函数完全一致,只不过其返回值是包括两个元素的元组:(new_string,number);第一个返回值new_string 为sub 函数的结果,第二个 number 为匹配及替换的次数。
<span style="font-size:18px;">>>> 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)</span>
4)、findall
<span style="font-size:18px;">>>> p = re.compile(r'\b\w\b') >>> print p.findall('one 1 two 2 three 3 four 4') ['1','2','3','4'] >>> p = re.compile(r'\b\w+\b') >>> print p.findall('one 1 two 2 three 3 four 4') ['one','1','two','three','four','4']</span>
5)、finditer
finditer(pattern,flags=0):找到 RE 匹配的所有子串,并把它们作为一个迭代器返回。这个匹配是从左到右有序地返回。如果无匹配,返回空列表。
<span style="font-size:18px;">>>> p = re.compile(r'\b\w+\b') >>> for m in p.finditer('one1 two2 three3 four4'): ... print m.group() ... one1 two2 three3 four4</span>
6)、purge
purge():清空缓存中的正则表达式。
<span style="font-size:18px;">>>> p = re.compile(r'\b\w+\b') >>> p.search('one1 two2 three3 four4').group() 'one1' >>> p = re.purge() #清空缓存中的正则表达式 >>> p.search('one1 two2 three3 four4').group() Traceback (most recent call last): File "<stdin>",in <module> AttributeError: 'NoneType' object has no attribute 'search' </span>
7)、split
照旧,我们使用help:
split(pattern,maxsplit=0,flags=0):按照能够匹配的子串将string分割后返回列表。maxsplit用于指定最大分割次数,不指定使用默认值0将全部分割。如设定好分割次数后,最后未分割部分将作为列表中的一个元素返回。<span style="font-size:18px;">>>> p = re.compile(r' ') >>> print p.split('one1 two2 three3 four4') ['one1','two2','three3','four4'] </span>
上述内容仅作学习记录!!!!!!