用Python学习正则表达式

2008年2月28日 07:05

正则表达式

正则表达式是一种字符串匹配模式。

一般字符

对于绝大多数的字符来说,它们都只是简单地匹配自己。如
>>> p=re.compile('hi')      # 匹配 hi
>>> m=p.search('very,high') #
>>> m.span()                # 找到了位置
(5, 7)
>>>
在正则表达式中,如下的字符是具有特殊含义的
 . ^ $ * + ? { [ ] \ | ( )
它们的含义,我们会慢慢地说。
\
象其它大部分语言一样,'\'表示转义。为了在你的字符串中匹配如上的特殊字符,你就需要用到它。还有就是一些常见的定义,如'\r','\n','\f','\t','\b'等。如
>>> p=re.compile(r'\.e')     #找字符'.'后面紧跟字符'e'
>>> m=p.search('a.exe')
>>> m.span()
(1, 3)
>>>
.
字符.用于匹配任意的非换行字符,在windows下为'\n',在unix下为'\r\n'。在Python中,如果你在构造表达式时指定参数DOTALL的话,字符也可以匹配换行。
>>> p=re.compile(r'.')          #找任意非\n的字符
>>> p.search('abc').span()
(0, 1)
>>> p.search('\ncba').span()
(1, 2)
>>> p=re.compile
(r'.',re.DOTALL) #找任意字符,包括\n在内
>>> p.search('\ncba').span()
(0, 1)
>>>
|
字符|表示选择,匹配它两边的任意一种。
>>> p=re.compile(r'abc|cba') #匹配 abc 或 cba
>>> p.search('cabcd').span() #找到 abc
(1, 4)
>>> p.search('cbcbad').span() # 找到 cba
(2, 5)

>>>

[]表示的字符类

如果你想匹配几个字符中的任意一个,那么就要用到字符类了。一个字符类是由[和]表示的,表示其中的任意一个。如
>>> p=re.compile(r'[aeiou.^]')  #找aeiou,字符.或字符^中的任意一个
>>> m=p.search('b.exe')         #找到了字符.
>>> m.span()
(1, 2)
>>>
在一个字符类中,只有字符^、-、]和\有特殊含义。字符\仍然表示转义,字符-可以定义字符范围,字符^放在前面,表示非,字符]表示字符类的结束。如
>>> p=re.compile(r'[-a-c]')  #匹配字符-和字符a到c之间的字母,即a,b,c和-。
...                          #第1个字符-就表示字符-,第2个表示范围
>>> m=p.search('d-a.exe')    #找到-
>>> m.span
()
(1, 2)
>>> m=p.search('b-a.exe')    #找到b
>>> m.span()
(0, 1)
>>> p=re.compile(r'[^-a-c]') #找非-,a,b,c的字符
>>> m=p.search('b-a.exe')    #找到字符.

>>> m.span()
(3, 4)
>>> p=re.compile(r'[-a-c^]') #^没有在首位,就只表示字符^
>>> m=p.search('def^-')      #匹配字符^
>>> m.span()
(3, 4)
>>> p=re.compile(r'[[-a-c]') #第2个字符[只是表示字符[

>>> m=p.search('a[')         #找到[
>>> m.span()
(0, 1)
>>> p=re.compile(r'[a-c]]')  #第1个]已经表示类的结束,第2个表示字符]
...                          #表示要匹配字符a,b或c后接字符]
>>> m=
p.search(']a')         #匹配失败
>>> m.span()

Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    m.span()
AttributeError: 'NoneType' object has no attribute 'span'

>>> m=p.search('a]')         #成功
>>> m.span()
(0, 2)
>>> p=re.compile(r'[a-c\]]') #要表示字符],需要用转义符\
>>> m=p.search(']a')         #匹配到字符]
>>> 
m.span()
(0, 1)
>>>
有若干常用的字符类的表达
转义 含义 可能的等价表达
\d 任意数字 [0-9]
\w 任意单词字符,包括下划线_,数字和字母 [a-zA-Z0-9_]
\s 空白字符 [ \t\r\f\n\v]
\D 非数字 [^\d]
\W 非单词字符 [^\w]
\S 非空白 [^\s]
注意的就是,对于Unicode的实现,会不一样。如\w会包含汉字字符在内。

{}表示重复

在{和}之间的数,用于指定重复的次数
{n} 重复n次
{n,} 重复n次或n次以上
{n,m} 重复n到m次
>>> p=re.compile(r'ab{2}c');  #匹配abbc
>>> p=re.compile(r'ab{2,}c');  #匹配abbc,abbbbbbc
>>> p=re.compile(r'ab{2,4}c');  #匹配abbc,abbbc,abbbbc

>>>
除了用{}表示重复外,还有几个字符也是这种含义
* 重复0次或更多次,与{0,}等价
? 重复0次或1次,与{0,1}等价
+ 重复1次或更多次,与{1,}等价
>>> p=re.compile(r'windows\d+');  #匹配windows后跟至少1个数字
>>> p=re.compile(r'13\d{9}');     #匹配13后跟9个数字
>>>

懒惰模式

在正则表达式的匹配中,采用的尽可能多的匹配原则,即贪婪模式(Greeding),如
>>> p=re.compile(r'a.*c');
>>> m=p.search(r'abcbca'); #匹配尽可能多的情况,即abcbc,而不是abc
>>> m.span()
(0, 5)
>>>
如果说想要采用尽可能少的匹配的话,可以使用懒惰模式(Lazy)。在表达重复的符号后,加上字符?即可,如+?,*?,{n,}?等。
* ? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复
>>> p=re.compile(r'a.*?c');  #指定Lazy模式
>>> m=p.search(r'abcbca');   #匹配到abc就结束
>>> m.group()
'abc'
>>>

^和$表示行头和行尾

字符^表示在一行的开始位置,字符$则表示行的结尾位置,它们不代表任何字符。在Python中,需指定参数re.M或re.MULTILINE,才会表示行头和行尾;否则则表示字符串的开关和结尾。
>>> p=re.compile(r'^From:',re.M)     #匹配以From:开始的行
>>> p.search('hello,\nFrom: rui').span()  #找到
(7, 12)
>>> p.search('hello, From: rui').span()    #失败


Traceback (most recent call last):
  File "<pyshell#15>", line 1, in <module>
    p.search('hello, From: rui').span()
AttributeError: 'NoneType' object has no attribute 'span'

>>> p=re.compile(r'^From:')           #没加参数,则表示要求字符串以From:开头
>>> p.search('hello,\nFrom: rui').span() #失败

Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>

    p.search('hello,\nFrom: rui').span()
AttributeError: 'NoneType' object has no attribute 'span'
>>> p.search('From: rui').span()         #成功
(0, 5)
>>>

除了用^和$表示位置外,还有
\b 匹配一个单词边界,也就是指单词和非单词字符间的位置。
\B [^\b]
\A 匹配字符串的开头
\Z 匹配字符串的结尾
如:
>>> p=re.compile(r'ba\b')   # 找以ba为结尾的单词
>>> m=p.search('cba')       # 找到,后面可以是结束
>>> m.span()                # \b本身不占用字符位
(1, 3)
>>> m=
p.search('cba ')      # 可以是空格
>>> m.span()
(1, 3)
>>> m=p.search('cba-')      # 可以是非单词字符
>>> m.span()
(1, 3)
>>> m=p.search('cbac')      # 失败

>>> m.span()

Traceback (most recent call last):
  File "<pyshell#55>", line 1, in <module>
    m.span()
AttributeError: 'NoneType' object has no attribute 'span'

>>>

()表示分组

前面讲了对单个字符的重复,如果你想重复一个子串的话,就可以采用分组的方法
>>> p=re.compile(r'(ab)+')      #匹配一个以上的ab
>>> p.search('acabababc').span()#成功
(2, 8)
>>> p=re.compile(r'(ab|bc)+')      #匹配一个以上的ab或bc
>>> 
p.search('acabbcababc').span()
(2, 10)
>>>
分组除了当作了表达式外,还有个重要的功能就是记录。(和)中匹配的内容会被记录下来,以备以后引用。在Python中,MatchObject的方法 group(),start(),end(),span()可以传入一个整形参数来得到这些记录。记录以0开始计数,并且0表示整个匹配了的表达式,是这些方法的缺省参数。分组从左到右从1开始计数。
>>> p=re.compile(r'(a(b)c)d')
>>> m=p.search('abcd')
>>> m.group()             #就是缺省参数0
'abcd'
>>> m.group(0)
'abcd'

>>> m.group(1)
'abc'
>>> m.group(2)
'b'
>>> m.group(3)            #超出范围

Traceback (most recent call last):
  File "<pyshell#33>", line 1, in <module>

    m.group(3)
IndexError: no such group
>>> m.groups()            #列出所有的分组
('abc', 'b')
>>>
在正则表达式中,还可以对分组做出向后引用,是用'\数字'来表示。即\1表示第1个分组,\2表示第2个,等。
>>> p = re.compile(r'(\b\w+)\s+\1')   # 匹配两个同样的单词连着出现,
...                        # 即找一个单词,并且在若干个空格后,还是同样的单词
...                        # \1 表示的是前面的分组(\b\w+)匹配的同样的内容
>>> 
p.search('Paris in the the spring').groups()
('the',)
>>> p.search('Paris in the the spring').group()  #找到
'the the'
>>>

非记录分组和命名分组

若一个表达式很复杂,有很多的分组,而每个分组都要记录的话就会很困难。有两种相应的方法来处理这个问题,不过属于正则表达式的扩展。

在Perl中,它定义了一种正则表达式的扩展,就是在括号后紧跟一个问题,即'(?...)'。而紧跟在问号'?'后的字符则表达了扩展的含义,如(?:foo)表示非记录型的分组含foo,而(?=foo)则表示另一种含义(表示前面有foo)。

除了实现Perl的这些扩展外,Python还有自己的扩展。若在'?'后紧跟的是P的话,则表示是Python的扩展。如(?P< name>...)定义了一个命名分组,而(?P=name)则表示的对前面的向后引用(与\1等的功能一样)。若Perl中引入了同样功能的符号,Python会同样引入,同时保留P字符功能。

(?P<name>...)和(?P=name)
(?P<name>...)用来定义一个命名分组,它可以通过MatchObject的方法group('name')得到,同时,在表达式中,也可以用(?P=name)来表示对它的引用。
>>> p = re.compile(r'(?P<word>\b\w+\b).*(?P=word)')   #注意命名分组及对它的引用
>>> m = p.search( '(((( Lots of punctuation aLots)))' )
>>> m.group()
'Lots of punctuation aLots'

>>> m.group('word')                                   #在方法中引用
'Lots'
>>> m.span('word')
(5, 9)
>>>
在imaplib模块中,有个读取INTERNALDATA的例子
InternalDate = re.compile(r'INTERNALDATE "'
        r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
	r'(?P<year>[0-9][0-9][0-9][0-9])'
        r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'

        r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])'
        r'"')
显然,用InternalData.group('zonen')比InternalData.group(9)要好得多。
(?:...)
表示非记录分组。即这个分组不会被记录下来。
>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")      #分组不被记录
>>> 
m.groups()
()
(?=...)
称为零宽度正预测先行断言。它断言自身出现的位置的后面能匹配表达式...,但它不包括这些表达式。就像\b,^,$那样用于指定一个位置,这个位置应该满足一定的条件(断言),因此它们也被称为零宽断言。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分)。
>>> p=re.compile(r'Rui (?=Zhang)')  #匹配Rui 后面跟Zhang时的情况,不包括Zhang在内
>>> p.search('hi, Rui Zhang').group() #成功,匹配的内容不含Zhang
'Rui '
>>> p.search
('hi, Rui Zhang').groups() #分组中没有被记录的部分
()
>>> p.search('hi, Rui Zhou').group()   #Rui 后面跟的是Zhou,失败

Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>

    p.search('hi, Rui Zhou').group()
AttributeError: 'NoneType' object has no attribute 'group'
>>>
(?<= ...)
与前面类似,也是用于指定位置,不作实际匹配,叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式...。比如(?<= \bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分)
>>> p=re.compile(r'(?<=Rui )Zhang')   #匹配前面是Rui 的字符Zhang
>>> p.search('hi, Rui Zhang').group() #成功
'Zhang'
>>> p.search('hi, Shan Zhang').group() #失败


Traceback (most recent call last):
  File "<pyshell#14>", line 1, in <module>
    p.search('hi, Shan Zhang').group()
AttributeError: 'NoneType' object has no attribute 'group'

>>>
(?!...)
零宽度负预测先行断言。断言此位置的后面不能匹配表达式...。例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。
>>> p=re.compile(r'Rui (?!Zhang)')    #匹配Rui 后面不是Zhang的情况
>>> p.search('hi, Rui Zhang').group() #失败

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>

    p.search('hi, Rui Zhang').group()
AttributeError: 'NoneType' object has no attribute 'group'
>>> p.search('hi, Rui Zhou').group()  #成功
'Rui '
>>>

(?<!...)
零宽度负回顾后发断言。断言此位置的前面不能匹配表达式...。(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。
>>> p=re.compile(r'(?<!Rui )Zhang')   #匹配前面不是Rui 的字符Zhang
>>> p.search('hi, Rui Zhang').group() #失败

Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>

    p.search('hi, Rui Zhang').group()
AttributeError: 'NoneType' object has no attribute 'group'
>>> p.search('hi, Shan Zhang').group()  #成功
'Zhang'
>>>

(?#...)
表示注释,其中的内容直接被忽略
>>> p=re.compile(r'Rui (?#Zhang)')  #与'Rui '是等价的
>>> p.search('Rui Zhang').groups()
()
>>> p.search('Rui Zhang').group()
'Rui '

>>>
(?iLmsux)
在"i", "L", "m", "s", "u", "x"中的一个或多个,它不匹配任何字串,而是表示对应(re.I, re.L, re.M, re.S, re.U, re.X)的6种选项。
>>> p=re.compile(r'(?m)^From:')       #与使用re.M选项是一样的
>>> p.search('hi,\nFrom: rui').span()
(4, 9)
>>>
(?(id/name)yes-pattern|no-pattern)
这是Python 2.4后加入的特性。表示若分组id或name存在的话,就匹配yes-pattern,否则就匹配no-pattern。|no-pattern是可选的,若没有的话,就表示忽略它。如(<)?(\w+@\w+(?:\.\w+)+)(?(1)>)就是个简单的E-Mail地址匹配表达式,它可以匹配'<user@host.com>'或'user@host.com'但不能匹配'< user@host.com'。其中最后一个分组表达(?(1)>)就表示若分组(1)存在(这里是指(<),即是否有符号<),则要求匹配表达式>,否则就忽略它。
>>> p=re.compile(r'(<)?(\w+@\w+(?:\.\w+)+)(?(1)>)')
>>> m=p.search('<xxx@gmail.com.cn>')
>>> m.group
()
'<xxx@gmail.com.cn>'
>>> m.groups()
('<', 'xxx@gmail.com.cn')
>>> m=p.search('<
xxx@gmail.com')
>>> m.group()
'xxx@gmail.com'
>>> m.groups()
(None, 'xxx@gmail.com
') >>> m=p.search('xx@gmail.com') >>> m.group() 'xx@gmail.com' >>> m.groups() (None, ' xx@gmail.com') >>>

正则表达式的优先权顺序

在构造正则表达式之后,就可以象数学表达式一样来求值,也就是说,可以从左至右并按照一个优先权顺序来求值。

下表从最高优先级到最低优先级列出各种正则表达式操作符的优先权顺序:

操作符 描述
\ 转义符
(), (?:), (?=), [] 圆括号和方括号
*, +, ?, {n}, {n,}, {n,m} 限定符
^, $, \anymetacharacter 位置和顺序
| "或"操作

小结

"."
(点.)代表任何非回车换行字符。当DOTALL选项指定时候,可以匹配包括回车在内的任意字符。
"^"和"$"
表示字串的头和尾。若指定选项MULTILINE,还表示一行的头和尾
"*"
表示重复0或更多个
"+"
表示重复1个以上
"?"
表示重复0或1个
*?, +?, ??
是"*","+"和"?"的懒惰匹配,即尽可能少的匹配
{m}
重复m个
{m,n}
重复n到m个
{m,n}?
{m,n}的懒惰匹配
"\"
转义字符。
[]
字符集。匹配[]中指定的任何一个。可以用"-"来指定一个范围,也可以用[^...]来表示不是其中的字符
"|"
A|B,其中A和B可以是表达式,表示匹配A或B中的任意一个
(...)
分组
(?...)
正则表达式的扩展标识
(?iLmsux)
(取字符"i", "L", "m", "s", "u", "x"中的1个或多个)不匹配任何字串,仅仅用于设置选项。它们分别对应于(re.I, re.L, re.M, re.S, re.U, re.X),一般放在表达式的第一位
(?:...)
非记录分组
(?P<name>...)
命名分组
(?P=name)
取出命名的分组
(?#...)
注释
(?=...)
先行断言。断言此位置的后面能匹配表达式...。
(?!...)
负先行断言。断言此位置的后面不能匹配表达式...。
(?<=...)
后发断言。断言此位置的前面能匹配表达式...。
(?<!...)
负后发断言。断言此位置的前面不能匹配表达式...。
(?(id/name)yes-pattern|no-pattern)
若分组id若name已经匹配,则使用yes-pattern,否则用no-pattern。|no-pattern可选。

一个好的网址

正则表达式30分钟入门教程
Regular Expression HOWTO

--
6G免费网络U盘: http://www.orbitfiles.com/signup/rleon
Python学习笔记 - 正则表达式 (by Rui Zhang)