本文笔记大部分系网上听课所得,并对顺序做了调整。感觉这个课程挺好的,讲的很详细,课程地址:https://www.bilibili.com/video/BV1SJ411n7kk?p=1
另外,文中部分示例代码修改自:https://www.cnblogs.com/misswangxing/p/10736310.html
一、正则表示式的组成
在Python(读作拍萨恩或者拍森)中,正则表达式由原子,元字符和模式修正符(在其他语言中可能存在定界符)组成。
1.1 原子
原子是组成正则表达式的最小单位。一个正则表达式至少由一个原子组成。
- 所有可见字符都是原子,例如:a,b,c,d,A,B,C,D,你,我,他,+,-,*,/
- 所有不可见字符都是元组,例如:\t \n \r ...
- 正则专用转义字符:
为在正则的元字符和原子当中,使用了部分的符号作为语法,为了匹配这些符号的单纯的字符格式在正则表达式中,添加\即可去掉其特殊意义,获取字符,例如,匹配“.”正则表达式要写成“\.”,匹配“*”则写成“\*”
-
表1.1 正则专用转义字符 \d 表示0~9之间任意【一个】字符 \D 表示除了0~9之外的任意【一个】字符 ->[^0123456789]自定义排除列表(见表1.2) \s 表示任意【一个】空白字符(不可见字符)->[\n\r\t\v\f]自定义原子列表格式(见表1.2) \S 表示任意【一个】非空白字符 ->[^\n\r\t\v\f]自定义排除列表 \w 表示0~9a~zA~Z和_中的任意【一个】字符 ->[a-zA-Z0-9_]自定义原子列表格式 \W 表示除了0~9a~zA~Z和_意外的任意【一个】字符 . 表示除了\n的任意【一个】字符(使用模式修正符re.S可以令其也匹配\n) \b 表示能够当做英文字符分割的符号,(除了字母和数字都是词边界) \B 表示不能够当做英文字符分割的符号(字母和数字) patter = r'\byou'#表示you前面的符号可以当做英文字符分割符 url = 'if you lose yourself' res = re.search(pattern,url)#参见表2.1 print(res)#输入:you 表示you前面的空格可以作为英文字符分隔符 url = 'if you lose mmmyourself' res = re.search(pattern,url) print(res)#输入为:None 表示you前面的m不可以作为英文字符分隔符
1.2 元字符(原子修饰符)
[] | 自定义原子列表,表示自定义原子列表中的任意一个字符,如果多个字符在ASCII码中是连续的,则可以进行缩写,例如:[0123456789]=[0-9] |
[^] | 自定义排除列表,表示原子列表之外的任意一个字符,如果多个字符在ASCII码中是连续的,则可以进行缩写,例如:[^0-9]表示0-9以外的字符。 |
+ | 表示1~多个修饰的原子 例如:“a+”匹配1个以上的a |
? | 表示0~1个修饰的原子 |
* | 表示0~多个修饰的原子 |
{m} | 表示m个修饰的原子 |
{m,n} | 表示[m,n]个修饰的原子 |
{m,} | 表示m~多个修饰的原子 |
{,n} | 表示[0,n]个修饰的原子 |
注:表中[m,n]为数学上的闭区间表示,即包括m和n
\A | 表示限定内容必须在整个字符串的开头部位 |
\Z | 表示限定内容必须在整个字符串的结束部位 |
^ | 表示限定内容必须在整个字符串的开头部位,支持多行匹配模式(见注) |
$ | 表示限定内容必须在整个字符串的结束部位,支持多行匹配模式 |
| | 表示选择关系,其左右的内容二选一,例如:ab|cd表示ab或者cd |
() |
|
注:匹配模式(见表1.5),如果字符串中包含\n字符,则可以使用多行匹配模式,例如,aaa\nbbb视做一行。对^$有效对\A与\Z无效。
1.3 模式修正符
修正符 | 或者写成 | 对应值 | 注释 |
---|---|---|---|
re.A | re.ASCII | 256 | 在ASCII模式下进行匹配(只能匹配ASCII码表中的字符) |
re.U | re.UNICODE | 32 | 在unicode模式下进行匹配操作(默认模式) |
re.S | re.DOTALL | 16 | 使得"."可以匹配任意字符(包括\n) |
re.M | re.MULTILINE | 8 | 表示使用多行匹配模式,和^$相关 |
re.X | re.VERBOSE | 64 | 表示匹配的时候忽略正则表达式中的空格或者注释 |
re.I | re.IGNORECASE | 2 | 表示匹配过程忽略大小写 |
注1:还有一个re.L表示字符集本地化,对应整型值4,但是对于中文环境基本上没有什么作用,不能匹配中文字符,所有这个修正符并不常用。
注2:在使用中可以添加多个模式,使用‘|’隔开。例如:re.compile('', re.I|re.M|re.S),具体可使用模式修正符的方法,参见第二节。
(?Limsux) | 模式修正符(见表1.5)的应用(可同时使用多个),例如(?imx)xxxx表示匹配过程中忽略大小写、使用多行模式并且忽略正则表达式中的空格或者注释 |
(?:) | 分组的不捕获模式,计算索引时会跳过这个分组,即取消单元存储功能,节省资源 (参见1.4 .2) |
(?P<name>) | 自定义模式单元的名称,表示字典模式单元匹配(见表后示例,亦有索引模式单元)(参见1.4 .1) |
(?P=name) | 获取自定义单元的内容,表示字典模式单元匹配,使用时,正则表达式中要有(?P<name>),并且这一部分不作为一组(参见1.4 .1分组用法示例代码) |
(?#) | 正则注释内容,解析的时候不会做任何处理 |
(?=) | 零宽断言,正向先行断言(见注1)(本表后示例) |
(?!) | 零宽断言,负向先行断言 |
(?<=) | 零宽断言,正向后行断言(注2) |
(?<!) | 零宽断言,负向后行断言 |
?(?(id/name)Y|N) | 类似于变成语言中的三目运算符:a>b?fun1():fun2()。根据模式单元是否存在决定使用Y的的规则(模式单元存在)或者使用N的规则(模式单元不存在),与(?P<name>)结合使用(本表后的示例) |
- 注1 正向->有 先行->向前或者说向右进行查找
负向->没有 后行->向后或者说向左进行查找 - 注2:对于零宽断言,可以两边同时使用,例如:'(?<!www\.)lmonkey\d(?!\.com)'#意思是匹配其前没有www.的并且其后没有.com的lmonkey\d
- 注3:后行括号中需要匹配的字符串长度必须是固定长度,例如(?<=a{3})(?<=aaa)(?<!a\w\d)都可以,诸如(?<=a+)的都是非法的
'''
零宽断言示例
'''
pattern1 = r'lmonkey\d(?=\.\w?)'#意思就是匹配其后有.加若干字母的lmonkey
pattern2 = r'lmonkey\d(?!\.\wom)'#意思就是匹配其后没有.\wom的lmonkey
pattern3 = r'(?<=www\.)lmonkey\d'#意思就是匹配其前面有.的lmonkey
pattern4 = r'(?<!www\.)lmonkey\d'#意思就是匹配其前面没有.com的lmonkey
pattern5 = r'(?<!www\.)lmonkey\d(?!\.com+)'
st = 'www.lmonkey1.com-www.lmonkey2.cn-aaa.lmonkey3.net-bbb.lmonkey4.dommm-bbb.lmonkey5.edu'
res1 = re.findall(pattern1, st)#方法参见表2.1
res2 = re.findall(pattern2, st)
res3 = re.findall(pattern3, st)
res4 = re.findall(pattern4, st)
res5 = re.findall(pattern5, st)
print('res1>>',res1)
print('res2>>',res2)
print('res3>>',res3)
print('res4>>',res4)
print('res5>>',res5)
'''
输出结果:
res1>> ['lmonkey1', 'lmonkey2', 'lmonkey3', 'lmonkey4', 'lmonkey5']
res2>> ['lmonkey2', 'lmonkey3', 'lmonkey5']
res3>> ['lmonkey1', 'lmonkey2']
res4>> ['lmonkey3', 'lmonkey4', 'lmonkey5']
res5>> ['lmonkey3', 'lmonkey4', 'lmonkey5']
'''
'''
(?(id/name)Y|N)示例
'''
pattern1 = r'(?P<name>www)?(?(name).*|\w+\.)'
st1 = 'www.baidu.com'
st2 = 'aaa.baidu.com'
reg = re.compile(pattern1)
res1 = reg.search(st1)
res2 = reg.search(st2)
print(res1)
print(res2)
'''
输出结果:
<re.Match object; span=(0, 13), match='www.baidu.com'>
<re.Match object; span=(0, 4), match='aaa.'>
'''
pattern2 = r'(www)?(?(1).*|\w+\.)'
st1 = 'www.baidu.com'
st2 = 'aaa.baidu.com'
reg = re.compile(pattern2)
res1 = reg.search(st1)
res2 = reg.search(st2)
print(res1)
print(res2)
'''
输出结果:
<re.Match object; span=(0, 13), match='www.baidu.com'>
<re.Match object; span=(0, 4), match='aaa.'>
'''
1.4 分组(模式单元)用法
本节部分参考了《python 正则表达式详解》中的五 分组用法
Python中的正则表达式使用“(”表示分组,按照每个分组中前半部分出现的顺序 "(" 判定分组的索引,索引从 1 开始,
1.4.1 每个分组在访问的时候可以使用索引,也可以使用别名。
'''
如果你在写正则的时候需要在正则里面重复书写某个表达式,那么你可以使用正则的引用分组功能,需要注意的是引用的【不是】前面分组的【正则表达式】 而是捕获到的【内容】,并且引用的分组不算在分组总数中.
'''
st = '<title>百度</title><body>www.baidu.com</body>'
'''
重复引用
#索引模式单元的应用 \1代表了([a-z]+)匹配到的内容,并不算一组
'''
pattern = r'<([a-z]+)>.*</\1>'
reg = re.compile(pattern)
res = reg.search(st)#方法参见表2.2
print(reg.groups)#参见表2.3
print(res.groups())#参见表2.4
print(res.group(1))
'''
输出结果:
1
('title',)
title
'''
'''
重复引用
字典模式单元的应用,(?P=biaoti)代表了(?P<biaoti>[a-z]+)所匹配的内容,并不算一组
'''
pattern = r'<(?P<biaoti>[a-z]+)>.*</(?P=biaoti)>'
reg = re.compile(pattern)
res = reg.search(st)
print(reg.groups)
print(res.groupdict())#参见表2.4
print(res.group('biaoti'))
print(res.group(1))
'''
输出结果:
1
{'biaoti': 'title'}
title
title
'''
1.4.2 有时候可能只是为了把正则表达式分组,而不需要捕获其中的内容,这时候可以使用非捕获分组
r'''
(?:)取消单元存储功能
'''
pattern = r'(?:bai)(du)'
reg = re.compile(pattern)#参见表2.2
st = 'baidudududu'
res = reg.search(st)
print(reg.groups)#参见表2.3
print(res.groups())
'''
输出结果:
1
('du',)
也就是说,对于(?:bai)只做匹配,不当做一组来记录,也就对应了res.groups()返回的元组中只有'du'
'''
s = 'Hello, Mr.Gumby : 2016/10/26'
p = re.compile(r'(?:(?P<name>\w+\.\w+)|(\d+/)+)')
m = p.search(s)
print(p.groups)
print(m.groups())
'''
输出结果:
2
('Mr.Gumby', None)
'''
二 re模块中正则表达式方法
2.1 常用的re.xxx
方法名 | 功能 | 格式形参 | 返回值或注释 |
---|---|---|---|
compile() | 1.编译正则表达式,获取正则表达式对象。 2.可以循环利用,提高程序效率 | re.compile(正则字符串,模式修正符) | 正则表达式对象 |
purge() | 清除正则表达式缓存 | re.purge() | 当你在程序中使用 re 模块,无论是先使用 compile 还是直接使用比如 findall 来使用正则表达式操作文本,re 模块都会将正则表达式先编译一下, 并且会将编译过后的正则表达式放到缓存中,这样下次使用同样的正则表达式的时候就不需要再次编译, 因为编译其实是很费时的,这样可以提升效率,而默认缓存的正则表达式的个数是 100, 当你需要频繁使用少量正则表达式的时候,缓存可以提升效率,而使用的正则表达式过多时,缓存带来的优势就不明显了 |
escape() | 对字符串中的非数字和非字母部分进行转义 | re.escape(字符串) | 字符串 |
findall() | 对字符串进行正则匹配,获取所有匹配的内容 | re.findall(正则表达式,匹配字符串,模式修正符) | 匹配到的字符串元组 |
finditer() | 对字符串进行正则匹配,获取所有匹配到的内容的迭代器对象 | re.finditer(正则表达式,匹配字符串,模式修正符) | 包含所有匹配结果对象的迭代器 |
match() | 在开头位置对字符串进行正则匹配,相当于这个方法往正则表达式上加了" ^ " | re.match(正则表达式,匹配字符串,模式修正符) | 匹配结果对象 |
search() | 在任意位置对字符串进行正则匹配 | re.search(正则表达式,匹配字符串,模式修正符) | 匹配结果对象 |
split() | 使用正则表达式切割字符串 | re.split(正则表达式,要切割的字符串[,切割次数])例如:切割3次生成4个字符串 | 切割后字符串的列表 |
sub() | 正则替换 | re.sub(正则表达式,被替换内容的字符串,替换的字符串[,最大替换次数]) | 替换后的字符串 |
subn() | 正则替换 | re.subn(正则表达式,被替换内容的字符串,替换的字符串[,最大替换次数]) | 元组:(替换后的字符串,替换进行的次数) |
2.2 常用的正则表达式对象方法和属性(借助compile()方法获取的对象)
注:regex = re.compile(...)
方法名 | 功能 | 格式形参 | 返回值或注释 |
---|---|---|---|
findall() | 使用正则表达式对象对制定的字符串进行匹配,获取所有匹配内容 | regex.findall(匹配字符串) | 匹配到的字符串元组 |
finditer() | 对字符串进行正则匹配,获取所有匹配到的内容的生成器对象 | regex.finditer(匹配字符串) | 包含所有匹配结果对象的迭代器 |
match() | 在开头位置对字符串进行正则匹配 | regex.match(要匹配的字符串) | 第一次匹配到的结果对象 |
search() | 在任意位置对字符串进行正则匹配 | regex.search(要匹配的字符串) | 第一次匹配到的结果对象 |
sub() | 正则替换 | regex.sub(被替换内容的字符串,替换的字符串[,最大替换次数]) | 替换后的字符串 |
subn() | 正则替换 | regex.subn(被替换内容的字符串,替换的字符串[,最大替换次数]) | 替换后的字符串 |
split() | 正则表达式分割 | regex.split(要切割的字符串) | 切割后字符串的列表 |
fullmatch() | 如果整个字符串匹配此正则表达式,则返回相应的匹配对象。 | regex.fullmatch(string[,pos[,endpos]]) | 匹配结果对象 |
注:sub和subn的repl参数
s = "the sum of 7 and 9 is [7+9]."
# 基本用法 将目标替换为固定字符串
print(re.sub('\[7\+9\]', '16', s))
# output> the sum of 7 and 9 is 16.
# 高级用法 1 使用前面匹配的到的内容 \1 代表 pattern 中捕获到的第一个分组的内容
print(re.sub('\[(7)\+(9)\]', r'\2\1', s))
# output> the sum of 7 and 9 is 97.
# 高级用法 2 使用函数型 repl 参数, 处理匹配到的 SRE_Match 对象
def replacement(m):
p_str = m.group()
if p_str == '7':
return '77'
if p_str == '9':
return '99'
return ''
print(re.sub('\d', replacement, s))
# output> the sum of 77 and 99 is [77+99].
# 高级用法 3 使用函数型 repl 参数, 处理匹配到的 SRE_Match 对象 增加作用域 自动计算
scope = {}
example_string_1 = "the sum of 7 and 9 is [7+9]."
example_string_2 = "[name = 'Mr.Gumby']Hello,[name]"
def replacement(m):
code = m.group(1)
st = ''
try:
st = str(eval(code, scope))
except SyntaxError:
exec code in scope
return st
# 解析: code='7+9'
# str(eval(code, scope))='16'
print(re.sub('\[(.+?)\]', replacement, example_string_1))
# output> the sum of 7 and 9 is 16.
# 两次替换
# 解析1: code="name = 'Mr.Gumby'"
# eval(code)
# raise SyntaxError
# exec code in scope
# 在命名空间 scope 中将 "Mr.Gumby" 赋给了变量 name
# 解析2: code="name"
# eval(name) 返回变量 name 的值 Mr.Gumby
print(re.sub('\[(.+?)\]', replacement, example_string_2))
# output> Hello,Mr.Gumby
属性名 | 作用 |
---|---|
flags | 获取当前正则对象的模式修正符 |
pattern | 获取正则表达式的字符串格式 |
groups | 模式单元的个数 |
geoupindex | 一个字典,用于映射由 (?P<id>) 组号定义的任何符号组名。如果模式中没有使用符号组,则字典为空。 |
p = re.compile('''(?:(?P<name>\w+\.\w+)|(?P<no>\s+\.\w+)).*? (\d+)''', re.X)
print(p.flags)
print(p.groupindex)
print(p.pattern)
print(p.groups)
'''
输出结果:
96 #这里的96是32+64,即re.U+re.X,re.U是默认模式,这个机制跟Linux系统中的文件权限很像
{'name': 1, 'no': 2}
(?:(?P<name>\w+\.\w+)|(?P<no>\s+\.\w+)).*? (\d+)
3
'''
注:正则表达式添加pos参数的用法。
选取要匹配的字符串中的(pos,endpos]位置的字符来进行匹配,不写endpos参数,则默认到最后
- findall(string[,pos[,endpos]])
- finditer(string[,pos[,endpos]])
- match(string[,pos[,endpos]])
- search(string[,pos[,endpos]])
2.3 匹配结果对象常用的方法和属性
start([group]) | 返回指定分组的开始位置,默认返回正则表达式所匹配到的第一个字符的索引 |
end([group=0]) | 返回指定分组的结束位置,默认返回正则表达式所匹配到的最后一个字符的索引 |
expand(template) | 根据模版返回相应的字符串,类似与 sub 函数里面的 repl, 可使用 \1 或者 \g<name> 来选择分组 |
group([group1, ...]) | 根据提供的索引或名字返回响应分组的内容,默认返回 start() 到 end() 之间的字符串, 提供多个参数将返回一个元组 |
groupdict([default=None]) | 返回一个包含所有匹配到的命名分组的字典,没有命名的分组不包含在内,key 为组名, value 为匹配到的内容,参数 default 为没有参与本次匹配的命名分组提供默认值 |
groups([default=None]) | 以元组形式返回每一个分组匹配到的字符串,包括没有参与匹配的分组,其值为 default |
span([group]) | 返回指定分组的起止位置组成的元组,默认返回由 start() 和 end() 组成的元组 |
__getitem__(g) | 这与之相同 m.group(g),这允许从match中更容易地访问个人组 |
s = 'Hello, Mr.Shiar : 2016/10/26'
reg = re.compile('''(?:(?P<name>\w+\.\w+)|(?P<no>\s+\.\w+)).*? (\d+)''', re.X)
res = reg.search(s)
# 返回指定分组的结束位置,默认返回正则表达式所匹配到的最后一个字符的索引
print(res.end())
#输出结果:22
# 根据模版返回相应的字符串,类似与 sub 函数里面的 repl, 可使用 \1 或者 \g<name> 来选择分组
print(res.expand("my name is \\1"))
#输出结果:my name is Mr.Shiar
# 根据提供的索引或名字返回响应分组的内容,默认返回 start() 到 end() 之间的字符串, 提供多个参数将返回一个元组
print(res.group())
#输出结果:Mr.Shiar : 2016
print(res.group(1,2))
#输出结果:('Mr.Shiar', None)
# 返回 返回一个包含所有匹配到的命名分组的字典,没有命名的分组不包含在内,key 为组名, value 为匹配到的内容,参数 default 为没有参与本次匹配的命名分组提供默认值
print(res.groupdict('default_string'))
#输出结果:{'name': 'Mr.Shiar', 'no': 'default_string'}
# 以元组形式返回每一个分组匹配到的字符串,包括没有参与匹配的分组,其值为 default
print(res.groups('default_string'))
#输出结果:('Mr.Shiar', 'default_string', '2016')
# 返回指定分组的起止未知组成的元组,默认返回由 start() 和 end() 组成的元组
print(res.span(3))
#输出结果:(18, 22)
# 返回指定分组的开始位置,默认返回正则表达式所匹配到的第一个字符的索引
print(res.start(3))
#输出结果:18
endpos | 本次搜索结束位置索引 |
lastgroup | 本次搜索匹配到的最后一个分组的别名 |
lastindex | 本次搜索匹配到的最后一个分组的索引 |
pos | 本次搜索开始位置索引 |
re | 本次搜索使用的 SRE_Pattern 对象 |
regs | 列表,元素为元组,包含本次搜索匹配到的所有分组的起止位置 |
strin | 本次搜索操作的字符串 |
s = 'Hello, Mr.Shiar : 2016/10/26'
m = re.search(', (?P<name>\w+\.\w+).*?(\d+)', s)
# 本次搜索的结束位置索引
print(m.endpos)
# output> 28
# 本次搜索匹配到的最后一个分组的别名
# 本次匹配最后一个分组没有别名
print(m.lastgroup)
# output> None
# 本次搜索匹配到的最后一个分组的索引
print (m.lastindex)
# output> 2
# 本次搜索开始位置索引
print(m.pos)
# output> 0
# 本次搜索使用的 SRE_Pattern 对象
print(m.re)
# output> re.compile(', (?P<name>\\w+\\.\\w+).*?(\\d+)')
# 列表,元素为元组,包含本次搜索匹配到的所有分组的起止位置 第一个元组为正则表达式匹配范围
print(m.regs)
# output> ((7, 22), (7, 15), (18, 22))
# 本次搜索操作的字符串
print(m.string)
# output> Hello, Mr.Shiar : 2016/10/26