一、Python正则表达式re模块简介

正则表达式,是一门相对通用的语言。简单说就是:用一系列的规则语法,去匹配,查找,替换等操作字符串,以达到对应的目的;此套规则,就是所谓的正则表达式。各个语言都有各自正则表达式的内置模块,包括Linux系统中sed、awk也都是使用正则表达式。当然Python中也有对正则表达式的支持,对应的就是Python内置的re模块。

Python的re模块(Regular Expression,正则表达式)提供各种正则表达式的匹配操作,使用这一内嵌于Python的语言工具,尽管不能满足所有复杂的匹配情况,但足够在绝大多数情况下能够有效地实现对复杂字符串的分析并提取出相关信息。Python会将正则表达式转化为字节码,利用C语言的匹配引擎进行深度优先的匹配。


二、正则表达式(Regexp)

正则表达式是由普通字符(例如字符a到z)以及特殊字符(称为”元字符”)组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。

  • 普通字符

普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。

  • 特殊字符

所谓特殊字符,就是一些有特殊含义的字符,如tes*t中的*,简单的说就是表示任何字符串的意思。如果要查找字符串中的*符号,则需要对*进行转义,即在其前加一个\,如tes\*t匹配tes*t。许多元字符要求在试图匹配它们时特别对待,若要匹配这些特殊字符,必须首先使字符”转义”,即,将反斜杠字符\放在它们前面。

  • 限定符

限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。常用有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 等等。

  • 定位符

定位符用来描述字符串或单词的边界,^和$分别指字符串的开始与结束,\b描述单词的前或后边界,\B表示非单词边界。

下面表格列出了常用的正则表达式符号并给出了说明:

符号

说明

例子

.,.*

“.”表示任意字符,除换行符’\n’外。如果说指定了DOTALL的标识,就表示包括新行在内的所有字符。而“.*”表示任意长度任意字符

‘.est’ 可以匹配‘test’但不能匹配‘tooltest’,但‘.*est’可以匹配‘test’和‘tooltest’。

^

匹配以某某字符串开头

‘test’可以匹配‘test’和‘tooltest’,但‘^test$’只能匹配‘test’。

$

匹配以某某字符串结尾

‘test’可以匹配‘test’和‘testtool’,但‘test$’只能匹配‘test’。

*,+,?

‘*’表示后面可跟0个或多个字符,’+’表示后面可跟1个或多个字符,’?’表示后面可跟0个或多个字符

正则表达式’ab*’如果用于查找’abbbc’,将找到’abbb’。’ab?’将找到’ab’;而如果使用非贪婪的数量词’ab*?’,将找到’a’。

*?,+?,??

在上面的结果中只取第一个

<*>会匹配'<H1>title</H1>’整个字符串(贪婪匹配),使用*?可以只找出<H1>(非贪婪匹配)。

{m}

对于前一个字符重复m次

a{3}匹配3个’a’,’aaa’可以匹配,但’aaba’无法匹配。

{m,n}

对于前一个字符重复m到n次

a{2,4}匹配2-4个a;a{2,}匹配2个以上a;a{,4}匹配4个以下a。

{m,n}?

对于前一个字符重复m到n次,并且取尽可能少的情况

在字符串’aaaaaa’中,a{2,4}会匹配4个a,但a{2,4}?只匹配2个a。

\

对特殊字符进行转义,或者是指定特殊序列


[]

表示一个字符集

[abc]会匹配字符a,b或者c,[a-z]匹配所有小写字母,[a-zA-Z0-9]匹配所有字母和数字,[^6]表示除了6以外的任意字符。注意[]一次只能匹配单个字符,一般配合[]*或[]{m,n}使用。

|

表示或者,只匹配其中一个表达式

A|B,如果A匹配了,则不再查找B,反之亦然。一般可以在[]或()中使用。

(pattern)

匹配括号中的任意正则表达式,并捕获其结果放到一个分组中

([\d]*)会匹配任意数字,并把匹配到的结果放到当前分组中,默认此分组number为1。

(pattern)\<number>

使用\<number>可以引用编号为number的分组匹配到的字符串

([\d]*)\1表达式就等于把([\d]*)匹配到的内容再次引用,比如说([\d]*)能匹配121,而([\d]*)\1能匹配121121。

(?P<name>pattern

给分组起一个别名,在引用时比number更好引用

(?P<digit>[\d]*)表达式就是匹配任意数字,并给匹配到的数字一个digit别名。

(?P=name>)

引用别名为name的分组匹配到的字符串

其实跟\<number>有点类似,不过引用从number变成name了。比如(?P<digit>[\d]*)把匹配到的数字设置一个别名,而(?P<digit>[\d]*)(?P=digit)就是根据引用别名再次匹配。

(?#commentpattern)

允许在正则表达式中写入注释,在’(?#’ ‘)’之间的内容将被忽略。

正则表达式’(?<=abc)def’会在’abcdef’中匹配’def’。




包含’ \ ’的特殊序列的意义如下表:

特殊表达式序列

意义

\n

匹配一个换行符,等价于\x0a和\cJ。

\b

匹配一个单词边界,也就是指单词和空格间的位置。例如,’er\b’可以匹配”never”中的’er’,但不能匹配”verb”中的’er’。

\B

匹配非单词边界,’er\B’能匹配”verb”中的’er’,但不能匹配”never”中的’er’。

\d

匹配任意十进制数,相当于[0-9]。

\D

匹配任意非数字字符,相当于[^0-9]。

\s

匹配任意空白字符,相当于[ \t\n\r\f\v]。

\S

匹配任意非空白字符,相当于[^ \t\n\r\f\v]。

\w

匹配任意数字和字母,相当于[a-zA-Z0-9_]。

\W

匹配任意非数字和字母的字符,相当于[^a-zA-Z0-9_]。

\r

匹配一个回车符,等价于\x0d和\cM。

三、Python re使用

Python的re正则表达式模块定义了一系列函数,常量以及异常;同时,正则表达式被编译成‘ RegexObject ’实例,本身可以为不同的操作提供方法。接下来简要介绍一下这些函数的功能和用法。

1. compile

re.compile(pattern[, flags])

把正则表达式的模式和标识转化成正则表达式对象,就是把规则编译好,供match()和search()这两个函数使用。

re所定义的flag包括:

re.I:忽略大小写。

re.L:表示特殊字符集\w, \W, \b, \B, \s, \S依赖于当前环境。

re.M:多行模式。

re.S:即为’ . ’并且包括换行符在内的任意字符(’ . ’不包括换行符)。

re.U:表示特殊字符集\w, \W, \b, \B, \d, \D, \s, \S依赖于Unicode字符属性数据库。

re.X:为了增加可读性,忽略空格和’ # ’后面的注释。

例:以下两种用法结果相同,只是第一种编译过,重复使用效率更好;第二种是没有编译过的。

第一种:

>>> pattern = re.compile(r'^a')
>>> pattern.match('abc')
<_sre.SRE_Match object; span=(0, 1), match='a'>

当把正则表达式规则实例化之后,就会对应生成有很多实例属性与方法,如:match()、findall()、finditer()、split()、sub()、subn()、fullmatch()、search()、scanner()、flags、groupindex、groups、pattern等。

# 实例属性信息;
>>> pattern.flags
32
>>> pattern.pattern
'^a'

第二种

>>> re.match(r'^a', 'abc')        
<_sre.SRE_Match object; span=(0, 1), match='a'>

上面两种方式,任意一种如果匹配成功,则会返回一个对象,以及匹配范围和匹配到的值。接下来就可以把此对象实例化:

>>> pattern = re.compile(r'a')
>>> data = pattern.match('abc abc')

对应也会生成很多实例属性和方法,如:end()、expand()、group()、groupdict()、groups()、span()、start()、endpos、pos、re、regs、string、lastgroup、lastindex等。

# 显示被匹配字符串;
>>> data.string
'abc abc'

# 显示匹配规则实例;
>>> data.re
re.compile('^a')

# 显示匹配结果;
>>> data.group()
'a'

# 匹配结果在原字符串中的索引位置;
>>> data.span()
(0, 1)

# 显示从什么索引位置开始匹配;
>>> data.start()
0

# 显示匹配到什么索引位置结束;
>>> data.end()
1

对于groups()方法,是把匹配结果以组的方式返回,是一个元祖;但有个条件就是正则表达式以组的形式匹配才行,如下:

>>> pattern = re.compile(r'a(\w*) a(\w*)')
>>> data = pattern.match('abc abc')
>>> data.group()
'abc abc'
>>> data.groups()
('bc', 'bc')

对于groupdict()方法,是把匹配结果以dict方式显示;但正则匹配条件必须以组形式匹配,并且赋值一个key才行,如下:

>>> data = re.match(r'(?P<mail>[a-zA-Z0-9]{6,11}@163.com)', '12100231231@163.com')
>>> data.groupdict()
{'mail': '12100231231@163.com'}

更多属性与方法自己尝试

# 可以看到,我们在写规则时都在前面加了一个‘r’字符,是为了用来表明r”内的字符都无须转义,不然当我们包含一个’\n’时可能就会被转义为换行符,就无法做正确匹配操作了。


2. search

re.search(pattern, string[, flags])

在字符串中查找匹配正则表达式模式的位置,返回MatchObject的实例,如果没有找到匹配的位置,则返回None。

第一个参数:匹配规则

第二个参数:表示要匹配的字符串

第三个参数:标致位,用于控制正则表达式的匹配方式,比如上面介绍的大小写,多行匹配等

对于已编译的正则表达式对象来说(re.RegexObject),有方法:search (string[, pos[, endpos]])

若regex是已编译好的正则表达式对象,regex.search(string, 0, 50)等同于regex.search(string[:50], 0)。

具体示例如下:

>>> pattern = re.compile(r"a") 

# 匹配成功;
>>> pattern.search("abcde")
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> pattern.search("bcade")
<_sre.SRE_Match object; span=(2, 3), match='a'>

# 未匹配成功;
>>> pattern.search("abcde", 1)

# \b界定符使用;
>>> re.search(r'de\b', "abcde")
<_sre.SRE_Match object; span=(3, 5), match='de'>
>>> re.search(r'de\b', "abcdeef")


3. match

re.match(pattern, string[, flags])

尝试从字符串的起始位置尝试匹配一个正则表达式,也等于说是从第一个字符开始匹配。

第一个参数:匹配规则

第二个参数:表示要匹配的字符串

第三个参数:标致位,用于控制正则表达式的匹配方式,比如上面介绍的大小写,多行匹配等

>>> re.match('a','ab bc cd')  #能匹配到;
>>> re.match('b','ab bc cd') #不能匹配到;

对于已编译的正则表达式对象来说(re.RegexObject),有方法:match (string[, pos[, endpos]])

match()函数只在字符串的开始位置尝试匹配正则表达式,也就是只报告从位置0开始的匹配情况,而search()函数是扫描整个字符串来查找匹配。如果想要搜索整个字符串来寻找匹配,应当用search()。

案例一:匹配特殊符号

>>> re.match(r"\[[\w]\]", '[a]')
<_sre.SRE_Match object; span=(0, 3), match='[a]'>

案例二:匹配IP地址

>>> ip = "172.16.10.1"
>>> re.match(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', ip)
<_sre.SRE_Match object; span=(0, 11), match='172.16.10.1'>

案例三:匹配邮箱地址

>>> re.match(r'[a-zA-Z0-9]{6,11}@[a-z0-9]*\.[a-z]{1,3}', '12100231231@163.com')
<_sre.SRE_Match object; span=(0, 19), match='12100231231@163.com'>

>>> re.match(r'[a-zA-Z0-9]{6,11}@[a-z0-9]*\.com$', '12100231231@163.com')
<_sre.SRE_Match object; span=(0, 19), match='12100231231@163.com'>

案例三:分组匹配

>>> data = re.match(r'[a-zA-Z0-9]{6,11}@(163|126).com', '12100231231@163.com')
>>> data.groups()
('163',)

>>> data = re.match(r'[a-zA-Z0-9]{6,11}@(163|126).com', '12100231231@126.com')
>>> data.groups()
('126',)

>>> data = re.match(r'[a-zA-Z0-9]{11}@(.*),[\w]*@(.*)', '12100231231@163.com,abc@126.com')
>>> data.group()
'12100231231@163.com,abc@126.com'
>>> data.groups()
('163.com', '126.com')

案例四:分组匹配并起别名

>>> data = re.match(r'(?P<name>\w*) "(?P<mail>[a-zA-Z0-9]{6,11}@[a-z0-9]*\.[a-z]{1,3})" (?P<age>\d{1,3})', 'dkey "12100231231@163.com" 23')
>>> data.groupdict()
{'mail': '12100231231@163.com', 'name': 'dkey', 'age': '232'}


4. split

re.split(pattern, string[, maxsplit=0, flags=0])

此功能很常用,可以将正则表达式匹配的部分进行分割,并返回一个列表。我们在python中,使用str的方法split也可以做字符串分割,但是使用正则会方便很多。

第一个参数:匹配规则

第二个参数:字符串

第三个参数:最大分割次数,默认为0,表示每个匹配项都分割

对于已编译的正则表达式对象来说(re.RegexObject),有方法:split(string[, maxsplit=0])

例如,利用上面章节中介绍的语法:

# 以:或空格分割;
>>> program = "ywnds:C C++ Java Python"
>>> re.split(r':| ', program)
['ywnds', 'C', 'C++', 'Java', 'Python']

# 以:或空格或,分割;
>>> program = "ywnds:C C++ Java Python,Go"
>>> re.split(r':| |,', program)
['ywnds', 'C', 'C++', 'Java', 'Python', 'Go']

对于一个找不到匹配的字符串而言,split不会对其作出分割,如:

>>> re.split(r'a', 'hello world')
['hello world']


5. findall

re.findall(pattern, string[, flags])

在字符串中查找到正则表达式所匹配的所有字符,并组成一个列表返回。跟search方法最大的区别就在于search只会查找到第一个匹配值后就返回,而findall是查找所有。

第一个参数:匹配规则

第二个参数:目标字符串

但三个参数:后面还可以跟一个规则选择项

对于已编译的正则表达式对象来说(re.RegexObject),有方法:findall(string[, pos[, endpos]])

示例如下:

>>> re.findall(r"\d+", 'python=90, java=80, go=70')
['90', '80', '70']

>>> re.search(r"\d+", 'python=90, java=80, go=70')
<_sre.SRE_Match object; span=(7, 9), match='90'>


6. finditer

re.finditer(pattern, string[, flags])

和findall方法类似,在字符串中找到正则表达式所匹配的所有字符,并组成一个迭代器返回。

对于已编译的正则表达式对象来说(re.RegexObject),有方法:finditer(string[, pos[, endpos]])


7. sub

re.sub(pattern, repl, string[, count, flags])

将字符串中匹配到正则表达式的部分用另一个字符串repl进行替换。如果没有找到匹配pattern的字符,则返回未被修改的string。repl既可以是字符串也可以是一个函数。

第一个参数:匹配规则

第二个参数:替换后的字符串

第三个参数:字符串

第四个参数:替换个数,默认为0,表示每个匹配项都替换

对于已编译的正则表达式对象来说(re.RegexObject),有方法:sub(repl, string[, count=0])

此语法的示例有:

>>> p = re.compile(r'(one|two|three)') 
>>> p.sub( 'num', 'one word two words three words')
'num word num words num words'

同样可以用以下方法,并指定count为1(只替换第一个):

>>> p.sub( 'num', ' one word two words three words', count=1)
' num word two words three words'

上面说了,repl也可以是一个函数,下面我们测试一下看看:

def add(v):
var = v.group()
num = int(var) + 1
return str(num)

上面定义了一个函数,默认会接收一个实例化对象,必须返回一个str对象。

>>> incr = "score = 90"
>>> incr = re.sub(r'[\d]+', add, incr)
>>> incr
'score = 91'
>>> incr = re.sub(r'[\d]+', add, incr)
>>> incr
'score = 92'


8. subn

subn(pattern, repl, string[, count, flags])

该函数的功能和sub()相同,但它还返回新的字符串以及替换的次数。

对于已编译的正则表达式对象来说(re.RegexObject),有方法:subn(repl, string[, count=0])


​参考​

​正则表达式(常用案例)​

​​正则表达式30分钟入门教程​​

​Python专题教程:正则表达式re模块详解​