狮子的雄心,骆驼的耐力,孩子的执著!
分类: python/ruby
2023-09-03 10:24:10
正则表达式的用法较多,也比较灵活。网上的资料不是很全面。自己之前也只学了一点点,这里复习一下,并按照官方文档做一个总结,按照python3.10归纳。
本文按照下面的顺序讲解。
re.compile()是用于创建一个正则表达式【pattern】对象。这个对象就支持了正则匹配的所有方式。是一个不可变类型。
所以,正则匹配一般有两种方式:
#直接使用正则对象的方法
a = re.compile(r"\d ")
x = a.findall("hello12world34python56")
print(a) #re.compile('\\d ') #对象不变,属于不可变对象,方便后面反复使用
print(x) #['12', '34', '56']
#或者使用re模块的函数
x = re.findall(r"\d ","hello12world34python56") #需要指定正则表达式
print(x)
测试效率
start_time = time.time()
a = re.compile(r"\d ")
for i in range(1000000):
x = a.findall("hello12world34python56")
x = a.match("hello12world34python56")
end_time = time.time()
print(f"方法一用了{end_time-start_time}秒")
start_time = time.time()
for i in range(1000000):
x = re.findall(r"\d ","hello12world34python56")
x = re.match(r"\d ","hello12world34python56")
end_time = time.time()
print(f"方法二用了{end_time-start_time}秒")
结果:
shape \* mergeformat
一般情况下,对于个人使用者,两种方式在效率上的区别不大,只不过pattern正则对象方便反复调用。
【两个含义】:
?:表示对它前面的正则式匹配0到1次重复,\?表示?,不再是功能符号。
【特殊序列功能】
\d:表示匹配任何十进制数字;这等价于类[0-9] 。这里不是代表匹配字母"d"。
如果是”\\d“因为转义的存在,先将”\\“表现为”\“,然后与”d“组合成匹配数字的功能。并不会匹配”\“。
这里先介绍,pycharm在正则表达式的提示功能。
shape \* mergeformat
在pycharm中,在输入字符串时。
※※※正则表达的多次转义:
在不使用r前缀的情况下,正则表达式会因为多次转义而导致,与我们的预期不符合。
比如:
我们想要匹配【"\" "数字"】。
从图中,我们看一下。
shape \* mergeformat
在不加r前缀的情况,\\\d中的\d功能已经失效。因为在正则表达式中,会多次转义。
实际上,在不使用r前缀的时候。四个反斜杠是单斜杠,八个反斜杠才是双斜杠,不正确的分斜杠个数可能就会破坏后面的序列功能。
※※※r的功能:不再多次转义。但仍有转义过程。
这个时候,r"\\"代表单斜杠,r"\\\\"代表双斜杠。
r"\\\d"中的\d仍然可以匹配数字。"\\\d"就不能匹配数字了。
【提示】:如果不是很熟悉正则表达式,那么在pycharm上编写,可以通过颜色大致进行判断。
我们通过a = re.compile(".") 创建正则表达式pattern对象。 #默认方式
也可以指定pattern对象的flags属性。这个属性,会对匹配方式造成一些影响,这里做具体的讲解。
a = re.compile(".",re.dotall) #指定flags属性
正则匹配对象的创建方法。第二种方法,指定了一个
(点) 在默认模式,匹配除了换行的任意字符。
如果指定了flags属性re.dotall,它将匹配包括换行符的任意字符。指定方式。
a = re.compile(".",re.dotall)
标签属性可能比较难以记住,但是编辑器的代码提示功能会帮我们解决这个问题。平时多写写就习惯了。
匹配点号:r"\."
例子:①re.compile(".123")匹配"a123",②re.compile(".123")不匹配"a\n123",
③re.compile(".123",re.dotall)匹配"a\n123"的”\n123",
④re.compile(".*123",re.dotall)匹配"a\n123"的"a\n123",
flags其他属性这里先不用过于在意,后面再讲。
对它前面的正则式匹配0到任意次重复, 尽量多的匹配字符串。ab*会匹配'a','ab',或者'a'后面跟随任意个'b'。
匹配星号:r"\*"
例子:ca*t 将匹配 'ct' (0个 'a' 字符),'cat' (1个 'a' ), 'caaat' (3个 'a' 字符),等等。
对它前面的正则式匹配0到1次重复,尽量多的匹配。 ab? 会匹配 'a' 或者 'ab'。
匹配问号:r"\?"
例子:ab? 会匹配 'a' 或者 'ab'
小括号里面看成一个整体。
匹配小括号:用 \( 或 \)转义, 或者把它们包含在字符集合里: [(], [)].
例子:1(23) 匹配123、12323.....
其他功能:括号的功能很多,比如分组。这个在后面再将。第七节。
[]用于表示一个字符集合。内部可以用-链接
匹配中括号:\[ \]转义
例子:[a-c]是一个集合,代表匹配集合里面的一个字符。 与后面3.9的竖线|对比着看。
[a-c][1-3]匹配a3、c1,不匹配aa、a5、d6
※※※集合的特殊情况
对它前面的正则式匹配1到任意次重复,尽量多的匹配。
匹配加号:\
例子:ab 会匹配 'a' 后面跟随1个以上到任意个 'b',它不会匹配 'a'。
\d 匹配多个数字组合,比如131,131789
用法1:{m}
对其之前的正则式指定匹配 m 个重复;少于 m 的话就会导致匹配失败。
例子: a{6} 将匹配6个 'a' , 但是不能是5个。
用法2:{m,}
对其之前的正则式指定匹配至少m 个重复,尽量取多。少于 m 的话就会导致匹配失败。
例子: a{6} 将与 'aaaaaaaaaaaaaa' 匹配, 得到全部a。
用法2:{m,n}
对正则式进行 m 到 n 次匹配,在 m 和 n 之间取尽量多。
例子:a{3,5} 匹配 'aaaaaa'得到5个a
用法3:{m,n}?
非贪婪模式,只匹配尽量少的字符次数。
例子:a{5,10}?与10个a匹配,将匹配成功两次,都是五个a
补充:贪婪和非贪婪
区分贪婪和非贪婪,是为了更好的获得我们想要的内容。并且不与正则表达式的其他本意冲突。
*', ' ',和 '?' {m,} {m,n}修饰符都是贪婪的。就算?问号只匹配0个或者1个,也是贪婪的。
例子1:如果我们要从内容"head内容"找出标签
<.*>或者<. >或者<.{1,}>或者<.{1,99}>将匹配整个字符串:head内容.
这与我们的预期不一致。我们想要的是、这种形式。
贪婪的意思是找出符合条件的{banned}最佳长字符串。
我们使用非贪婪方式。
*? ? ? ? {m,} ? {m,n}? 是非贪婪字符。就是在贪婪的功能后面加上一个问号。
<.*?>或者<. ?>或者<.{1,}?>或者<.{1,99}?>将只能匹配出来,多次匹配后,分别也会将后面的内容匹配出来,当然这需要与相应的函数配合。
例子2:上面讲了 *、、{m,} 、 {m,n}的贪婪影响的例子。还没有讲?。这个例子就讲?问号的情况。
一般情况?问号因为{banned}最佳多匹配一次,所以很难对其他内容造成影响。但是在分组的时候,还是会造成影响。分组的具体含义,后面再讲,这里先看看案例。
匹配电话号码:下面的例子看不懂可以不同管,主要是介绍?对于匹配的影响。
a = re.compile(r'(.?)(\d -\d -\d )')
x = a.match("131-1111-1111")
print(x)
print(x.group(2))
shape \* mergeformat
虽然成功匹配了电话号码131-1111-1111,用到分组x.group(2)。然后发现保存的内容出错了。
我们改成非贪婪模式。(.?)改成(.??)
关于分组,这里不用过于纠结。后面再具体讲。
默认:匹配字符串尾或者在字符串尾的换行符的前一个字符。
他是行尾限制符。
com$代表以com结尾。
.*com$如果与"\"匹配,只能匹配到''
因为,$虽然是行尾符,但是在默认匹配模式下面,是不能跨行限制的。
如果添加re.multiline属性。那么可以根据换行符分割尾部。
print(re.findall(".*com$","\",re.multiline))得到
['', '']两个匹配结果。
补充:
$ 行尾
\z 字符串尾 无论是否设置re.multiline属性,它都指的字符串的尾巴用什么结尾。
print(re.findall(".*com\z","\",re.multiline))得到
['']
a|b, a 和 b 可以是任意正则表达式匹配 a 或者 b。 '|' 分隔开的正则样式从左到右进行匹配。当一个样式完全匹配时,这个分支就被接受。意思就是,一旦 a 匹配成功, b 就不再进行匹配,即便它能产生一个更好的匹配。或者说,'|' 操作符绝不贪婪。
例子:
aa|bb|cc与aabbcc匹配,将得到aa。使用findall函数,会得到aa、bb、cc。因为匹配了三次。先看前面两个字符,成功了。继续从第三个,判断,34个字符匹配仍然成功。一直到第三次匹配成功。
aa|aabb|aabbcc与aabbcc匹配,只能得到aa。因为从左到右绝不贪婪。并不会为了让结果匹配的更多,而变成aabbcc。
看一下与中括号的区别:
[abc]与aabbcc匹配,可以成功匹配6次。
print(re.findall('[abc]',"aabbcc"))结果是['a', 'a', 'b', 'b', 'c', 'c']
作用1:
匹配字符串的开头,这个跟$类似,默认没有打开跨行限制,根据情况设置re.multiline。
^www.*与"\"匹配,得到
设置re.multiline后,可以匹配 和
补充:
^ 行头
\a 字符串头 无论是否设置re.multiline属性,它都指的字符串的头部用什么开头。
print(re.findall("\awww.*","\",re.multiline))得到
['']
作用2:
[]中括号内首字母是^,代表取反。
[^1-47-9] 与"0124558952abc334\n876"匹配,结果['0', '55', '5', 'abc', '\n', '6']
[^1-47-9]也会包含进去换行符。
另外,序列功能的大小写,一般对应相反关系,有些没有对应的相反关系。下面做一个总结。
前面提到了\a 与^都是用什么开头,\a是字符串开头,限制更加严格,^是行开头。
print(re.findall("^1.*","132541256\n1245",re.multiline))
结果:['132541256', '1245'] ^是行开头,设置re.multiline支持多行。^1.*匹配1开头的行。
print(re.findall("\a1.*","132541256\n1245",re.multiline))
结果:['132541256'] \a是字符串开头,设置re.multiline也没用。^1.*开头的字符串
\w 匹配字母数字下划线,
\w 匹配非字母数字下划线
\w \w一起组成unicode字符。但是上面的介绍一般是对于英文使用者。
案例:
import re
str_="今天,温度是26℃ 明天,是28℃。"
print(re.findall("\b ",str_))
print(re.findall("\w ",str_))
print(re.findall("\w ",str_))
结果:
shape \* mergeformat
也就是。\w匹配字母数字下划线,虽然这么说,但是实际上,中文也在这个范围。
对于不同的语言环境,必要的测试是必要的。
\w匹配的是各种符号。空格也在这个范围。
\b 匹配空字符串“”,匹配的单词边界。不输出实际的空字符串。
\b 匹配空字符串,但 不 能在词的开头或者结尾。
\b和\b的用法和例子可以看看下面的例子。
\d 匹配任意数字,等价于 [0-9]
\d 匹配任意非数字 在中文环境是匹配了中文的。
\d 与"123一二三"匹配,可以匹配到123
\s匹配任意空白字符,等价于[\t\n\r\f\v]。
\s匹配非\s
print(re.findall("\s ","今天 \n,明天\f后天"))
print(re.findall("\s ","今天 \n,明天\f后天"))
shape \* mergeformat
前面提到了,代表字符串尾。不再举例。
之前稍微提了一下,正则对象,可以设置属性。属性只读。
a = re.compile(". ",re.a)
print(a) #re.compile('. ', re.ascii)
a = re.compile(". ")
a.flags = re.a #报错:attributeerror: readonly attribute
print(a)
这个也用于re的函数中。
a = re.compile(". ",re.dotall)
print(a.findall("今天天气好\n明天天气不好"))
##等价
print(re.findall(". ","今天天气好\n明天天气不好",re.dotall))
两种方法书写都是可以的。
让 \w, \w, \b, \b, \d, \d, \s 和 \s 只匹配ascii,而不是unicode。这只对unicode样式有效,会被byte样式忽略。
上面的解释是凯发k8官网下载客户端中心官网文档上的解释。但是我们作为中文使用者,实际上会遇到一些坑。
案例:
a = re.compile("\s 好",re.a)
print(a.findall("今天天气好\n"))
a = re.compile("\s 好")
print(a.findall("今天天气好\n"))
a = re.compile("\w 好",re.a)
print(a.findall("今天天气好\n"))
a = re.compile("\w 好")
print(a.findall("今天天气好\n"))
shape \* mergeformat
\s应该匹配非[\t\n\r\f\v]的其他字符
我们发现,\s不论是否设置re.a,都仍然匹配了中文。
\w设置re.a后,就不会匹配中文了。但是官方文档写的【让 \w, \w, \b, \b, \d, \d, \s 和 \s 只匹配ascii】。
所以,设置ascii 限制的时候,{banned}最佳好测试一下,一般\w不会出错。
如果我们需要设置,提取所有字符,就用.*
提取ascii 字符,就用\w*,并设置re.ascii 。
显示编译时的debug信息。无论成功不成功,都会显示一串信息。
a = re.compile("\w ",re.debug)
print(a.findall("今天天气好\n"))
a = re.compile("[0-9a-za-z] ",re.debug)
print(a.findall("今天天气好\n"))
shape \* mergeformat
也可以用在[a-z]这种地方。
print(re.findall("[a-z] ", "天天up")) #['up']
print(re.findall("[a-z] ", "天天up")) #[]
print(re.findall("[a-z] ", "天天up", re.ignorecase)) #['up']
print(re.findall("one", "one,two,three")) #[]
print(re.findall("one", "one,two,three", re.ignorecase)) #['one']
由当前语言区域决定\w,\w,\b,\b和大小写敏感匹配。这个标记只能对byte样式有效。这个标记不推荐使用,因为语言区域机制很不可靠,它一次只能处理一个 "习惯”,而且只对8位字节有效。unicode匹配在python 3 里默认启用,并可以处理不同语言。
前面讲^和$的时候提到过,他们的操作针对的行头和行尾,但是如果不设置跨行,就只会对{banned}中国第一行的行头匹配,或者{banned}最佳后一行的行尾匹配。
如果忘记了。可以翻上去看看,这里不写了。
.号的时候,提到过.不包含换行符,如果需要换行符弄进来,需设置re.dotall
添加注释。用不用看个人。
下面是标准写法。
a = re.compile(r"今天.*明天.*后天.*")
str_ = "今天吃鸡,明天吃鸭子,后天吃鹅"
print(a.findall(str_)) #['今天吃鸡,明天吃鸭子,后天吃鹅']
下面是修改后。
a = re.compile(r"""今天.* # 注释1
明天.* # 注释2
后天.* # 注释3
""",re.verbose)
str_ = "今天吃鸡,明天吃鸭子,后天吃鹅"
print(a.findall(str_)) #['今天吃鸡,明天吃鸭子,后天吃鹅']
正则表达的函数和正则对象的方法是一一对应的。之前也提到过想用哪种书写方式就用哪种书写。
找到匹配样式的{banned}中国第一个位置,并返回一个相应的匹配对象。如果没有匹配,就返回一个none; 注意这和找到一个零长度匹配是不同的。
它的返回值是一个对象。re.match object。比如
match object的方法:
案例:
a = re.compile("\d{3}") #匹配三个数字
x = a.search("a123b1234c567")
print(x)
<re.match object; span=(1, 4), match='123'> #因为是找到search是找{banned}中国第一个匹配项目,所以只有一个结果。
根据属性,我们可以通过span()获得位置(1, 4),通过match()获得'123'。
比如菜鸟教程上的案例。
另外,我们使用search()的时候必须判断是否为none。
print(re.match('com', '').span())
.
补充,因为还有.start() 和.end() 方法。所以我们可以获取匹配结果以外的值。
a = re.compile("\d{3}") #匹配三个数字
str_ = "a123b1234c567"
x = a.search(str_)
print(str_[:x.start()] str_[x.end():]) #ab1234c567 获取匹配结果以外的值
尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match() 就返回 none。
.match()与.search()的区别就是是不是从字符串开始。
a = re.compile("\w\d{3}")
str_ = "a123b1234c567"
x = a.match(str_)
print(x) #
可能之前在菜鸟教程上看到过.group()方法,而且结果还不一致。
这里就补充一下分组的概念。
这里就需要用到分组和match object的另一个方法.group()。
方法:
案例:
a = re.compile("\w\d{3}中间字符.*")
str_ = "a123中间字符b1234c567"
x = a.match(str_)
print(x)
print(x.group()) #与print(x.group(0))等价,就是输出结果的意思。
print(x.group(0)) #上面一个意思
print(x.group(1)) #报错,因为没有{banned}中国第一组
print(x.group(2)) #报错,因为没有第二组
print(x.group(3)) #报错,因为没有第三组
先简单说明一下,括号里面的内容不仅看成一个整体,还可以看成一个分组。
这里修改。\w\d{3}中间字符.*修改为(\w\d{3})中间字符(.*) 这里不会改变匹配结果,但是会影响函数的输出。
其中,(\w\d{3})就是{banned}中国第一组,(.*)是第二组。
所以:
a = re.compile("(\w\d{3})中间字符(.*)")
str_ = "a123中间字符b1234c567"
x = a.match(str_)
print(x) #
print(x.group()) #a123中间字符b1234c567
print(x.group(0)) #a123中间字符b1234c567
print(x.group(1)) #a123 #{banned}中国第一个分组匹配的是a123
print(x.group(2)) #b1234c567 #第二个分组匹配的是b1234c567
print(x.group(3)) #报错,没有第三组。
groups()就是返回所有组的信息。比如上面的例子就是返回:('a123', 'b1234c567')
前面提到了将括号中的字符作为一个分组。
前面提到了re.ignorecase模式,忽略大小写。但是只想在某个地方使用忽略,而不在其他地方忽略呢?就可以用到内联标记。
①re.ignorecase 与 (?i:)
用法:(?i:正则表达式)
a = re.compile("(super) app")
x = a.search("super app")
print(x) #不能用super匹配super
a = re.compile("(?i:super) app")
x = a.search("super app")
print(x) #
看到这里,你应该已经理解了。内联标记实际就是匹配模式的部分范围生效。
②re.multiline 与 (?m:) '^'和'$'的跨行限制开头结尾。
③re.dotall 与 (?s:) 让 '.'支持换行符的匹配。
④re.verbose 与 (?x:) 支持注释
⑤re.ascii 与 (?a:) 让 \w, \w, \b, \b, \d, \d, \s 和 \s 只匹配ascii
补充:
多种标记方式:(?im: ) im可以换成任何你想要加的属性,只要不冲突就行。
比如:(?is:)
a = re.compile(r'(?i:天天.*up)')
str_ = '天天up天天\nup'
x = re.match(a,str_)
print(x) #
a = re.compile(r'(?is:天天.*up)')
str_ = '天天up天天\nup'
x = re.match(a,str_)
print(x) #
排除标记方式:
比如flasg属性中,已经指定了忽略大小写,但是在某个地方一定不能忽略大小写。那么我们就可以设置排除标记方式。
(?-im: )im可以换成任何你想要加的属性。?后面的-就是排除flasg属性的意思。
例子:例子不好举。这里就用python的none举例。
none expressing empty。none 不能忽略大小写,而后面的可以忽略大小写。
a = re.compile(r'(?-i:none).*empty',re.ignorecase)
str_1 = 'none expressing empty'
str_2 = 'none expressing empty'
x = a.match(str_1)
print(x) #none 因为str_1 中none 不能忽略大小写,所以无匹配项。
x = a.match(str_2)
print(x) #
另外,还支持这种方式:(?ms-i: )
①() 按照顺序,从1开始分组。
之前提到了()小括号就是一个分组。
比如,座机号码有区号和号码。{banned}中国第一组区号,是三到四个数字,第二组号码(5-8位),首位非0,其他的是数字
(\d{3,4})-([1-9]\d{4,7}){banned}中国第一组是区号,第二组是号码。
②\number 它不是创建分组功能,是调用分组的功能。
常用在匹配html标签。
html匹配的时候,我们一般找的都是标签对应的部分。
html与/html对应,所以html设置一个分组,/html可以用\number代替。
a = r'<(. )><(. )>. '
str_ = 'head部分'
x = re.match(a,str_ )
print(x) #
print(x.groups()) #('html', 'head')
③(?p
上面的例子,可以用这种形式修改,组的名字。称为命名组。
a = r'<(?p
str_ = 'head部分'
x = re.match(a,str_)
print(x) #
print(x.group('label_1')) #head
print(x.group(1)) #html
print(x.group('label_2')) #html
print(x.group(2)) #head
print(x.groups()) #('html', 'head')
我在上面的例子中,特意将{banned}中国第一个分组,命名为label_2,第二个分组命名为label_1。
我们看到x.group('label_1')输出的就是第二个分组,x.group(数字)是按照分组序号输出。
x.groups()是按照1,2,3这种序号顺序输出的。
好处:在不同的网页中,可能都存在我们想要的信息,但是位置不同,我们可以通过分组命名方式,然后用同一个名字输出想要的内容。而不用在意他们的顺序。
①非捕获组 (?:)
它的意思就是让小括号只具备看成一个整体的意义,而不具备分组意义。
例子:
x = re.search('([a-z][a-z]) \d','aa12aa1')
print(x) #
print(x.group(1)) #aa
普通方法,x.group(1)能输出组的匹配内容。
但是:
x = re.search('(?:[a-z][a-z]) \d','aa12aa1') #(?:),让小括号不具备分组的意义
print(x) #
print(x.group(1)) #报错 indexerror: no such group
我们看到匹配的整体结果是不变的,但是x.group(1)已经不能输出组的内容,因为组已经不存在了。
②(?#…) 注释,里面的内容会被忽略。
这个就是字面意思,一看就懂。
③ 断定标记
有四个:
(?<=…)xx:去匹配xx,但是前面的内容也必须满足条件。
(?<!…)xx:去匹配xx,但是前面的内容必须不能满足条件,就是取反。
xx(?=…):去匹配xx,但是后面的内容也必须满足条件。
xx(?!…): 去匹配xx,但是后面的内容也必须不能满足条件,就是取反。
共同特点,断定标记里面的内容用于额外判断,但不作为匹配结果。
比如,你要检索一串数字,但是需要前后的字符串满足条件,而且输出结果只包含数字。
a = re.compile('\d (?=good)')
print(a.search("a12b3h5good56"))
a = re.compile('\d (?!good)')
print(a.search("1good12a"))
a = re.compile('(?<=good)\d ')
print(a.search("a12good3h5z56z"))
a = re.compile('(?
print(a.search("a12z56good3h5z56"))
结果:
shape \* mergeformat
④判断匹配。(?(id/name)yes-pattern|no-pattern)
这个功能很强大,可以理解成正则表达式的条件判断。
某个组,如果匹配成功,将会尝试匹配 yes-pattern ,否则就尝试匹配 no-pattern,no-pattern 可选,也可以被忽略。
shape \* mergeformat
官方文档上,只给了尝试匹配的效果。
下面补充一个例子。
比如,匹配qq邮箱和126邮箱,我记得当初我注册126邮箱的时候,要求{banned}中国第一个必须是字母。不管,现在变化了没有,但是我们仍然这样规定。
qq邮箱:@前面全部是数字。
126邮箱:@前面{banned}中国第一个是字母,其他的是字母、数字、下划线。
a = re.compile(r'^((\d )|[a-za-z](?a:\w ))@(?(2)(qq)|(126))\.com$')
print(a.search('12345@qq.com'))
print(a.search("a123@qq.com"))
print(a.search("123@qq.com"))
print(a.search("a123@126.com"))
print(a.search("a1中文23@126.com"))
print(a.search("a123_15@126.com"))
print(a.search("123@126.com"))
shape \* mergeformat
shape \* mergeformat
前面提到过,match与search的区别是,match需要是起始位置匹配成功。
fullmatch需要是起始位置和结束位置同时匹配成功,也就是整个字符串匹配成功。
实际上search、match、fullmatch是可以相互转化成等价意义的。
re.search("小明", "张小明说他累了") #只有它能匹配
re.match("小明", "张小明说他累了")
re.fullmatch("小明", "张小明说他累了")
我们加上限制符号。
re.search("^小明", "张小明说他累了")跟re.match("小明", "张小明说他累了")一个意思。
re.search("^小明$", "张小明说他累了")跟re.fullmatch("小明", "张小明说他累了")一个意思。
print(re.match("小明", "张小明说他累了"[1:])) 加了字符串的切片,也能匹配成功。
re.fullmatch("小明", "张小明说他累了"[1:3]) 加了字符串的切片,也能匹配成功。
这里小括号有重要的影响。组里的文字也会包含在列表里。
例子:"1 2-3*5/6"进行分割,按照 -*/进行分割
re.split('[ \-*/]', "1 2-3*5/6")
结果是:['1', '2', '3', '5', '6']
之前再{banned}中国第一篇小结:数字型中提到过,如果我们要保留运算,使用decimal数字类型。
那么我们就需要在分割的时候保留 -*/。我们使用分组。就是加一个括号就行了。
re.split('([ \-*/])', "1 2-3*5/6")
结果是:['1', ' ', '2', '-', '3', '*', '5', '/', '6']
maxsplit=0 是指定分割的次数。默认0是不受限制的意思。
re.split('([ \-*/])', "1 2-3*5/6",maxsplit=2)
结果是:['1', ' ', '2', '-', '3*5/6']
返回字符串中的所有非重叠模式,作为字符串或元组列表。左右扫描字符串,匹配在找到的顺序中返回。结果包含空匹配。
结果取决于模式中捕获组的数量。它的结果不一定是列表。
1. 如果没有分组,则返回与整个模式匹配的字符串列表。
2. 如果恰好有一个组,请返回匹配该组的字符串列表。
3. 如果存在多个组,则返回与组匹配的字符串的元组列表。
4. 非捕获组不会影响结果的形式。
正因为这样多变的特性,所以我们在没有理解小括号的全部意义的时候,findall的返回结果会让我们莫名其妙。
{banned}中国第一点:
例子1:查找a1b22c333d4444e5555,中所有【一个字母 2个及以上数字】的匹配结果。
re.findall('[a-za-z]\d{2,}','a1b22c333d4444e5555')
结果:['b22', 'c333', 'd4444', 'e5555']
这是我们{banned}最佳常见的用法。比较简单。
第二点:
例子2:这里我们对【一个字母 2个及以上数字】的【2个及以上数字】进行分组。加上括号。
re.findall('[a-za-z](\d{2,})','a1b22c333d4444e5555')
结果:['22', '333', '4444', '5555']
结果已经变了。这个在理解了分组之后,也很好理解。使用了分组之后,findall不会输出分组以外的内容。
例子3:如果我们又要使用分组功能,又想要输出全部内容,那么就在整体外面再加一个括号。
re.findall('([a-za-z](\d{2,}))','a1b22c333d4444e5555')
结果是:[('b22', '22'), ('c333', '333'), ('d4444', '4444'), ('e5555', '5555')]
看到元组列表中,每个元组的{banned}中国第一项就是全部内容的结果。
第三点:如果存在多个组,则返回与组匹配的字符串的元组列表。。上面例子3就是也可以当做案例。
第四点:我们讲例子2中的小括号弄成非捕获组。
re.findall('[a-za-z](?:\d{2,})','a1b22c333d4444e5555')
结果:['b22', 'c333', 'd4444', 'e5555'] #原来的结果是['22', '333', '4444', '5555']
与findall的区别
①finditer返回的是迭代器,需要再next()后使用, 或者在循环中使用。
②如果有分组的存在。
findall会输出分组里面的内容,而不输出分组以外的匹配内容。
finditer会迭代输出每一个匹配结果的re.match对象,所以finditer的信息会更加完整。
举个例子:
x=re.findall('(\d )-(\d )-(\d )','131-1111-1111,132-2222-2222')
print('findall的结果,只有分组信息:',x)
findall在分组的时候,只能输出分组信息。
shape \* mergeformat
x = re.finditer('(\d )-(\d )-(\d )', '131-1111-1111,132-2222-2222')
num = 1
for i in x:
print(f'finditer第{num}个匹配结果:', i) #可以通过i.match()获得匹配的全部而内容,而不是只有分组内容。
print(f'finditer第{num}个匹配结果的分组信息:', i.groups())
num = 1
shape \* mergeformat
总结:
如果你只需要分组的信息,那么可以用findall。
如果你还想使用分组以外的内容,那么可以用finditer。
首先明确:替换是全部替换还是替换分组?
我们测试一下:
x = re.sub('(\d )-(\d )-(\d )','替换内容', '131-1111-1111,132-2222-2222')
print(x)
shape \* mergeformat
这里是把整个匹配结果替换了。
实际上repl参数是指的替换成的内容。可以是字符串,也可使函数。
如果是字符串,那么就直接是匹配结果全部替换。
如果是函数,就可以有目的的选替换内容。
比如上面,131-1111-1111我想把中间的号码改成****变成131-****-1111
def sub_func(match):
return match.group(1) "****" match.group(3) #将【组1 "****" 组2】返回
x = re.sub('(\d )-(\d )-(\d )', sub_func, '131-1111-1111,132-2222-2222')
print(x)
这样就实现了匹配内容的部分替代。
我们再看看分组在这里的重命名。
我们分别把(\d )-(\d )-(\d )的三个组的名字命名为group1、group2、group3
def sub_func(match):
return match.group('group1') "****" match.group('group3')
x = re.sub('(?p
print(x)
没有问题。所以sub里面(?p
之前在分组里面讲了,在正则表达式里面可以通过\n序号引用分组; 通过(?p=name)组名引用分组。
在sub函数里面,有一个独特的repl参数引用分组功能。比如\g
改写上面的案例。
x = re.sub('(?p
'\g
print(x)
shape \* mergeformat
是不是很方便、很强大?
功能与.sub(repl, string, count=0)几乎一模一样。比如调用8.5中的{banned}最佳后一个案例,只把sub改成subn。
x = re.sub('(?p
'\g
print(x)
改成subn。
x = re.subn('(?p
'\g
print(x)
shape \* mergeformat
sub的结果是字符串(替换后的内容),subn的结果除了一个字符串(替换后的内容),还有替换的次数。返回一个元组(字符串,替换次数)
前面提到过,pattern的flags属性。因为re模块的函数,也支持传入flasgs信息。
pattern.subn(repl, string, count=0)
re.subn(pattern, repl, string, count=0, flags=0)
所以两种写法没啥区别。
但是下面介绍两个re模块函数不能使用的参数,而pattern具备的属性。主要用于测试。
①pattern.groups 分组的数量
import re
a = re.compile('(?p
print(a.groups) # 3 #一共三个组
a = re.compile('(\d )-(\d )-(\d )')
print(a.groups) # 3 #一共三个组
a = re.compile('\d -\d -\d ')
print(a.groups) # 0 #没有分组
②pattern.groupindex 映射由(?p
import re
a = re.compile('(?p
print(a.groupindex) #只有这种形式可以返回。
a = re.compile('(\d )-(\d )-(\d )')
print(a.groupindex) # 序列组不能返回
a = re.compile('\d -\d -\d ')
print(a.groupindex) # 这里没有分组
上面两个属性,一般都是用于测试的,查看分组情况。当然也可以用于循环,分别输出分组的信息。避免分组不存在而报错。
③pattern.pattern 编译对象的原始样式字符串
import re
a = re.compile('(?p
print(a.pattern)
print(a)
请查看区别。
前面提到过。
.span() 返回匹配对象的切片位置。比如(1,3)
.start([group]) 返回结果的,开始位置。如果不指定group,就是group(0)也就是整个匹配字符串。
.end([group]) 返回结果的,结束位置。如果不指定group,就是group(0)也就是整个匹配字符串。
.match() 返回匹配对象的对应的字符串。
.group(数字或者分组名) 返回匹配对象的某一个分组的对应的字符串。
.groups() 返回匹配对象的所有分组的对应的字符串,则称一个字符串元组。
除了.findall()方法,其他的方法都可以直接或者间接得到re.match对象。
finditer()得到re.match对象组成的迭代器。
sub(){banned}最佳后的结果虽然是字符串。但是,我们可以在替换步骤,可以通过替换函数调用re.match对象。
所以re.match对象是正则表达式不得不提的内容。
这里对re.match对象的内容做一个补充。
可以将分组的信息组成一个字典。只能是命名组,不能是序号组。
a = re.match(r"(?p
a.groupdict() #{'first_name': 'malcolm', 'last_name': 'reynolds'}
a = re.match(r"(?p
a.groupdict() #{'first_name': 'malcolm'} #因为第二个组是序号组,不是命名组,所以不能加入字典。
听到替换,就想到了re.sub()实际上也是这个方法。
比如之前提到的sub()的例子。
x = re.sub('(?p
'\g
print(x)
我们这里改成finditer。
x = re.finditer('(?p
for i in x:
print(i.expand('\g
是不是效果差不多?所以我有理由相信,sub()实际上就是finditer()和match.expand(template) 结合的产物。
pattern.finditer()是检索,match.expand()是替换,pattern.sub()是检索和替换。
这样我们就从底层了解了各种函数的功能和作用。
之前提到过,分组的引用一般都是通过match.group(数字或者命名组的名字)
在python3.5之后,支持,通过__getitem__获取组。
也就是match[1]或者match["name"]这种形式。
原文: