正则表达式(Regular Expression,常简写为 regex 或 regexp)是一种用于匹配、查找、替换文本中特定模式的强大工具,在很多编程语言中都有相应的实现,Python 也不例外,通过内置的 re
模块来支持正则表达式的使用。以下是关于正则表达式的详细介绍:
1. 基本概念
正则表达式由普通字符(例如字母、数字、标点等)和特殊字符(具有特定匹配含义的字符)组成,通过特定的语法规则构建出用于描述文本模式的表达式,进而可以对目标文本进行相关操作。
例如,正则表达式 abc
就是简单地匹配文本中连续出现的 abc
这三个字符;而像 \d
这样的特殊字符组合则代表匹配任意一个数字(0 - 9)。
2. 常用特殊字符及语法
字符类
\d
:匹配任意一个数字,等同于[0-9]
,例如正则表达式\d{3}
可以匹配连续出现的三个数字,像123
、567
等都能被匹配到。\D
:与\d
相反,匹配任意一个非数字字符,例如正则表达式\D+
可以匹配连续出现的多个非数字字符,像abc
、!@#
等符合要求的内容都会被匹配。\w
:匹配包括下划线在内的任何字母数字字符,等同于[a-zA-Z0-9_]
,比如\w{5}
能匹配由字母数字或下划线组成的长度为 5 的字符串,像abcde
、a1b2c
等都满足。\W
:与\w
相反,匹配任何非字母数字下划线的字符,例如\W*
可以匹配连续出现的任意非字母数字下划线字符序列,像!@#
、&*()
等都可被匹配。[ ]
:自定义字符类,用来指定要匹配的一组字符范围,例如[abc]
表示匹配a
、b
或者c
这三个字符中的任意一个;而[a-z]
则表示匹配所有小写字母。
量词
*
:匹配前面的元素零次或多次,例如正则表达式a*
可以匹配空字符串、a
、aa
、aaa
等,也就是包含任意多个a
(也可以没有a
)的字符串都能匹配。+
:匹配前面的元素一次或多次,如a+
会匹配a
、aa
、aaa
等,但不能匹配空字符串,必须至少有一个a
存在。?
:匹配前面的元素零次或一次,比如a?
可以匹配空字符串或者a
,有或者没有a
的情况都能应对。{n}
:匹配前面的元素恰好n
次,例如a{3}
只会匹配aaa
这样刚好连续出现三次a
的字符串。{n,m}
:匹配前面的元素至少n
次且最多m
次,像a{1,3}
可以匹配a
、aa
、aaa
,也就是a
的数量在 1 到 3 个之间的字符串都符合要求。
边界匹配符
^
:匹配字符串的开头,当它位于正则表达式开头时,表示要从目标文本的开头去匹配相应模式,例如^abc
只会匹配以abc
开头的字符串,像abcdef
能匹配,而defabc
就不能匹配。$
:匹配字符串的结尾,例如xyz$
只会匹配以xyz
结尾的字符串,像abcxyz
能匹配,xyzabc
则不行。
分组与引用
( )
:用于分组,将括号内的内容作为一个整体进行处理,比如在匹配电话号码时,可能会写成(\d{3})-(\d{4})-(\d{4})
,这里把区号、电话号码的前四位和后四位分别进行了分组,方便后续提取匹配到的各个部分,也可以在正则表达式内部对分组进行引用等操作(不同编程语言实现方式略有不同)。
其他常用符号
.
:匹配除换行符\n
以外的任意一个字符,例如a.c
可以匹配abc
、aac
、a#c
等任何中间是一个任意字符的字符串。|
:逻辑或运算符,表示匹配它两边的表达式中的任意一个,例如abc|def
可以匹配abc
或者def
。
断言
-
正向断言,正则在前,条件在后
-
反向断言: 条件在前,正则在后
-
正向确定断言:rex(?=\d+) 提取后面是数字的rex
-
正向否定断言:rex(?!\d+) 提取后面不是数字的rex
-
反向确定断言:(?<=window)\d+ 提取前面是window的数字
-
反向否定断言:(?<!window)\d+ 提取前面不是window的数字 (?=.*rex):某个字符串必须包含rex
练习
# 手机号 ^1[3-9]\d{9}$
# 10-12 ^1[0-2]$
# 0-12 ^(\d|1[0-2])$
0-9 \d
10-12 1[0-2]
# 0-255 ^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$
0-9 \d
10-99 [1-9]\d
100-199 1\d{2}
200-249 2[0-4]\d
250-255 25[0-5]
# 匹配 ipv4地址
^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])(\.(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])){3}$
0-255.0-255.0-255.0-255
# 必须有大写字母 小写字母 数字 6-8位
^(?=.*\d)(?=.*[A-Z])(?=.*[a-z])[A-Za-z0-9]{6,8}$
3. Python 中 re
模块的使用
匹配操作
-
re.match()
:从字符串的开头开始匹配,如果开头不符合正则表达式模式,则匹配失败,返回None
,只有当开头匹配成功时,才会返回一个匹配对象。例如:
import re
"""
re.match(pattern, string, flags=0)
pattern:匹配正则
string: 匹配的字符串
flags:匹配模式
匹配以 pattern 开头的 子串 ,返回match对象
函数:
group(): 获取最终匹配子串结果
groups():获取分组结果组成的元组
group(index):获取指定索引分组结果
group(name):获取指定名称分组结果
span(): 获取匹配到的子串的首尾下标
"""
s = '1223ab'
res = re.match(r'(\d{2})(\d{2})', s)
print(res)
print(res.group()) # 12
print(res.span()) # (0, 2)
print(res.start()) # 0
print(res.end()) # 2
print(res.groups())
# 匹配以 pattern 开头的 以pattern结尾 子串 ,返回match对象
s = '1223'
res = re.fullmatch(r'(\d{2})(\d{2})', s)
print(res)
print(res.group()) # 1223
-
re.search()
:在整个字符串中查找是否存在符合正则表达式模式的部分,只要字符串中有一处匹配成功,就返回一个匹配对象,若整个字符串都没有符合模式的部分,则返回None
。例如:
import re
'''
search(pattern, string,flag)
search:匹配符合条件第一个子串和首尾下标,
返回match对象,找不到返回None
index(''):返回第一个下标,找不到报错
find(''):返回第一个下标,找不到-1
'''
s = 'a12b23'
res = re.search(r'\d{2}', s)
print(res)
'''
search(pattern, string,flag)
finditer:匹配符合条件所有子串,返回迭代器
迭代器中每一个元素是match对象
'''
s = 'a12b23'
res = re.finditer(r'\d{2}', s)
for r in res:
print(r.group())
-
re.findall()
:在整个字符串中查找所有符合正则表达式模式的子串,并以列表形式返回这些子串。例如:
import re
'''
findall(pattern,string,flag)
如果没有分组:返回匹配子串组成的列表
如果有一个分组:只返回分组结果组成的列表
如果有多个分组:返回分组元祖组成的列表
总结:
如果有分组,findall只返回分组结果
'''
s = '13067895786'
res = re.findall(r'(\d{3})(\d{4})\d{4}', s)
print(res)
# 提取身份证号的 出生年 月 日
s = '41072419980815451x'
res = re.findall(r'\d{6}(\d{4})(\d{2})(\d{2})', s)
print(res[0])
# 30岁,25个 ,95年=》30,25,95
s = '30岁,25个 ,95年'
res = re.findall(r'\d+', s)
print(res)
'''
假设我们有一些’username@companyname.com'格式的电子邮件地址,
请编写程序打印给定电子邮件地址的用户名,用户名和公司名都只由字母组成
如果下面的电子邮件地址作为程序的输入:john@google.com.
那么程序的输出应该是:john
'''
s = 'john@google.com'
res = re.findall(r'(\w+)@', s)
print(res)
'''
insert into tb_user(name, sex, age)
values ( #{name} , #{sex} , #{age} )
提取 #{name} , #{sex} , #{age}
'''
s = 'insert into tb_user(name, sex, age) values ( #{name} , #{sex} , #{age} )'
res = re.findall(r'#{\w+}', s)
print(res)
# asb13345672234fgfg156345245778dshh, 提取所有手机号
# [('13345672234', '133', '4567'), ('15634524577', '156', '3452')]
s = 'asb13345672234fgfg156345245778dshh'
res = re.findall(r'((\d{3})(\d{4})\d{4})', s)
print(res)
替换操作
使用 re.sub()
函数可以根据正则表达式模式来替换字符串中的相应部分,它接收三个主要参数,第一个是正则表达式模式,第二个是用于替换的字符串,第三个是要被操作的原始字符串。例如:
'''
match(pattern,string,flags):以pattern开头,返回match对象
fullmatch(pattern,string,flags):以pattern开头,以pattern结尾,返回match对象
finditer(pattern,string,flags): 符合pattern的所有结果,返回迭代器,迭代器的元素放的是match对象
search(pattern,string,flags):符合pattern的第一个数据,,返回match对象
findall(pattern,string,flags):
没有分组: 返回所有匹配结果组成的列表
如果有1个分组: 只返回这1个分组结果组成的列表
如果有多个分组: 只返回多个分组结果元组组成的列表
sub(pattern,repl,string,count,flags):
返回替换后的结果
pattern:被替换的正则
repl:
要替换的正则或字符串或lamda,lamda 参数为匹配结果
如果使用反向引用,必须 使用 r'\1***\2'
count:替换个数
subn(pattern,repl,string,count,flags):
返回替换后的结果和替换个数 组成的元组
re.split(pattern,string,maxsplit,flags)
按照pattern拆分,返回拆分后的列表
字符串替换:
replace(old,new,string,count)
old:被替换的子串
new:要替换的字串
count:替换的个数
'''
import re
# 替换所有数字为#
s = 'he12llow wo34rld11'
res = re.sub(r'\d+', '#', s, count=2)
print(res)
# 手机号脱敏 165****3768
s = '16586983768'
res = re.sub(r'(\d{3})\d{4}(\d{4})', r'\1****\2', s)
print(res)
# 邮箱脱敏 h*******@163.com
s = 'henanzhengzhou@163.com'
res = re.sub(r'(.).+(@.+)', r'\1*******\2', s)
print(res)
# 将所有数字 翻转替换
s = 'he123llow wo456rld'
res = re.sub(r'\d+', lambda x: (x.group())[::-1], s)
print(res)
s = 'he12llow wo34rld11'
res = re.subn(r'\d+', '#', s)
print(res) # ('he#llow wo#rld#', 3)
s = 'hello11world22qiku'
res = re.split(r'\d+', s)
print(res) # ['hello', 'world', 'qiku']
# 先创建正则对象
# pattern = re.compile(r'\d+')
# m = pattern.match('12twothree34four')
# print(m)
分割操作
re.split()
函数依据正则表达式模式来分割字符串,它返回分割后的字符串列表。例如:
import re
pattern = r",|\s" # 以逗号或者空白字符作为分割依据
text = "apple, banana cherry"
result = re.split(pattern, text)
print(result)
运行后输出 ['apple', 'banana', 'cherry']
,也就是按照指定的模式将原字符串进行了分割。
正则表达式功能强大,但语法相对复杂,需要通过不断练习和实际应用才能熟练掌握,以便在文本处理、数据验证、信息提取等众多场景中发挥作用。