狮子的雄心,骆驼的耐力,孩子的执著!
分类: 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的用法和例子可以看看下面的例子。
正则表达式里\b和\B,Python实例_uvyoaa的专栏-CSDN博客_python正则表达式\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样式忽略。
上面的解释是官网文档上的解释。但是我们作为中文使用者,实际上会遇到一些坑。
案例:
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'<(.+)><(.+)>.+\2>\1>'
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"]这种形式。
原文: