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

re模块的玩法都在这里~~~

习淇
2023-12-01

想要使用python的正则表达式功能就需要调用re模块,re模块为高级字符串处理提供了正则表达式工具。模块中提供了不少有用的函数,比如:compile函数、match函数、search函数、findall函数、finditer函数、split函数、sub函数、subn函数等。接下来本文将会介绍这些函数的使用情况,然后通过分析编译流程对比两种re模块的调用方式,之后会介绍其他一些应用正则表达式需要知道的理论知识,最后通过一些经典的实例将之前学习的理论应用于实际。让我们开始正则表达式的学习之旅吧!!!

一、正则函数

re.match函数

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

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

例子:

import re

s = '12abc345ab'
m = re.match(r'\d+', s)
print(m.group())  # 12  match只能匹配一次,group()返回匹配的完整字符串
print(m.span())  # (0, 2)  span()返回起始、结束位置的元组,顾头不顾尾

m = re.match(r'\d{3,}', s)
print(m)  # None {3,}规定了匹配最少3个字符,match从起始位置匹配,匹配不到3个字符,返回None

注:例子中涉及部分还未讲解的知识,若例子未看懂,可以将下面内容都看完后再回过头来看。

re.search函数

功能:re.search 扫描整个字符串并返回第一个成功的匹配,如果匹配成功re.search方法返回一个匹配的对象,否则返回None。

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

例子:

import re

s = '12abc345ab'
m = re.search(r'\d{3,}', s)  # search与match相比的优势,匹配了整个字符串,而match只从起始位置匹配
print(m.group())  # 345
print(m.span())  # (5, 8)

m = re.search(r'\d+', s)  # search与match一样,只匹配一次
print(m.group())  # 12

re.sub函数

功能:re.sub用于替换字符串中的匹配项。

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

repl参数可以是替换的字符串,也可以是一个函数。

  • 如果repl是字符串,那么就会去替换字符串匹配的子串,返回替换后的字符串;
  • 如果repl是函数,定义的函数只能有一个参数(匹配的对象),并返回替换后的字符串。

例子:

import re

phone = '2004-959-559  # 这是一个外国电话号码'
# 删除字符串中的Python注释
num = re.sub(r'#.*$', '', phone)  # .*表示匹配除了换行符之外的0个或多个字符,$表示匹配到字符末尾
print(num)  # 2004-959-559

# 删除非数字的字符串
num = re.sub(r'\D', '', phone)  # \D表示匹配非数字字符,相当于[^0-9]
print(num)  # 2004959559

count可指定替换次数,不指定时全部替换。例如:

import re

s = 'abc,123,ef'
s1 = re.sub(r'[a-z]+', '*', s)
print(s1)  # *,123,*
s2 = re.sub(r'[a-z]+', '*', s, count=1)
print(s2)  # *,123,ef

repl可以为一个函数。例如:

import re


# 调用函数对每一个字符进行替换,得到了不同的替换结果
def more(matched):
    print(matched.group())
    return '*' * len(matched.group())


s = 'abc,123,ef'
s1 = re.sub(r'[a-z]+', more, s)
print(s1)
"""
abc
ef
***,123,**
"""

re.subn函数

功能:和sub函数差不多,但是返回结果不同,返回一个元组"(新字符串,替换次数)"

例子:

import re

s = 'abc,123,ef'
s1 = re.subn(r'[a-z]+', '*', s)
print(s1)  # ('*,123,*', 2)
s2 = re.subn(r'[a-z]+', '*', s, count=1)
print(s2)  # ('*,123,ef', 1)

re.compile函数

功能:compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。如果匹配成功则返回一个Match对象。

语法:re.compile(pattern[, flags])

注:compilte函数调用情况比较复杂,下面会有一节专门讲解。

re.findall函数

功能:在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。

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

例子:

import re

s = '123abc345ab'
ms = re.findall(r'\d+', s)  # findall匹配所有字符,而match和search只能匹配一次
print(ms)  # ['123', '345']

ms = re.findall(r'\d{5}', s)  # {5}表示匹配5个字符,没有匹配到,返回空列表
print(ms)  # []

re.finditer函数

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

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

例子:

import re

s = '12abc345ab'
for m in re.finditer(r'\d+', s):
    print(m.group())

re.split函数

功能:split 方法用pattern做分隔符切分字符串,分割后返回列表。如果用’(pattern)’,那么分隔符也会返回。

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

例子:

import re

m = re.split(r'\W+', 'dog,dog,dog.')  # \W表示匹配非字母数字下划线
print(m)  # ['dog', 'dog', 'dog', '']
m = re.split(r'(\W+)', 'dog,dog,dog.')  # 使用'(pattern)',连带分隔符一起返回
print(m)  # ['dog', ',', 'dog', ',', 'dog', '.', '']
m = re.split(r'\W+', 'dog,dog,dog.', maxsplit=1)  # 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数
print(m)  # ['dog', 'dog,dog.']

小结

1 函数辨析:match和search的区别

re.match 只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;

re.search 匹配整个字符串,直到找到一个匹配。

2 函数辨析:3个匹配函数match、search、findall

match 和 search 只匹配一次 ,匹配不到返回 None,findall 查找所有匹配结果。

3 函数返回值

函数re.finditer、re.match 和 re.search 返回匹配对象,而 findall、split 返回列表。

4 re.compile函数是个谜。

二、compile函数

re模块的使用一般有两种方式:

方法1:

直接使用上面介绍的 re.match, re.search 和 re.findall 等函数对文本进行匹配查找。

方法2:

(1)使用compile 函数将正则表达式的字符串形式编译为一个 Pattern 对象;

(2)通过 Pattern 对象提供的一系列方法对文本进行匹配查找,获得匹配结果(一个 Match 对象);

(3)最后使用 Match 对象提供的属性和方法获得信息,根据需要进行其他的操作。

接下来重点介绍一下compile函数。

re.compile函数 用于编译正则表达式,生成一个Pattern对象,调用形式如下:

re.compile(pattern[, flag])

其中,pattern是一个字符串形式的正则表达式,flag是一个可选参数(下一节具体讲解)。

例子:

import re

# 将正则表达式编译成Pattern对象
pattern = re.compile(r'\d+')

利用Pattern对象可以调用前面提到的一系列函数进行文本匹配查找了,但是这些函数的调用与之前有一些小差别。

1. match函数

使用语法:

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

这个之前讲解过了。

(2)Pattern对象:match(string[, pos[, endpos]])

其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。因此,当不指定 pos 和 endpos 时,match 方法默认匹配字符串的头部。当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。

import re

# 将正则表达式编译成Pattern对象
pattern = re.compile(r'\d+')
m = pattern.match('one12twothree34four')  # 这种情况下和re.match一样
print(m)  # None
m = pattern.match('one12twothree34four', 3, 15)
print(m)  # 返回一个Match对象 <re.Match object; span=(3, 5), match='12'>

当匹配成功时会返回一个Match对象,其中:

  • group([0, 1, 2,…]): 可返回一个或多个分组匹配的字符串,若要返回匹配的全部字符串,可以使用group()或group(0)。
  • start(): 匹配的开始位置。
  • end(): 匹配的结束位置。
  • span(): 包含起始、结束位置的元组。等价于(start(), end())。
  • groups(): 返回分组信息。等价于(m.group(1), m.group(2))。
  • groupdict(): 返回命名分组信息。
import re

pattern = re.compile(r"([a-z]+) ([a-z]+)", re.I)  # re.I表示忽略大小写,()表示一个分组
m = pattern.match('hello nice world')
print(m.groups())  # ('hello', 'nice')
print(m.group(0))  # hello nice
print(m.span(0))  # (0, 10)
print(m.start(0))  # 0
print(m.end(0))  # 10
print('----')
print(m.group(1))  # hello
print(m.span(1))  # (0, 5)
print(m.start(1))  # 0
print(m.end(1))  # 5
print('----')
print(m.group(2))  # nice
print(m.span(2))  # (6, 10)
print(m.start(2))  # 6
print(m.end(2))  # 10

2. search函数

使用语法:

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

这个函数前面已经讲解过了。

(2)Pattern对象:search(string[, pos[, endpos]])

例子:

import re

pattern = re.compile(r'\d+')
m = pattern.search('one12twothree34four')
print(m.group())  # 12
print(m.groups())  # ()
m = pattern.search('one12twothree34four', 10, 30)
print(m.group())  # 34
print(m.groups())  # ()

3. findall函数

使用语法:

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

这个函数前面已经讲解过了。

(2)Pattern对象:findall(string[, pos[, endpos]])

findall 以列表形式返回全部能匹配的子串,如果没有匹配,则返回一个空列表。

例子:

import re

pattern = re.compile(r'\d+')
s1 = pattern.findall('hello world 123 456 789')
s2 = pattern.findall('one12twothree34four56', 0, 15)
print(s1)  # ['123', '456', '789']
print(s2)  # ['12', '34']

4. finditer函数

使用语法:

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

这个函数前面已经讲解过了。

(2)Pattern对象:finditer(string[, pos[, endpos]])

finditer 函数与 findall 类似,但是它返回每一个匹配结果(Match 对象)的迭代器。

例子:

import re

pattern = re.compile(r'\d+')
s1 = pattern.finditer('hello world 123 456 789')
s2 = pattern.finditer('one12twothree34four56', 0, 15)
for m in s1:
    print(m.group())
for m in s2:
    print(m.group())

5. split函数

使用语法:

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

这个函数前面已经讲解过了。

(2)Pattern对象:split(string[, maxsplit]])

maxsplit 可指定分割次数,不指定将对字符串全部分割。

例子:

import re

s = 'a,1;b 2, c'
m = re.compile(r'[\s\,\;]+')
print(m.split(s))  # ['a', '1', 'b', '2', 'c']
m = re.compile(r'[\s\,\;]+')
print(m.split(s, maxsplit=1))  # ['a', '1;b 2, c']

6. sub函数

使用语法:

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

这个函数前面已经讲解过了。

(2)Pattern对象:sub(repl, string[, count])

当repl为字符串时,可以用\id的形式引用分组,但不能使用编号0;当repl为函数时,返回的字符串中不能再引用分组。

例子:

import re

pattern = re.compile(r'(\w+) (\w+)')
s = 'ni 123, hao 456'


def func(m):
    return 'hi' + ' ' + m.group(2)


print(pattern.findall(s))  # [('ni', '123'), ('hao', '456')]
print(pattern.sub('hello world', s))  # hello world, hello world
print(pattern.sub(r'\2 \1', s))  # 123 ni, 456 hao
print(pattern.sub(func, s))  # hi 123, hi 456
print(pattern.sub(func, s, 1))  # hi 123, hao 456

7. subn函数

subn和sub类似,也用于替换操作。使用语法如下:
Pattern对象:subn(repl, string[, count])

返回一个元组,元组第一个元素和sub函数的结果相同,元组第二个元素返回替换次数。

例子:

import re

pattern = re.compile(r'(\w+) (\w+)')
s = 'ni 123, hao 456'


def func(m):
    return 'hi' + ' ' + m.group(2)


print(pattern.findall(s))  # [('ni', '123'), ('hao', '456')]
print(pattern.subn('hello world', s))  # ('hello world, hello world', 2)
print(pattern.subn(r'\2 \1', s))  # ('123 ni, 456 hao', 2)
print(pattern.subn(func, s))  # ('hi 123, hi 456', 2)
print(pattern.subn(func, s, 1))  # ('hi 123, hao 456', 1)

小结

1 使用Pattern对象的match、search、findall、finditer等函数可以指定匹配字符串的起始位置。

2 对re模块的两种使用方式进行对比:

  • 使用 re.compile 函数生成一个 Pattern 对象,然后使用 Pattern 对象的一系列方法对文本进行匹配查找;
  • 直接使用 re.match, re.search 和 re.findall 等函数直接对文本匹配查找。

例子:

import re

# 方法1
# 将正则表达式先编译成 Pattern 对象
pattern = re.compile(r'\d+')
print(pattern.match('123, 123'))
print(pattern.search('234, 234'))
print(pattern.findall('345, 345'))
# 方法2
print(re.match(r'\d+', '123, 123'))
print(re.search(r'\d+', '234, 234'))
print(re.findall(r'\d+', '345, 345'))

在上述例子中,我们发现他们共用了同一个正则表达式,表明上看好像没发现什么问题,但是当我们结合正则表达式的匹配过程进行分析时,就会发现这两种调用方式的效率是不一样的。

使用正则表达式进行匹配的流程是先对正则表达式进行编译,然后得到一个对象,再使用该对象对需要匹配的文本进行匹配。这时我们就发现方式2会对正则表达式进行了多次编译,这样效率不就降低了。

所以我们可以得到如下结论

如果一个正则表达式要用多次,那么出于效率考虑,我们可以预先编译正则表达式,然后调用的一系列函数时复用。如果直接使用re.match、re.search等函数,则需要每一次都对正则表达式进行编译,效率就会降低。因此在这种情况下推荐使用第一种方式。

三、元字符和通用字符

首先,认识常用的元字符:

  • . 匹配除 “\n” 和 “\r” 之外的任何单个字符。
  • ^ 匹配字符串开始位置
  • $ 匹配字符串中结束的位置
  • * 前面的原子重复 0 次、1 次、多次,相当于{0,}
  • ? 前面的原子重复 0 次或者 1 次,相当于{0,1}
  • + 前面的原子重复 1 次或多次,相当于{1,}
  • {n} 前面的原子出现了 n 次
  • {n,} 前面的原子至少出现 n 次
  • {n,m} 前面的原子出现次数介于 n-m 之间
  • ( ) 分组,输出需要的部分

再认识常用的通用字符:

  • \s 匹配空白字符
  • \w 匹配任意字母/数字/下划线
  • \W 和小写 w 相反,匹配任意字母/数字/下划线以外的字符
  • \d 匹配十进制数字
  • \D 匹配除了十进制数以外的值
  • [0-9] 匹配一个 0~9 之间的数字
  • [a-z] 匹配小写英文字母
  • [A-Z] 匹配大写英文字母

四、贪婪与非贪婪匹配

正则表达式匹配时默认的是贪婪匹配,也就是会尽可能多的匹配更多字符。如果想使用非贪婪匹配,可以在正则表达式中加上 ?

语法说明
??重复0或1次,但尽可能少重复
+?重复1次或更多次,但尽可能少重复
*?重复0次或更多次,但尽可能少重复
{n,m}?重复n到m次,但尽可能少重复
{n,}?重复n或更多次,但尽可能少重复
{n}?重复n次,但尽可能少重复

例子:

import re

content = 'a<exp>hello world</exp>b<exp>ni hao</exp>c'
pattern1 = re.compile(r'<exp>.*</exp>')
s1 = pattern1.findall(content)  # 贪婪匹配 会在匹配到第一个</exp>时继续向右匹配,查找更长的匹配子串
print(s1)  # ['<exp>hello world</exp>b<exp>ni hao</exp>']
pattern2 = re.compile(r'<exp>.*?</exp>')  # 非贪婪匹配
s2 = pattern2.findall(content)
print(s2)  # ['<exp>hello world</exp>', '<exp>ni hao</exp>']

五、分组

如果你想要提取子串或是想要重复提取多个字符,那么你可以选择用定义分组的形式。用()就可以表示要提取的分组(group),接下来用几个实例来理解一下分组的使用方式:

例子1:

import re

m = re.match(r'(\d{4})-(\d{3,8})$', '0528-86889099')
print(m.group())  # 0528-86889099
print(m.group(1))  # 0528
print(m.group(2))  # 86889099
print(m.groups())  # ('0528', '86889099')

正则表达式 (\d{4})-(\d{3, 8})$ 表示匹配两个分组,第一个分组 (\d{4}) 是一个有4个数字的子串,第二个分组 (\d{3,8}) 表示匹配一个数字子串,子串长度为3到8之间。

例子2:

import re

s = 'ab123.456.78.90c'
m = re.search(r'(\d{1,3}\.){3}\d{1,3}', s)
print(m.group())  # 123.456.78.90
print(m.group(1))  # 78.
print(m.groups())  # ('78.',)

正则表达式 (\d{1,3}\.){3}\d{1,3} 的匹配过程分为两个部分,(\d{1,3}\.){3} 表示匹配一个长度为1到3之间的数字子串加上一个英文句号的字符串,重复匹配 3 次该字符串,\d{1,3} 表示匹配一个1到3位的数字子串,所以最后得到的结果是 123.456.78.90

例子3:

import re

line = 'Cats are smarter than dogs'
match_obj = re.match(r'(.*) are (.*?) .*', line, re.M | re.I)
if match_obj:
    print(match_obj.group())  # Cats are smarter than dogs
    print(match_obj.group(1))  # Cats
    print(match_obj.group(2))  # smarter
else:
    print('no match')

(.*) 第一个分组,.* 代表匹配除换行符之外的所有字符。(.*?) 第二个匹配分组,.*? 后面加了个问号,代表非贪婪模式,只匹配符合条件的最少字符。后面的一个 .* 没有括号包围,所以不是分组,匹配效果和第一个一样,但是不计入匹配结果中。

group() 等同于group(0),表示匹配到的完整文本字符;

group(1) 得到第一组匹配结果,也就是 (.*) 匹配到的;

group(2) 得到第二组匹配结果,也就是 (.*?) 匹配到的;

因为只有匹配结果中只有两组,所以如果填 3 时会报错。

六、正则表达式修饰符

正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过 | 来指定。

修饰符含义
re.I忽略大小写。
re.L做本地化识别(locale-aware)匹配。对中文支持不好。
re.M多行匹配,影响 ^ 和 $
re.S单行。’.’ 匹配包括换行在内的所有字符
re.U根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.
re.X忽略多余空白字符,让表达式更易阅读。
import re

s = '123abCD45ABcd'
print(re.findall(r'[a-z]+', s))  # ['ab', 'cd']
print(re.findall(r'[a-z]+', s, re.I))  # ['abCD', 'ABcd']

七、扩展

[\u4e00-\u9fa5] 中文表达式的范围
>>> import re
>>> text = 'hello,马露露,五黑吗,go!'
>>> re.findall(r'[\u4e00-\u9fa5]+', text)
['马露露', '五黑吗']
 类似资料: