正则表达式概念
python正则表达式
深入理解正则表达式环视的概念与用法
资源 | 正则表达式的功法大全
在线正则表达式验证网站
概念定义
使用单个字符串来描述匹配某个句法规则的字符串,是对字符串操作的一种逻辑公式
应用场景
处理文本和数据,提高复杂文本分析的效率
正则表达式过程
依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;否则匹配失败
1 2 3 4 5 6 7 8 9 10 11 12 import refrom IPython.core.interactiveshell import InteractiveShellInteractiveShell.ast_node_interactivity = "all" pattern_s = r'imooc' pattern_r = re.compile (pattern_s) str1 = 'imooc book' match_r = pattern_r.match (str1) print (f'match_r.group(): {match_r.group()} ' )match_r.group(): imooc
正则元字符 内容匹配
正则表达式
代表的匹配字符
.
匹配任意字符(不包括换行符)
[0-9]
0-9的数字
[a-z]
小写字母
[A-Z]
大写字母
\d
匹配数字,等同于[0-9]
\D
匹配非数字,等同于[\^0-9]
\w
匹配大小写字母、数字和下划线,等同于[a-z0-9A-Z_]
\W
匹配非大小写字母、数字和下划线,等同于[\^a-z0-9A-Z_]
\s
匹配空白
\S
匹配非空白
\u4e00-\u9fff
匹配中文字符
ps: 第(?P<law_idx>(?:(?!第|条).)*?)条
可以匹配出公平竞争审查第三方评估实施指南(2023)第十一条 中的第十一条
,而不是第三方评估实施指南(2023)第十一条
个数匹配
正则表达式
代表的匹配字符
*
匹配前面的字符或者子表达式0次或多次
+
匹配前一个字符或子表达式一次或多次
?
匹配前一个字符或子表达式0次或1次重复
{n}
匹配前一个字符或子表达式n次
{m,n}
匹配前一个字符或子表达式m至n次
{n,}
匹配前一个字符或者子表达式至少n次
*? / +? / ??
惰性匹配上一个
位置匹配
正则表达式
代表的匹配字符
^
匹配字符串开头, 多行模式下匹配每一行的开始
$
匹配字符串结尾, 多行模式下匹配每一行的结束
\A / \Z
指定字符串必须出现在开头/结尾
\b
匹配位于单词开始或结束位置的空字符串
\B
匹配不位于单词开始或结束位置的空字符串
分组匹配
正则表达式
代表的匹配字符
|
匹配左右任意一个表达式
(ab)
括号里的表达式作为一个分组
\<number>
引用编号为num的分组匹配到的字符串
(?P<name>)
分组起别名
(?P=name)
引用别名为name的分组匹配字符串
[ ]
可匹配其中任意一个字符
转义匹配
正则表达式
代表的匹配字符
\
转义字符,如\.只能匹配.,不能再匹配任意字符
基本使用 预编译 compile 函数用于编译正则表达式,生成一个正则表达式(Pattern)对象,供 match() 和 search() 这两个函数使用
如果重复多次地使用正则表达式,最好是使用compile函数把正则表达式编译成对象re.Pattern,这样会大大地提高搜索的效率
re.compile(pattern, flags=0)
属性说明
flags :编译时指定的模式
groupindex :以正则表达式中有别名的组的别名为键、以该组对应的编号为值的字典,没有别名的组不包含在内。
groups :正则表达式中分组的数量
pattern :编译时用的正则表达式
方法说明
findall、finditer、match、search、split、sub、subn 等函数
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import res = 'Hello, Mr.Gumby : 2016/10/26' pat = '''(?: # 构造一个不捕获分组 用于使用 | (?P<name>\w+\.\w+) # 匹配 Mr.Gumby | # 或 (?P<no>\s+\.\w+) # 一个匹配不到的命名分组 ) .*? # 匹配 : (\d+) # 匹配 2016 ''' p = re.compile (pat, re.X) print (f'p.flags: {p.flags} ' )print (f'p.groupindex: {p.groupindex} ' )print (f'p.groups: {p.groups} ' )print (f'p.pattern: {p.pattern} ' )p.flags: 96 p.groupindex: {'name' : 1 , 'no' : 2 } p.groups: 3 p.pattern: (?: (?P<name>\w+\.\w+) | (?P<no>\s+\.\w+) ) .*? (\d+)
匹配模式
Python正则表达式,请不要再用re.compile了
在正则表达式中,采用预编译的优势就是可以节约时间,提高效率
re.compile(pattern, flags=0)
给定一个正则表达式 pattern
指定使用的模式 flags 默认为0 即不使用任何模式,然后会返回一个 SRE_Pattern对象
参数说明
pattern : 一个字符串形式的正则表达式
flags : 可选,表示匹配模式,比如忽略大小写,多行模式等,使用按位或运算符 | 同时添加多个模式,具体参数为:
re.I :数值2,忽略大小写
re.L :数值4,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境
re.M :数值8,多行模式
re.S :数值16,即为 . 并且包括换行符在内的任意字符(. 不包括换行符)
re.U :数值32,表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库
re.X :数值64,为了增加可读性,忽略空格和 # 后面的注释
忽略大小写re.I 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import restr1 = 'hello World!' pattern_s = "hello world!" pattern_r = re.compile (pattern_s, re.I) print (f'pattern_r.match(str1).group(): {pattern_r.match (str1).group()} ' )pattern_s = "(?#注释)(?i)hello world!" pattern_r = re.compile (pattern_s) print (f'pattern_r.match(str1).group(): {pattern_r.match (str1).group()} ' )pattern_r.match (str1).group(): hello World! pattern_r.match (str1).group(): hello World!
字符集本地化re.L
多行模式re.M 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import restr1 = '''first line1 second line2 third line3''' regex_start = re.compile ("^\w+" ) print (f'regex_start.findall(str1): {regex_start.findall(str1)} ' )regex_start_m = re.compile ("^\w+" , re.M) print (f'regex_start_m.findall(str1): {regex_start_m.findall(str1)} ' )regex_end = re.compile ("\w+$" ) print (f'regex_end.findall(str1): {regex_end.findall(str1)} ' )regex_end_m = re.compile ("\w+$" , re.M) print (f'regex_end_m.findall(str1): {regex_end_m.findall(str1)} ' )regex_start.findall(str1): ['first' ] regex_start_m.findall(str1): ['first' , 'second' , 'third' ] regex_end.findall(str1): ['line3' ] regex_end_m.findall(str1): ['line1' , 'line2' , 'line3' ]
所有字符re.S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import restr2 = '''first line1 second line2 third line3''' regex = re.compile (".+" ) print (f'regex.findall(str2): {regex.findall(str2)} ' )regex_dotall = re.compile (".+" , re.S) print (f'regex_dotall.findall(str2): {regex_dotall.findall(str2)} ' )regex.findall(str2): ['first line1' , 'second line2' , 'third line3' ] regex_dotall.findall(str2): ['first line1\nsecond line2\nthird line3' ]
冗余模式re.X 1 2 3 4 5 6 7 8 9 10 import reemail_regex = re.compile ("[\w+\.]+@[a-zA-Z\d]+\.(com|cn)" ) email_regex = re.compile ("""[\w+\.]+ # 匹配@符前的部分 @ # @符 [a-zA-Z\d]+ # 邮箱类别 \.(com|cn) # 邮箱后缀 """ , re.X)
UNICODE解析re.U
re.Match类 若匹配成功,match/search返回的是Match对象,finditer返回的也是Match对象的迭代器
获取匹配结果需要调用Match对象的group()、groups或group(index)方法
属性说明
endpos : 本次搜索结束位置索引
lastgroup : 本次搜索匹配到的最后一个分组的别名
lastindex : 本次搜索匹配到的最后一个分组的索引
pos : 本次搜索开始位置索引
re : 本次搜索使用的 SRE_Pattern 对象
regs : 列表,元素为元组,包含本次搜索匹配到的所有分组的起止位置
方法说明
end([group=0]) :返回指定分组的结束位置,默认返回正则表达式所匹配到的最后一个字符的索引
expand(template) :根据模版返回相应的字符串,类似与 sub 函数里面的 repl, 可使用 \1 或者 \g 来选择分组
group([group1, …]) :根据提供的索引或名字返回响应分组的内容
默认返回 start() 到 end() 之间的字符串, 提供多个参数将返回一个元组
groupdict([default=None]) :返回一个包含所有匹配到的命名分组的字典,没有命名的分组不包含在内
key 为组名, value 为匹配到的内容,参数 default 为没有参与本次匹配的命名分组提供默认值
groups([default=None]) :以元组形式返回每一个分组匹配到的字符串,包括没有参与匹配的分组,其值为 default
span([group]) :返回指定分组的起止位置组成的元组,默认返回由 start() 和 end() 组成的元组
start([group]) :返回指定分组的开始位置,默认返回正则表达式所匹配到的第一个字符的索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import res = 'Hello, Mr.Gumby : 2016/10/26' m = re.search(', (?P<name>\w+\.\w+).*?(\d+)' , s) print (f'm.endpos: {m.endpos} ' )print (f'm.lastgroup: {m.lastgroup} ' )print (f'm.lastindex: {m.lastindex} ' )print (f'm.pos: {m.pos} ' )print (f'm.re: {m.re} ' )print (f'm.regs: {m.regs} ' )print (f'm.string: {m.string} ' )m.endpos: 28 m.lastgroup: None m.lastindex: 2 m.pos: 0 m.re: re.compile (', (?P<name>\\w+\\.\\w+).*?(\\d+)' ) m.regs: ((5 , 22 ), (7 , 15 ), (18 , 22 )) m.string: Hello, Mr.Gumby : 2016 /10 /26
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import res = 'Hello, Mr.Gumby : 2016/10/26' m = re.search('''(?: # 构造一个不捕获分组 用于使用 | (?P<name>\w+\.\w+) # 匹配 Mr.Gumby | # 或 (?P<no>\s+\.\w+) # 一个匹配不到的命名分组 ) .*? # 匹配 : (\d+) # 匹配 2016 ''' , s, re.X) print (f'm.end(): {m.end()} ' )mExpend = m.expand('my name is \\1' ) print (f"m.expand('my name is \\1'): {mExpend} " )print (f'm.group(): {m.group()} ' )print (f'm.group(1, 2): {m.group(1 , 2 )} ' )m.groupdict('default_string' ) print (f"m.groups('default_string'): {m.groups('default_string' )} " )print (f'm.span(3): {m.span(3 )} ' )print (f'm.start(3): {m.start(3 )} ' )m.end(): 22 m.expand('my name is \1' ): my name is Mr.Gumby m.group(): Mr.Gumby : 2016 m.group(1 , 2 ): ('Mr.Gumby' , None ) m.groups('default_string' ): ('Mr.Gumby' , 'default_string' , '2016' ) m.span(3 ): (18 , 22 ) m.start(3 ): 18
匹配 match
re.match(pattern, string[, flags])
从首字母开始匹配 ,string如果包含pattern子串,则匹配成功,返回Match对象,失败则返回None,若要完全匹配,pattern要以$结尾
参数说明
pattern :匹配的正则表达式
string :要匹配的字符串。
flags :标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志
match函数在string的开始位置 匹配,如果匹配则返回对象,否则返回None
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import re text = 'python world' m = re.match (r"\w+" , text) print (m.group(0 )) if m else print ('not match' )text = '#python world' m = re.match (r"\w+" , text) print (m.group(0 )) if m else print ('not match' ) output: python output: not match
1 2 3 4 5 6 7 8 import restr1 = '333STR1666STR299' pattern_s = r'([A-Z]+(\d))' print (f're.match(pattern_s, str1): {re.match (pattern_s, str1)} ' ) re.match (pattern_s, str1): None
search
re.search(pattern, string[, flags])
若string中包含pattern子串 ,则返回Match对象,否则返回None,注意,如果string中存在多个 pattern子串,只返回第一个
参数说明
pattern :匹配的正则表达式
string :要匹配的字符串
flags :标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等
search会扫描整个string 查找匹配,如果找到则返回一个相应的匹配对象,否则返回None
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import retext = 'python world' m = re.search(r"\w+" , text) print (m.group(0 )) if m else print ('not match' )text = '#python world' m = re.search(r"\w+" , text) print (m.group(0 )) if m else print ('not match' )output: python output: python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import restr1 = '333STR1666STR299' pattern_s = r'([A-Z]+(\d))' match_r = re.search(pattern_s, str1) print (f'match_r.group(0): {match_r.group(0 )} ' ) print (f'match_r.group(1): {match_r.group(1 )} ' )print (f'match_r.group(2): {match_r.group(2 )} ' )print (f'match_r.groups(): {match_r.groups()} ' )match_r.group(0 ): STR1 match_r.group(1 ): STR1 match_r.group(2 ): 1 match_r.groups(): ('STR1' , '1' )
检索 findall
re.findall(pattern, string[, flags])
返回string中所有与pattern相匹配的全部字串 ,返回形式为数组
参数说明
string : 待匹配的字符串
pos : 可选参数,指定字符串的起始位置,默认为 0
endpos : 可选参数,指定字符串的结束位置,默认为字符串的长度
findall返回所有匹配的指定模式的文本子串到列表中,一个元素一个匹配串
1 2 3 4 5 6 7 8 9 10 11 12 import restr1 = '333STR1666STR299' pattern_s = r'([A-Z]+(\d))' match_r = re.findall(pattern_s, str1) for m in match_r: print (f'm[0], m[1]: {m[0 ], m[1 ]} ' ) m[0 ], m[1 ]: ('STR1' , '1' ) m[0 ], m[1 ]: ('STR2' , '2' )
finditer
re.finditer(pattern, string[, flags])
返回string中所有与pattern相匹配的全部字串 ,返回形式为迭代器
参数说明
pattern :匹配的正则表达式
string :要匹配的字符串。
flags :标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志
finditer函数跟findall函数类似,但返回的是一个迭代器
1 2 3 4 5 6 7 8 9 10 11 12 import restr1 = '333STR1666STR299' pattern_s = r'([A-Z]+(\d))' match_r = re.finditer(pattern_s, str1) for m in match_r: print (f'm.group(0), m.group(1), m.group(2): {m.group(0 ), m.group(1 ), m.group(2 )} ' ) m.group(0 ), m.group(1 ), m.group(2 ): ('STR1' , 'STR1' , '1' ) m.group(0 ), m.group(1 ), m.group(2 ): ('STR2' , 'STR2' , '2' )
替换和分割 sub替换
re.sub(pattern, repl, string, count=0, flags=0)
将字符串中匹配正则表达式的部分替换为其他值
Return the string obtained by replacing the leftmost non-overlapping occurrences of the pattern in string by the replacement repl.
repl can be either a string or a callable; if a string, backslash escapes in it are processed. If it is a callable, it’s passed the Match object and must return a replacement string to be used.
参数说明
pattern : 正则中的模式字符串。
repl : 替换的字符串,也可为一个函数。
string : 要被查找替换的原始字符串。
count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # -*- coding: utf-8 -* import re def add_one(match): val = match.group() num = int(val) + 1 return str(num) str2 = 'imooc videonum=200' reSub_1 = re.sub(r'\d+', add_one, str2) print(f"re.sub(r'\d+', add_one, str2): {reSub_1}") reSub_2 = re.sub(r'\d+', '203', str2) print(f"re.sub(r'\d+', '203', str2): {reSub_2}") sample_text = '2020-05-20 10:59:23 hello world 2020-05-21 10:59:24 hello kitty' sample_pattern = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})' sample_repl = r'\g<month>/\g<day>/\g<year>' print(re.sub(sample_pattern, sample_repl, sample_text))
输出
1 2 3 re.sub(r'\d+', add_one, str2): imooc videonum=201 re.sub(r'\d+', '203 ', str2): imooc videonum=203 05 /20 /2020 10 :59 :23 hello world 05 /21 /2020 10 :59 :24 hello kitty
高级用法
一日一技:如何正确使用 re.sub 的第二个参数
re.sub
第二个参数可以是函数
设想有一个字符串abc18123456794xyz123
,这个字符串中有两段数字,并且长短是不一样的
第一个数字是11位的手机号。我想把字符串替换为:`abc[隐藏手机号]xyz*
不是手机号的数字,每一位数字逐位替换为星号
1 2 3 4 5 6 7 8 9 10 11 import redef test (repl ): if len (repl.group(0 )) == 11 : return '[隐藏手机号]' else : return '*' * len (repl.group(0 )) a = 'abc18123456794xyz123' b = re.sub('\d+' , test, a) print (b)
subn替换
subn(pattern, repl, string, count=0, flags=0)
作用与函数 sub 一样, 唯一不同之处在于返回值为一个元组,第一个值为替换后的字符串,第二个值为发生替换的次数
split分割
re.split(pattern, string, maxsplit=0, flags=0)
根据匹配分割字符串,返回分割字符串组成的列表
Split the source string by the occurrences of the pattern, returning a list containing the resulting substrings.
If capturing parentheses are used in pattern, then the text of all groups in the pattern are also returned as part of the resulting list. If maxsplit is nonzero, at most maxsplit splits occur, and the remainder of the string is returned as the final element of the list.
参数说明
pattern :匹配的正则表达式
string :要匹配的字符串。
maxsplit :分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。
flags :标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。参见:正则表达式修饰符 - 可选标志
代码
1 2 3 4 5 import restr3 = 'imooc:C C++ Java Python,c#' resub_3 = re.split(r':| |,' ,str3) print (f"re.split(r':| |,',str3):{resub_3} " )
输出
1 re.split(r':| |,',str3):['imooc', 'C', 'C++', 'Java', 'Python', 'c#']
正则缓存 当你在程序中使用 re 模块,无论是先使用 compile 还是直接使用比如 findall 来使用正则表达式操作文本
re 模块都会将正则表达式先编译一下, 并且会将编译过后的正则表达式放到缓存中
这样下次使用同样的正则表达式的时候就不需要再次编译, 因为编译其实是很费时的,这样可以提升效率
而默认缓存的正则表达式的个数是 100,当你需要频繁使用少量正则表达式的时候,缓存可以提升效率
而使用的正则表达式过多时,缓存带来的优势就不明显了
这个re.purge() 函数的作用是清除缓存中的正则表达式,可能在你需要优化占用内存 的时候会用到
爬虫例子
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import urllib.requestimport reimport osurl = r'https://www.imooc.com/course/list?c=ai' res = urllib.request.urlopen(url) html = res.read().decode('utf-8' ) listurl = re.findall(r'src="//img\d.+?jpg"' , html) lurl = ['http:%s' % x[5 :-1 ] for x in listurl] print (lurl)basepath = './res' if not os.path.exists(basepath): os.makedirs(basepath) imgspath = os.path.join(basepath, 're_op' ) if not os.path.exists(imgspath): os.mkdir(imgspath) for ii, uu in enumerate (lurl): savepath = os.path.join(imgspath, '%d.jpg' % (ii)) res = urllib.request.urlretrieve(uu, savepath)
输出
1 ['http://img4.mukewang.com/5 bd8157a0001a7a506000336-240 -135 .jpg', 'http://img3.mukewang.com/5 bc6e6b80001434f06000338-240 -135 .jpg', 'http://img2.mukewang.com/5 ba2386600013d3705980337-240 -135 .jpg', 'http://img3.mukewang.com/5 b9105800001288905400300-240 -135 .jpg', 'http://img4.mukewang.com/5 b7f737a0001cfb706000336-240 -135 .jpg', 'http://img4.mukewang.com/5 abc6159000142f706000338-240 -135 .jpg', 'http://img1.mukewang.com/5 a40c6370001d13c06000338-240 -135 .jpg']
进阶用法 预查询 1 2 3 4 5 6 7 8 9 10 import repattern = r'案由的相关法规:.*?(?=\n\n|$|```)' matches = re.findall(pattern, content, re.DOTALL) for match in matches: print (match )
解释 :
(?=\n\n|$|```)
- 这是一个正向预查(positive lookahead),它不消耗字符,只是检查其后面的内容。这个预查包含三个可能的匹配条件:
\n\n
匹配两个连续的换行符
$
匹配字符串的结束
```(三个反引号)
匹配代码块的开始标记,这在某些文本格式(如Markdown)中用于表示代码块
非贪婪匹配
匹配模式
Python里数量词默认是贪婪模式 的,使用*? / +? / ??可使贪婪模式变成非贪婪模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import restr1 = '9abc' pattern_s = r'[0-9][a-z]*?' pattern_r = re.compile (pattern_s) match_r = pattern_r.match (str1) print (f'match_r.group(): {match_r.group()} ' )pattern_s = r'[0-9][a-z]*' pattern_r = re.compile (pattern_s) match_r = pattern_r.match (str1) print (f'match_r.group(): {match_r.group()} ' )pattern_s = r'[0-9][a-z]+?' pattern_r = re.compile (pattern_s) match_r = pattern_r.match (str1) print (f'match_r.group(): {match_r.group()} ' )pattern_s = r'[0-9][a-z]+' pattern_r = re.compile (pattern_s) match_r = pattern_r.match (str1) print (f'match_r.group(): {match_r.group()} ' )pattern_s = r'[0-9][a-z]??' pattern_r = re.compile (pattern_s) match_r = pattern_r.match (str1) print (f'match_r.group(): {match_r.group()} ' )pattern_s = r'[0-9][a-z]?' pattern_r = re.compile (pattern_s) match_r = pattern_r.match (str1) print (f'match_r.group(): {match_r.group()} ' )match_r.group(): 9 match_r.group(): 9abc match_r.group(): 9a match_r.group(): 9abc match_r.group(): 9 match_r.group(): 9a
分组匹配(捕获组)
1 2 3 4 5 6 7 8 import regex as repat = re.compile (r"有(?P<zj>(许可证[件]?|执照))的?(?P<xf_type>(暂扣|吊销).{0,4}(?P=zj))" ) doc = "有许可证的吊销许可证,并给予严厉惩罚" res = list (re.finditer(pat, doc)) res[0 ].groupdict() Out[16 ]: {'zj' : '许可证' , 'xf_type' : '吊销许可证' }
1 2 3 4 5 6 7 8 9 import restr1 = '<book>python</book>' pattern_s = r'<([\w]+>)[\w]+</\1' print (re.match (pattern_s, str1).group())<book>python</book>
1 2 3 4 5 6 7 8 9 10 11 import restr1 = '<book>python</book>' pattern_s = r'<(?P<mark1>[\w]+>)[\w]+</(?P=mark1)' print (re.match (pattern_s, str1).group())Out[12 ]: <book>python</book> re.match (pattern_s, str1).groupdict() Out[13 ]: {'mark1' : 'book>' }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 group() / group(0 ): 母串中与模式pattern匹配的子串 group(index): 第index个group匹配成功的子串 groups(): 所有group组成的一个元组,与pattern中的()有关 import rep = re.compile ('\d-\d-\d' ) m = p.match ('2-3-1' ) print (f'm.group(): {m.group()} ' )print (f'm.group(0): {m.group(0 )} ' )print (f'm.groups(): {m.groups()} ' )m.group(): 2 -3 -1 m.group(0 ): 2 -3 -1 m.groups(): ()
1 2 3 4 5 6 7 8 9 10 11 import rep = re.compile ('(\d)-(\d)-(\d)' ) m = p.match ('2-3-1d5-4-3' ) print (f'm.group(): {m.group()} ' )print (f'm.group(0): {m.group(0 )} ' )print (f'm.groups(): {m.groups()} ' )m.group(): 2 -3 -1 m.group(0 ): 2 -3 -1 m.groups(): ('2' , '3' , '1' )
1 2 3 4 5 6 7 8 9 10 11 import rep = re.compile ('(\d)-(\d)-(\d)' ) m = re.match (p,'2-3-1d5-4-3' ) print (f'm.group(): {m.group()} ' )print (f'm.group(0): {m.group(0 )} ' )print (f'm.groups(): {m.groups()} ' )m.group(): 2 -3 -1 m.group(0 ): 2 -3 -1 m.groups(): ('2' , '3' , '1' )
转义匹配替代函数 使用python的过程中,你肯定对转义字符的使用苦恼过,因为有的时候我们需要使用一些特殊符号如”$ * . ^”等的原意
有时候需要被转义后的功能,并且转义字符地使用很繁琐,容易出错
re.escape(pattern)
转义: 如果你需要操作的文本中含有正则的元字符,你在写正则的时候需要将元字符加上反斜扛 \ 去匹配自身
而当这样的字符很多时,写出来的正则表达式就看起来很乱而且写起来也挺麻烦的,这个时候你可以使用这个函数
可以对字符串中所有可能被解释为正则运算符的字符进行转义的应用函数
1 2 3 4 5 6 7 8 9 10 11 12 import restr2 = ".+\d123" regex_str = re.escape(".+\d123" ) print (f'regex_str: {regex_str} ' )for g in re.findall(regex_str, str2): print (g) regex_str: \.\+\\d123 .+\d123
re增强的包装 主要目的是 ,增强re的匹配能力,为match对象添加了start: int=0
参数,start指定了当前字符串的起始位置,默认的起始位置是0
导入库,regex是re的增强版,需要pip安装,它支持更强的正则,比如一个表达式中可以出现两个一样的捕获组名字
1 2 from typing import Iterable, List import regex as re
包装match对象为ReMatch类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 class ReMatch : def __init__ (self, match , start: int = 0 ): """ 指定起始位置的Match类 :param match: re.Match对象 :param start: """ self.match = match self.start_idx = start @property def endpos (self ): """ 本次搜索结束位置索引 """ return self.match .endpos + self.start_idx @property def lastgroup (self ): """ 本次搜索匹配到的最后一个分组的别名 """ return self.match .lastgroup @property def lastindex (self ): """ 本次搜索匹配到的最后一个分组的索引 """ return self.match .lastindex + self.start_idx if type (self.match .lastindex) == int else self.match .lastindex @property def pos (self ): """ 本次搜索开始位置索引 """ return self.match .pos + self.start_idx @property def re (self ): """ 本次搜索使用的SRE_Pattern对象 """ return self.match .re @property def regs (self ): """ 列表,元素为元组,包含本次搜索匹配到的所有分组的起止位置 """ return tuple ([(s + self.start_idx, e + self.start_idx) for s, e in self.match .regs]) def end (self, *args, **kwargs ): """ 返回指定分组的结束位置,默认返回正则表达式所匹配到的最后一个字符的索引 """ return self.match .end(*args, **kwargs) + self.start_idx def expand (self, *args, **kwargs ): """ 根据模版返回相应的字符串,类似与 sub 函数里面的 repl, 可使用 \1 或者 \g 来选择分组 """ return self.match .expand(*args, **kwargs) def group (self, *args, **kwargs ): """ 根据提供的索引或名字返回响应分组的内容,默认返回 start() 到 end() 之间的字符串, 提供多个参数将返回一个元组 """ return self.match .group(*args, **kwargs) def groupdict (self, *args, **kwargs ): """ 返回一个包含所有匹配到的命名分组的字典,没有命名的分组不包含在内,key 为组名, value 为匹配到的内容,参数 default 为没有参与本次匹配的命名分组提供默认值 """ return self.match .groupdict(*args, **kwargs) def _build_key_2_index (self ): groups = self.match .groups() group_2_index = [] for idx, group in enumerate (groups, start=1 ): group_2_index.append((group, self.match .start(idx)+self.start_idx, self.match .end(idx)+self.start_idx)) return group_2_index def groupdict_with_index (self, *args, **kwargs ): """ 同groupdict, 但匹配结果包装为带索引的ContentWithIndex对象 """ value = self.match .groupdict(*args, **kwargs) new_group_dict = dict () for k, v in value.items(): for group, s, e in self._build_key_2_index(): if v == group: new_group_dict[k] = ContentWithIndex(content=v, start=s, end=e) assert len (new_group_dict) == len (value) return new_group_dict def groups (self, *args, **kwargs ): """ 以元组形式返回每一个分组匹配到的字符串,包括没有参与匹配的分组,其值为 default """ return self.match .groups(*args, **kwargs) def span (self, *args, **kwargs ): """ 返回指定分组的起止位置组成的元组,默认返回由 start() 和 end() 组成的元组 """ return tuple ([(s + self.start_idx, e + self.start_idx) for s, e in self.match .span(*args, **kwargs)]) def start (self, *args, **kwargs ): """ 返回指定分组的开始位置,默认返回正则表达式所匹配到的第一个字符的索引 """ return self.match .start(*args, **kwargs) + self.start_idx def start_and_end (self ): return self.start(), self.end() def __str__ (self ): return self.__repr__() def __repr__ (self ): return f"<regex.Match object; span=({self.start()} , {self.end()} ), match='{self.match .group()} '>"
匹配结果类ContentWithIndex:主要是统一像split这样的方法返回的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class ContentWithIndex : def __init__ (self, content: str , start: int , end: int ): """ 带索引的切分结果 :param content: 切分结果 :param start: 起始位置 :param end: 结束位置 """ self.content = content self.start = start self.end = end @property def pos (self ): return self.start @property def posend (self ): return self.end def start_and_end (self ): return self.start, self.end def __str__ (self ): return self.__repr__() def __repr__ (self ): return f"{self.content} ({self.start} , {self.end} )"
re包装的工具类ReUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 class ReUtils : def __init__ (self ): """ 正则匹配的工具类 """ pass @staticmethod def match (pattern, string, flags=0 , start: int = 0 ): match = re.match (pattern=pattern, string=string, flags=flags) return ReMatch(match =match , start=start) @staticmethod def fullmatch (pattern, string, flags=0 , start: int = 0 ): match = re.fullmatch(pattern=pattern, string=string, flags=flags) return ReMatch(match =match , start=start) @staticmethod def search (pattern, string, flags=0 , start: int = 0 ): match = re.search(pattern=pattern, string=string, flags=flags) return ReMatch(match =match , start=start) @staticmethod def sub (pattern, repl, string, count=0 , flags=0 ): return re.sub(pattern=pattern, repl=repl, string=string, count=count, flags=flags) @staticmethod def subn (pattern, repl, string, count=0 , flags=0 ): return re.subn(pattern=pattern, repl=repl, string=string, count=count, flags=flags) @staticmethod def split (pattern, string, maxsplit=0 , flags=0 ): return re.split(pattern=pattern, string=string, maxsplit=maxsplit, flags=flags) @staticmethod def split_with_index (pattern, string, maxsplit: int = 0 , flags=0 , start: int = 0 ) -> List [ContentWithIndex]: """ split带内容索引 :param pattern: :param string: :param maxsplit: :param flags: :param start: :return: """ matches = ReUtils.finditer(pattern, string, flags, start=start) content_with_indexs = [] start_idx = start for match in matches: end_idx = match .start() content_with_index = ContentWithIndex(content=string[start_idx - start:end_idx - start], start=start_idx, end=end_idx) content_with_indexs.append(content_with_index) start_idx = match .end() if maxsplit and len (content_with_indexs) >= maxsplit: content_with_index = ContentWithIndex(content=string[start_idx - start:], start=start_idx, end=len (string)) content_with_indexs.append(content_with_index) break else : content_with_index = ContentWithIndex(content=string[start_idx - start:], start=start_idx, end=len (string) + start) content_with_indexs.append(content_with_index) return content_with_indexs @staticmethod def findall (pattern, string, flags=0 ): return re.findall(pattern=pattern, string=string, flags=flags) @staticmethod def finditer (pattern, string, flags=0 , start: int = 0 ) -> Iterable[ReMatch]: for match in re.finditer(pattern=pattern, string=string, flags=flags): yield ReMatch(match =match , start=start)