目录
什么是正则表达式?
在日常生活中,我们经常会遇到设置密码的问题,系统需要判断我们设置的密码是否符合规定的要求;在微信聊天时,我们时常需要寻找含有某些关键词的聊天记录或者文件名称;在文档处理时,我们常常需要提取出符合某种特征的特定语句,显然,人工寻找是完全不现实的。在以上三个例子中,我们都会用到正则表达式。简而言之,正则表达式是一种判断字符串是否符合某种模式的表达式,它的用途非常广泛,我们可以用它来在文本中寻找并提取出符合某种模式的字符串。
例如,为了判断BNU21级数科新生的学号输入是否正确,我们可以提取出学号的正则表达式"20211113[0-9][0-9][0-9][0-9]",然后就能判断学号的输入是否正确了。
正则表达式入门
正则表达式的表达方式很简单,就是一个字符串。该字符串内由符号和内容组成,可以根据正则表达式的规则来生成你想要达到的字符串模式。例如可以用 "[1-9]\d*" (具体含义后面会写)来匹配所有的正整数。
下面总结一下正则表达式的匹配规律:
首先,无特殊含义的字符匹配本身,如"abc"匹配字符串"abc",这是最简单的匹配。但是实际运用中需要大量其他的条件,因此有了功能字符。
代词
顾名思义,用功能字符来代替某一类字符,从而简化了表达。
\d | 数字字符,即[0-9] |
\D | 非数字字符,即[^/d]或[^0-9] |
\s | 空白字符,如\t,\n,\r等 |
\S | 非空白字符 |
\w | 单词字符:汉字,大小写字母,数字,下划线均可 |
\W | 非单词字符 |
. | 除\n外的任何一个字符 |
量词
同样出于简化的目的,有的时候我们需要匹配多个重复的字符,例如6个数字字符,这时候如果把\d写6遍显然太复杂,所以引出了量词。其作用是控制量词左边的字符的重复次数。
注意,量词的作用对象仅限于量词左侧的字符。
* | 左边字符出现0或任意次 |
? | 左边字符出现0或1次 |
+ | 左边字符出现至少1次 |
{m} | 左边字符出现恰好m次 |
{m,n} | 左边字符出现至少m次至多n次,n可不写表示无上限 |
范围符号[XXX]
范围符号表示要匹配一个范围,属于该范围内的字符均能成功匹配。
[Ab3] | 匹配字符'A','b','3'之一 |
[5-9] | 匹配字符'5'-'9'之一 |
[a-d5-9] | 匹配字符'a'-'d','5'-'9'之一 |
[^a-z] | 匹配非(^)小写字母的字符 |
根据代词、量词和范围符号,我们就能理解为什么正整数的正则表达式是"[1-9]\d*"了
|:或
经典逻辑符号或与非在正则表达式里也有用到!(果然语言都是相通的)
简单举几个例子:
"[a-z]{3}|\d{3}" 匹配任意三个连续小写字母或者三个数字
"\w{3}a|b\d{2}|cd\w" 可以匹配:"abca","b12","cdg"等
需要注意的是,|会短路匹配,顺序是从左到右。
边界符号
除了对匹配的字符串本身有要求,有时候我们还希望匹配到的字符串的前后的字符也要满足特定条件,这时候我们就引入了边界符号来对字符串前后的字符做一定的限制。
\A | 左边界,要求字符串往左不能有字符 |
^ | 同\A,多行匹配模式下还可以表示一行文字的左边界 |
\Z | 右边界,要求字符串往右不能有字符 |
$ | 同\Z,多行匹配模式下还可以表示一行文字的左边界 |
\b | 表示此处应为单词的左/右边界,即不能为单词字符 |
\B | 表示此处不允许为单词的左/右边界,即必须是单词字符 |
?+符号
?的不仅仅具有量词的功能,还具有类似于边界符号的功能。
有时候我们需要对字符串的前/面进行限制,例如:我们要找放在数字中间的一串小写字母,也就是要求我们不仅仅要寻找一串小写字母,还要判断这串小写字母的前后是否都是数字。这时我们就需要正向/反向预查。
(?=pattern) | 正向肯定预查,表达式右边必须满足pattern |
(?!pattern) | 正向否定预查,表达式右边必不能满足pattern |
(?<=pattern) | 反向肯定预查,表达式左边必须满足pattern |
(?<!pattern) | 反向否定预查,表达式右边必不能满足pattern |
因此,要匹配放在数字中间的一串小写字母,正则表达式可写为:"(?<=\d)[a-z]+(?=\d)"
注意事项
如果要在正则表达式中表示特殊符号本身,则应该在其前面加'\'
正则表达式里的特殊符号很多,后面如果学到了会继续补充。
正则表达式的函数
首先,使用正则表达式要引用re库
import re
下面是使用正则表达式的几种常用函数
re.match函数
格式:re.match(pattern,string,flags)
如果匹配失败则返回None,匹配成功则返回匹配到的字符串
pattern是正则表达式,string是要匹配的字符串,flags是标志位
需要注意的是,re.match函数从string的起始位置开始匹配!
import re
x=re.match("[1-9]\d*","123abd")
if x!=None:
print(x.group())
else:
print("none")
y=re.match("[1-9]\d*","c123ad")
if y!=None:
print(y.group())
else:
print("none")
#输出结果:
123
none
re.search函数
格式:re.search(pattern,string,flags)
查找可匹配成功的字符串,返回(第一个)匹配对象或None
用group里包含匹配对象,span包含起止位置
import re
x = re.search("[1-9]\d*","asd234164jsj2341jkc")
if x != None:
print(x.group(),x.span()) # 输出子串及起止位置
else:
print("None")
#输出结果
234164 (3, 9)
re.findall函数
格式:re.findall(pattern,string,flags)
查找所有和pattern匹配的子串(不重叠),放入列表中
import re
lst = re.findall("[1-9]\d*","qw21313h1o58p4kjh8123jkh8435u")
for x in lst:
print(x,end=" ")
#输出结果:21313 1 58 4 8123 8435
分组
分组的基本性质
在正则表达式中加入括号可以对匹配到的字符串进行分组,目的是分离我们匹配到的字符串。多个分组左括号从左到右从1开始编号。
import re
m = "(([1-9])\d*)([a-z]{2})"
r = re.match(m,"3780qp")
if r !=None:
print(r.groups()) # >>('3780', '3', 'qp')
print(r.group(0)) # >>3780qp
print(r.group(1)) # >>3780
print(r.group(2)) # >>3
print(r.group(3)) # >>qp
#r.group(0)相当于r.group()
同时,分组还具有另外一种简化正则表达式的功能,即我们可以在正则表达式中引用分组本身。在分组的右边可以通过分组的编号引用该分组所匹配的子串。并且,我们可以将分组看作一个整体,在分组的后面可以加量词(此时量词的作用范围就是整个分组)。例如:
import re
m = r"(((ab*)c)d)e\3" #r 表示字符串里的'\'不再转义
#要求ab*cde后面跟着3号分组在本次匹配中匹配上的子串
r = re.match(m,"abbcdeabbkfg") #kfg前面少一个b则不能匹配
print(r.group(3)) #>>abb
print(r.group()) #>>abbcdeabb
分组与findall函数
在前面已经知道,没有分组时,re.findall 返回所有匹配子串构成的列表。如果有且只有一个分组 那么re.findall 返回的是一个子串的列表,每个元素是一个匹配子串中分组对应的内容。如果有超过一个分组时,re.findall返回的是一个元组的列表,每个元组对应于一个匹配的子串,元组里的元素依次是1 号分组、2 号分组、3 号分组......
后面的习题里会用到
贪婪模式和懒惰模式
贪婪模式,即量词+,*,?,{m,n}等默认匹配尽可能长的子串
如果在上述量词后加?则会匹配可能短的子串,即为懒惰模式
习题练习!
终于进入紧张刺激的实际演练环节了!
1.找出所有整数和小数
题目描述:给一段文字,可能有中文,把里面的所有非负整数和小数找出来,不需要去掉前导0或小数点后面多余的0, 然后依次输出。
输入:一段文字
输出:按顺序输出所有整数和小数,每个整数一行
分析:写出非负整数、小数的正则表达式,再套用对应的函数即可。
import re
m="\d+\.\d+|\d+"
while True:
try:
s = input()
lst = re.findall(m,s)
for x in lst:
print(x)
except:
break
2.找出小于100的整数
题目描述:有给定的两行输入,在每一行的输入中提取在[0,100)内的整数(不包括100)并依次输出。注意要排除负数。(题目不会包含00)
import re
m = r"(^|[^0-9-])(\d{1,2})([^0-9]|$)"
for i in range(2):
s = input()
lst = re.findall(m,s)
for x in lst:
// 在此处补充你的代码
样例输入:
12高兴-23大小256的数1234好啊24对的好0这个1这个2这个12这个134这个0123这个12
123高兴-23大小256的数1234好啊24对的23这
样例输出:
12
24
0
1
2
12
12
24
23
分析:100以内的整数,即是由两个数字字符组成的,并且左边和右边都不能出现整数。这道题已经给出了代码,实际上是用分组实现的。100以内的整数存储在第二组,也就是x[1]中,所以补充后的代码即为:
import re
m = r"(^|[^0-9-])(\d{1,2})([^0-9]|$)"
for i in range(2):
s = input()
lst = re.findall(m,s)
for x in lst:
print(x[1])
3 密码判断
题目描述:用户密码的格式是: 1) 以大写或小写字母开头 2) 至少要有8个字符,最长不限 3) 由字母、数字、下划线或 '-' 组成 输入若干字符串,判断是不是符合密码的条件。如果是,输出 yes 如果不是,输出 no
import re
// 在此处补充你的代码
while True:
try:
s = input()
if re.match(m,s) != None:
print("yes")
else:
print("no")
except:
break
输入:若干行
输出:对每行输入,判断其是否符合密码格式,相应地输出 yes 或no
分析:把密码的正则表达式写出来就行了
import re
m="^[a-zA-Z][a-zA-Z0-9_-]{7,}$"
#注意要添加边界符号
while True:
try:
s = input()
if re.match(m,s) != None:
print("yes")
else:
print("no")
except:
break
4 寻找h3
题目描述:程序填空,输出指定结果
import re
m = \
// 在此处补充你的代码
for x in re.findall(m,"cdef<h3>abd</h3><h3>bcK</h3><h3>123</h3>KJM"):
print(x)
输出:
abd
bcK
123
分析:即为找出<h3>与</h3>之间的部分,这里用到了正向肯定预查和反向肯定预查。即对我们要寻找的字符串,其前面必须是<h3> (?<=<h3>) 后面必须是</h3> (?=</h3>)
import re
m = \
r"(?<=<h3>)\w+(?=</h3>)"
for x in re.findall(m,"cdef<h3>abd</h3><h3>bcK</h3><h3>123</h3>KJM"):
print(x)
5 找<>中的数
题目描述:输入一串字符,将输入中的,在<>里面的,没有前导0的少于4位的整数依次输出。单独的0也要输出。
输入:第一行是整数n,表示后面一共有n个字符串。接下来有n行字符串
输出:对每个字符串,输出题目要求的结果
分析:写出对应的正则表达式,前面是<,后面是>,用到正向反向预查语句
正则表达式即为: r"(?<=<)(0{1}|[1-9]\d{0,2})(?=>)"
然后模拟一下整个过程即可
import re
m=r"(?<=<)(0{1}|[1-9]\d{0,2})(?=>)"
n=int(input())
for i in range(n):
x=input()
y=re.findall(m,x)
if y==[]:
print("NONE",end=" ")
else:
for o in y:
print(o,end=" ")
print("")
6 电话号码
题目描述:
输入:有多组数据,每组一行
输出:对每组数据, 抽取出其中的tag及其包含的电话号码中的区号输出。每个tag输出为一行。tag外的电话号码不用理会。如果找不到tag及其包含的电话号码, 则输出NONE。数据保证不会出现两个tag重叠的情况。
提示:
1) tag中间可以有任何文字,比如 <ab>xddd</cd></ab>也是一个合法tag
2) 在分组的右边可以通过分组的编号引用该分组所匹配的子串
m = r'(((ab*)c)d)e\3' #要求 ab*cde后面跟着第三分组的内容
r = re.match(m,"abbbcdeabbbkfg") # 后面的bbb少一个b则不能匹配,因为第三分组是abbb
print(r.group(3)) # abbb
print(r.group()) # abbbcdeabbb
3) 如果一个正则表达式搞不定,可以先用一个正则表达式抽取某个中间结果,再在中间结果里面手工或者用另外的正则表达式进一步分析
分析:题目相当于由两步,第一步是提取出所有tag,第二步是在tag里面找出电话号码。稍微有点麻烦的是要做好标记(以便于判断是否输出NONE)
这两步显然都需要用到正则表达式,所以可以先把对应的正则表达式写好。抽取tag显然要用到分组(因为要求两个X的内容相同)这道题用好分组也可以减省一些步骤。
实现过程会有点麻烦,可以多调试一下。
import re
m=r"(<([a-z]+)>.+?</\2>)" #tag
b=r"\((\d{1,2})\)-\d{3}(?=[^\d])" #电话号码
n=int(input())
for i in range(n):
s=input()
k=re.findall(m,s)#找到所有tag
flag=0
for x in k:
y=re.findall(b,x[0])
l=len(y)
if l !=0:
flag=1
print('<'+x[1]+'>'+y[0],end="")
for j in range(1,l,1):
print(','+y[j],end="")
print("</"+x[1]+'>')
if flag==0:
print("NONE")
以上就是正则表达式全部内容,后面也许还会有补充。