当前位置: 首页 > 工具软件 > Rephone > 使用案例 >

python正则表达式与re模块

郭通
2023-12-01

0 前言

日常的开发工作中,经常会有处理字符串的需求,简单的字符串处理,我们使用python内置的字符串处理函数就可以了,但是复杂的字符串匹配就需要借助正则表达式了。python是一门及其灵活的语言,在使用正则表达式的时候也是这样,这里集中介绍一下python中使用正则表达式来处理字符串。

1 正则表达式简介

首先简单介绍正则表达式的基础知识。正则表达式是独立于任何语言的一种字符串匹配表达式,任何编程语言都有处理正则表达式的能力。这里介绍的正则表达式的语法或者说是规则,对任何语言是通用的。

正则表达式基本语法:

  • 字母和数字表示他们自身。一个正则表达式模式中的字母和数字匹配同样的字符串。
  • 多数字母和数字前加一个反斜杠时会拥有不同的含义。
  • 标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。
  • 反斜杠本身需要使用反斜杠转义。
  • 由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r’\t’,等价于 ‘\t’)匹配相应的特殊字符。

元字符:

  • “^” :^会匹配行或者字符串的起始位置,有时还会匹配整个文档的起始位置。
  • “$” :$会匹配行或字符串的结尾
  • “\b” :不会消耗任何字符只匹配一个位置,常用于匹配单词边界 如 我想从字符串中"This is Regex"匹配单独的单词 “is” 正则就要写成 “\bis\b”,\b 不会匹配is 两边的字符,但它会识别is 两边是否为单词的边界
  • “\d”: 匹配数字。例如要匹配一个固定格式的电话号码以0开头前4位后7位,如0737-5686123 正则:^0\d\d\d-\d\d\d\d\d\d\d$ 这里只是为了介绍"\d"字符,实际上有更好的写法会在 下面介绍。
  • “\w”:匹配字母,数字,下划线.例如我要匹配"a2345BCD__TTz" 正则:“\w+” 这里的"+"字符为一个量词指重复的次数,稍后会详细介绍。
  • “\s”:匹配空格 。例如字符 “a b c” 正则:“\w\s\w\s\w” 一个字符后跟一个空格,如有字符间有多个空格直接把"\s" 写成 “\s+” 让空格重复
  • “.”:匹配除了换行符以外的任何字符。这个算是"\w"的加强版了"\w"不能匹配 空格 如果把字符串加上空格用"\w"就受限了,看下用 “.“是如何匹配字符"a23 4 5 B C D__TTz” 正则:”.+"
  • “[abc]”: 字符组 匹配包含括号内元素的字符 。这个比较简单了只匹配括号内存在的字符,还可以写成[a-z]匹配a至z的所以字母就等于可以用来控制只能输入英文了,

反义元字符:

  • “\W” 匹配任意不是字母,数字,下划线 的字符
  • “\S” 匹配任意不是空白符的字符
  • “\D” 匹配任意非数字的字符
  • “\B” 匹配不是单词开头或结束的位置
  • “[^abc]” 匹配除了abc以外的任意字符

量词:

  • “*”(贪婪) 重复零次或更多。例如"aaaaaaaa" 匹配字符串中所有的a 正则: “a*” 会出到所有的字符"a"
  • “+”(懒惰) 重复一次或更多次。例如"aaaaaaaa" 匹配字符串中所有的a 正则: “a+” 会取到字符中所有的a字符, “a+“与"a*“不同在于”+“至少是一次而”*” 可以是0次,稍后会与”?"字符结合来体现这种区别
  • “?”(占有) 重复零次或一次。例如"aaaaaaaa" 匹配字符串中的a 正则 : “a?” 只会匹配一次,也就是结果只是单个字符a
  • “{n}” 重复n次。例如从"aaaaaaaa" 匹配字符串的a 并重复3次 正则: “a{3}” 结果就是取到3个a字符 “aaa”;
  • “{n,m}” 重复n到m次。例如正则 “a{3,4}” 将a重复匹配3次或者4次 所以供匹配的字符可以是三个"aaa"也可以是四个"aaaa" 正则都可以匹配到
  • “{n,}” 重复n次或更多次。与{n,m}不同之处就在于匹配的次数将没有上限,但至少要重复n次 如 正则"a{3,}" a至少要重复3次。

懒惰限定符:

  • ?" 重复任意次,但尽可能少重复 。如 “acbacb” 正则 "a.?b” 只会取到第一个"acb" 原本可以全部取到但加了限定符后,只会匹配尽可能少的字符 ,而"acbacb"最少字符的结果就是"acb"
  • “+?” 重复1次或更多次,但尽可能少重复。与上面一样,只是至少要重复1次
  • “??” 重复0次或1次,但尽可能少重复。如 “aaacb” 正则 “a.??b” 只会取到最后的三个字符"acb"
  • “{n,m}?” 重复n到m次,但尽可能少重复。如 “aaaaaaaa” 正则 “a{0,m}” 因为最少是0次所以取到结果为空
  • “{n,}?” 重复n次以上,但尽可能少重复。如 “aaaaaaa” 正则 “a{1,}” 最少是1次所以取到结果为 “a”

分组()(小括号的使用):

  1. 小括号()在正则表达式的应用中代表“捕获分组”。什么是捕获分组呢,在Python中正则匹配得出的结果中,会将正则表达式里()中匹配的结果单独存放在一个数组中,第一个()里的结果放在数组的第一位,第二个()里匹配的结果放在数组的第二位,以此类推。详细参考博客:正则表达式中小括号与正向反向预查
  2. 我发现小括号还有一个额外的副功能。我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。

(\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[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?,这里我就不细说了,你自己应该能分析得出来它的意义。

IP地址中每个数字都不能大于255. 经常有人问我, 01.02.03.04 这样前面带有0的数字, 是不是正确的IP地址呢? 答案是: 是的, IP 地址里的数字可以包含有前导 0 (leading zeroes).

2 re模块使用

Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。
就其本质而言,正则表达式(或 RE)是一种小型的、高度专业化的编程语言, (在Python中)它内嵌在Python中,并通过 re 模块实现。正则表达式模式被 编译成一系列的字节码,然后由用 C 编写的匹配引擎执行。
re 模块使 Python 语言拥有全部的正则表达式功能。 python中有两种方式来使用re模块:

  • compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。
  • re 模块也提供了与这些方法功能完全一致的函数,这些函数使用一个模式字符串做为它们的第一个参数。 
    首先是使用一个例子比较两种方式的效率,然后再分别介绍两种方式:
import re
import timeit

print(timeit.timeit(setup='''import re; reg = re.compile('<(?P<tagname>\w*)>.*</(?P=tagname)>')''', stmt='''reg.match('<h1>xxx</h1>')''', number=1000000))
print(timeit.timeit(setup='''import re''', stmt='''re.match('<(?P<tagname>\w*)>.*</(?P=tagname)>', '<h1>xxx</h1>')''', number=1000000))
0.4837358
1.1406693

可以看到第一种方式,也就是使用compile函数生成正则表达式对象的方式执行的效率更高。
上面的例子使用了python的性能测试模块timeit,关于timeit模块的使用可以参考:python性能分析(一) | timeit模块
下面分别介绍两种匹配方式的使用。

2.1 使用re模块的函数

re.match(pattern, string, flags=0):

尝试从字符串的起始位置匹配一个模式,匹配成功的话返回一个匹配的对象,如果不是起始位置匹配成功的话,match()就返回none。
参数:

  • pattern:匹配的正则表达式
  • string:要匹配的字符串。
  • flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志
import re

line = "Cats are smarter than dogs"

matchObj = re.match(r'(.*) are (.*?) .*', line, re.M | re.I)

if matchObj:
    g = matchObj.group()
    print "matchObj.groups() : ", matchObj.groups()
    print "matchObj.group() : ", matchObj.group()
    print "matchObj.group(0) : ", matchObj.group(0)

    print "matchObj.group(1) : ", matchObj.group(1)
    print "start(0):", matchObj.start(1)
    print "end(0):", matchObj.end(1)
    print "span(0):", matchObj.span(1)

    print "matchObj.group(2) : ", matchObj.group(2)
    # print "matchObj.group(2) : ", matchObj.group(3)
    # print "matchObj.group(2) : ", matchObj.group(4)

    print "groupdict.group() : ", matchObj.groupdict('(.*)')

else:
    print "No match!!"
matchObj.groups() :  ('Cats', 'smarter')
matchObj.group() :  Cats are smarter than dogs
matchObj.group(0) :  Cats are smarter than dogs
matchObj.group(1) :  Cats
start(0): 0
end(0): 4
span(0): (0, 4)
matchObj.group(2) :  smarter
groupdict.group() :  {}

匹配对象的函数:group(),groups(),groupdict(),start(),end(),span()

调用re的匹配方法后,返回匹配对象SRE_MATHCH,匹配对象调用group函数可以返回匹配的内容。
matchobj.group():返回正则表达式整体匹配到的结果,也可以写成matchobj.group(0)
如果正则表示式中含有分组使用,传入分组的下标可以返回特定分组匹配到的字符串,分组小标从1开始。如果模式串多次匹配,group将返回最后一次匹配。
matchobj.group(1): 返回第一个分组匹配到的内容。
matchobj.group(1,2) : 返回第一个,第二个分组匹配到的内容,以列表的形式返回
matchobj.groups(): 返回所有分组(不包含group(0))匹配到的内容,构成一个列表。
matchobj.groupdict(): 如果分组有命名的话,返回分组命名和分组匹配到的内容构成的字符串。
matchobj.start(): 用于获取分组匹配的子串在整个字符串中的起始位置(子串第一个字符的索引),参数默认值为 0
matchobj.end(): 用于获取分组匹配的子串在整个字符串中的结束位置(子串最后一个字符的索引+1),参数默认值为 0
matchobj.span(): 返回((start(), end()))

import re
pattern = re.compile(r'\d+')                    # 用于匹配至少一个数字
m = pattern.match('one12twothree34four')        # 查找头部,没有匹配
print m

m = pattern.match('one12twothree34four', 2, 10) # 从'e'的位置开始匹配,没有匹配
print m

m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配,正好匹配
print m                                         # 返回一个 Match 对象

print m.group(0)   # 可省略 0
print m.groups()
print m.start(0)   # 可省略 0
print m.end(0)     # 可省略 0
print m.span(0)    # 可省略 0
None
None
<_sre.SRE_Match object at 0x00000000033EF780>
12
()
3
5
(3, 5)

re.search(pattern, string, flags=0):

扫描整个字符串并返回第一个成功的匹配。匹配成功re.search方法返回一个匹配的对象,否则返回None。我们可以使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。
参数:

  • pattern:匹配的正则表达式
  • string:要匹配的字符串。
  • flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
import re

line = "Cats are smarter than dogs";

matchObj = re.match(r'dogs', line, re.M | re.I)
if matchObj:
    print "match --> matchObj.group() : ", matchObj.group()
else:
    print "No match!!"

matchObj = re.search(r'dogs', line, re.M | re.I)
if matchObj:
    print "search --> matchObj.group() : ", matchObj.group()
else:
    print "No match!!"
No match!!
search --> matchObj.group() :  dogs

re.match与re.search的区别:

re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。

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

通过正则表达式将字符串分离。如果用括号将正则表达式括起来,那么匹配的字符串也会被列入到list中返回。maxsplit是分离的次数,maxsplit=1分离一次,默认为0,不限制次数。
参数:

  • pattern: 匹配的正则表达式
  • string: 要匹配的字符串。
  • maxsplit: 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。
  • flags: 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志
import re
print(re.split('\W+', 'runoob, runoob, runoob.'))
print(re.split('(\W+)', ' runoob, runoob, runoob.'))
print(re.split('\W+', ' runoob, runoob, runoob.', 1))
print(re.split('a*', 'hello world')) # 对于一个找不到匹配的字符串而言,split 不会对其作出分割
['runoob', 'runoob', 'runoob', '']
['', ' ', 'runoob', ', ', 'runoob', ', ', 'runoob', '.', '']
['', 'runoob, runoob, runoob.']
['hello world']

re.sub(pattern, repl, string, count=0, flags=0):

用于替换字符串中的匹配项。

  • pattern : 正则中的模式字符串。
  • repl : 替换的字符串,也可为一个函数。
  • string : 要被查找替换的原始字符串。
  • count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
import re

phone = "2004-959-559 # 这是一个国外电话号码"

# 删除字符串中的 Python注释
num = re.sub(r'#.*$', "", phone)
print "电话号码是: ", num

# 删除非数字(-)的字符串
num = re.sub(r'\D', "", phone)
print "电话号码是 : ", num
电话号码是:  2004-959-559 
电话号码是 :  2004959559

re.findall(pattern, string, flags=0):

在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。如果正则表达式中有小括号,则只返回,小括号中的内内容。多个小括号返回的元组列表。re.findall()的详细用法,参看博客:python正则表达式re模块之findall函数
注意: match 和 search 是匹配一次,findall 匹配所有。

import re

pattern = re.compile(r'\d+')  # 查找数字
result = re.findall(r'\d+','runoob 123 google 456')
print(result)
['123', '456']

re.finditer(pattern, string, flags=0):

和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。
参数:

  • pattern:匹配的正则表达式
  • string:要匹配的字符串。
  • flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志
import re

it = re.finditer(r"\d+", "12a32bc43jf3")
print(type(it))
for match in it:
    print (match.group())
<type 'callable-iterator'>
12
32
43
3

2.2 使用compile编译的正则表达式对象

上面介绍的所有的匹配的方法都可以有这种形式对应的函数,即先将正则表达式编译成一个正则表达式对象,再使用该对象调用相应的函数,进行匹配,匹配返回的结果和使用re模块调用相应的函数一致。
需要注意的一点是,使用compile编译这种形式对应的匹配函数多了两个可选的参数,即可以指定匹配的开始和结束位置。

re.compile(pattern[, flags]):

用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search(),findall()等匹配函数使用。
参数:

  • pattern : 一个字符串形式的正则表达式
  • flags : 可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数为:
    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 为了增加可读性,忽略空格和 # 后面的注释

match(string[, pos[, endpos]]):

尝试从字符串的起始位置匹配一个模式(如果指定了范围的话,是指定位置的起始位置),匹配成功的话返回一个匹配的对象,如果不是起始位置匹配成功的话,match()就返回none。
参数:

  • string : 待匹配的字符串。
  • pos : 可选参数,指定字符串的起始位置,默认为 0。
  • endpos : 可选参数,指定字符串的结束位置,默认为字符串的长度。
import re
pattern = re.compile(r'\d+')                    # 用于匹配至少一个数字
m = pattern.match('one12twothree34four')        # 查找头部,没有匹配
print m

m = pattern.match('one12twothree34four', 2, 10) # 从'e'的位置开始匹配,没有匹配
print m

m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配,正好匹配
print m                                         # 返回一个 Match 对象

print m.group(0)   # 可省略 0
print m.groups()
print m.start(0)   # 可省略 0
print m.end(0)     # 可省略 0
print m.span(0)    # 可省略 0
None
None
<_sre.SRE_Match object at 0x000000000389D780>
12
()
3
5
(3, 5)

findall(string[, pos[, endpos]]):

在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。
注意: match 和 search 是匹配一次,findall 匹配所有。

参数:

  • string : 待匹配的字符串。
  • pos : 可选参数,指定字符串的起始位置,默认为 0。
  • endpos : 可选参数,指定字符串的结束位置,默认为字符串的长度。
    相对于re.findall()增加了两个可选的参数指定匹配的范围。
import re

pattern = re.compile(r'\d+')  # 查找数字
result1 = pattern.findall('runoob 123 google 456')
result2 = pattern.findall('run88oob123google456', 0, 10)

print(result1)
print(result2)
['123', '456']
['88', '12']

说明: 其他的函数用法与re直接调用的同名函数的用法相同,只是多了两个指定开始位置和结束位置的参数,不做过多的介绍。

参考链接:

  1. Python中的正则表达式
  2. 详解Python中的正则表达式
  3. re库中group(), groups(), groupdict() 用法
  4. Python: str.split()和re.split()的区别
  5. Python:re中的group方法简介
  6. python3进阶之正则表达式之re模块之分组(group)、贪心匹配、编译
  7. python之re模块详解
 类似资料: