正则表达式
参考这篇文章:https://www.jb51.net/tools/zhengze.html
更好理解。
导言:做一个电话号码输入识别的例子。
1 phone_num=input('请输入您的电话号码:') 2 if len(phone_num)==11 \ 3 and phone_num.isdigit() \ 4 and phone_num.startswith('13') \ 5 or phone_num.startswith('15') \ 6 or phone_num.startswith('18') \ 7 or phone_num.startswith('17'): 8 print('您输入电话号码正确') 9 else: 10 print('您输入电话号码有误')
利用目前所学知识已经能够做到识别输入的手机号码是否正确
正则表达式测试工具网站:http://tool.chinaz.com/regex/
或者这个:http://tool.oschina.net/regex/
正则书籍:《正则指引》
正则表达式和re模块的关系:
正则表达式是客观存在的,只不过在python这门语言中如果需要使用正则表达式就需要用到re模块
正则表达式本身也和python没有什么关系,就是匹配字符串内容的一种规则。
官方定义:正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
字符组 : [字符组]
在同一个位置可能出现的各种字符组成了一个字符组,在正则表达式中用[]表示。比如10是由1和0构成的两个字符组,其中1和0分别是一个字符组,然后每个字符组用中括号括号起来
字符分为很多类,比如数字、字母、标点等等。
假如你现在要求一个位置"只能出现一个数字",那么这个位置上的字符只能是0、1、2...9这10个数之一。
正则
|
待匹配字符
|
匹配 |
说明
|
[0123456789]
|
8
|
True
|
在一个字符组里枚举合法的所有字符,字符组里的任意一个字符 |
[0123456789]
|
a
|
False
|
由于字符组中没有"a"字符,所以不能匹配
|
[0-9]
|
7
|
True
|
也可以用-表示范围,[0-9]就和[0123456789]是一个意思
|
[a-z]
|
s
|
True
|
同样的如果要匹配所有的小写字母,直接用[a-z]就可以表示
|
[A-Z]
|
B
|
True
|
[A-Z]就表示所有的大写字母
|
[0-9a-fA-F]
|
e
|
True
|
可以匹配数字,大小写形式的a~f,用来验证十六进制字符
|
字符:
元字符 |
匹配内容 |
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
\b | 匹配一个单词的结尾 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结尾 |
\W |
匹配非字母或数字或下划线 |
\D |
匹配非数字 |
\S |
匹配非空白符 |
a|b |
匹配字符a或字符b |
() |
匹配括号内的表达式,也表示一个组 |
[...] |
匹配字符组中的字符 |
[^...] |
匹配除了字符组中字符的所有字符 |
量词:
量词 |
用法说明 |
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
*,+,?的匹配范围
规则:
在写正则表达式时:必须是先规则再量词,且量词只对紧跟其的规则有效。
* + ? { }
正则 | 待匹配字符 | 匹配 结果 |
说明 |
李.? | 李杰和李莲英和李二棍子 |
李杰 |
?表示重复零次或一次,即只匹配"李"后面一个任意字符 |
李.* | 李杰和李莲英和李二棍子 | 李杰和李莲英和李二棍子 |
*表示重复零次或多次,即匹配"李"后面0或多个任意字符 |
李.+ | 李杰和李莲英和李二棍子 | 李杰和李莲英和李二棍子 |
+表示重复一次或多次,即只匹配"李"后面1个或多个任意字符 |
李.{1,2} | 李杰和李莲英和李二棍子 |
李杰和 |
{1,2}匹配1到2次任意字符 |
注意:前面的*,+,?等都是贪婪匹配,也就是尽可能匹配,后面加?号使其变成惰性匹配。也就是说按照最下限进行匹配
正则 | 待匹配字符 | 匹配 结果 |
说明 |
李.*? | 李杰和李莲英和李二棍子 | 李 李 李 |
惰性匹配 |
示例:
字符集[][^]
正则 | 待匹配字符 | 匹配 结果 |
说明 |
李[杰莲英二棍子]* | 李杰和李莲英和李二棍子 |
李杰 |
表示匹配"李"字后面[杰莲英二棍子]的字符任意次 |
李[^和]* | 李杰和李莲英和李二棍子 |
李杰 |
表示匹配一个不是"和"的字符任意次 |
[\d] | 456bdha3 |
4 |
表示匹配任意一个数字,匹配到4个结果 |
[\d]+ | 456bdha3 |
456 |
表示匹配任意个数字,匹配到2个结果 |
特殊字符:
加入要匹配的字符串中含有\d,\s等特殊字符的时候就需要其前面加上r。比如:
import re ret=re.findall(r'\\d',r'\d')#被匹配的目标字符中含有\d则就需要在前面加上r,同时加上两个\\ print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py ['\\d'] Process finished with exit code 0
分组 ()与 或 |[^]
身份证号码是一个长度为15或18个字符的字符串,如果是15位则全部🈶️数字组成,首位不能为0;如果是18位,则前17位全部是数字,末位可能是数字或x,下面我们尝试用正则来表示:
正则 | 待匹配字符 | 匹配 结果 |
说明 |
^[1-9]\d{13,16}[0-9x]$ | 110101198001017032 |
110101198001017032 |
表示可以匹配一个正确的身份证号 |
^[1-9]\d{13,16}[0-9x]$ | 1101011980010170 |
1101011980010170 |
表示也可以匹配这串数字,但这并不是一个正确的身份证号码,它是一个16位的数字 |
^[1-9]\d{14}(\d{2}[0-9x])?$ | 1101011980010170 |
False |
现在不会匹配错误的身份证号了 |
^([1-9]\d{16}[0-9x]|[1-9]\d{14})$ | 110105199812067023 |
110105199812067023 |
表示先匹配[1-9]\d{16}[0-9x]如果没有匹配上就匹配[1-9]\d{14} |
()分组是对多个字符组整体量词的约束的时候使用
转义符 \
在正则表达式中,有很多有特殊意义的是元字符,比如\n和\s等,如果要在正则中匹配正常的"\n"而不是"换行符"就需要对"\"进行转义,变成'\\'。
在python中,无论是正则表达式,还是待匹配的内容,都是以字符串的形式出现的,在字符串中\也有特殊的含义,本身还需要转义。所以如果匹配一次"\n",字符串中要写成'\\n',那么正则里就要写成"\\\\n",这样就太麻烦了。这个时候我们就用到了r'\n'这个概念,此时的正则是r'\\n'就可以了。
正则 | 待匹配字符 | 匹配 结果 |
说明 |
\n | \n | False |
因为在正则表达式中\是有特殊意义的字符,所以要匹配\n本身,用表达式\n无法匹配 |
\\n | \n | True |
转义\之后变成\\,即可匹配 |
"\\\\n" | '\\n' | True |
如果在python中,字符串中的'\'也需要转义,所以每一个字符串'\'又需要转义一次 |
r'\\n' | r'\n' | True |
在字符串之前加r,让整个字符串不转义 |
贪婪匹配
贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配
正则 | 待匹配字符 | 匹配 结果 |
说明 |
<.*> |
<script>...<script> |
<script>...<script> |
默认为贪婪匹配模式,会匹配尽量长的字符串 |
<.*?> | r'\d' |
<script> |
加上?为将贪婪匹配模式转为非贪婪匹配模式,会匹配尽量短的字符串 |
几个常用的非贪婪匹配Pattern
*? 重复任意次,但尽可能少重复 +? 重复1次或更多次,但尽可能少重复 ?? 重复0次或1次,但尽可能少重复 {n,m}? 重复n到m次,但尽可能少重复 {n,}? 重复n次以上,但尽可能少重复
.*?的用法
. 是任意字符 * 是取 0 至 无限长度 ? 是非贪婪模式。 何在一起就是 取尽量少的任意字符,一般不会这么单独写,他大多用在: .*?x 就是取前面任意长度的字符,直到一个x出现
惰性匹配
量词后面加?
.*? abc 表示:在字符串中一直取,直到遇到abc就停止
re模块
re模块有findall、search、match三个重要方法:
findall
import re ret=re.findall('a','eva egon yuan') print(ret)
能够匹配出所有的a,结果是一个列表
search
import re ret=re.search('a','eva egon yuan') print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py <re.Match object; span=(2, 3), match='a'> Process finished with exit code 0
从前往后找到第一个就返回,返回的是一个结果的对象,
从前往后,找到一个就返回,返回的变量需要调用group才能得到结果
如果没有找到,那么返回None,调用group方法会报错
import re ret=re.search('a','eva egon yuan') print(ret.group()) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py a Process finished with exit code 0
所以,一般用法是:使用if
import re ret=re.search('j','eva egon yuan') if ret: print(ret.group())
match是从头开始匹配,如果正则规则从头开始能匹配上,就返回一个变量,
匹配的内容需要用group才能显示
如果没有匹配上,就返回None,调用group会报错
findall:找所有
search:找第一个
match:从头开始找一个
split:分割
import re ret=re.split('[ab]','abcd')#先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割 if ret: print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py ['', '', 'cd'] Process finished with exit code 0
sub
import re ret=re.sub('\d','h','eva3egon4yuan4')#利用正则规则将数字全部替换成h,与replace功能类似,但是repalace只能替换特定的字符,而正则规则则可以替换所有的数字 print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py evahegonhyuanh Process finished with exit code 0
也可以设定替换的次数
import re ret=re.sub('\d','h','eva3egon4yuan4',1)#只替换一次 print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py evahegon4yuan4 Process finished with exit code 0
subn
import re ret=re.subn('\d','h','eva3egon4yuan4')#不仅按照预定正则规则完成替换,还会返回替换的次数 print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py ('evahegonhyuanh', 3) Process finished with exit code 0
compile:编译
import re obj=re.compile('\d{3}')#将正则表达式编译成一个正则表达对象,规则是要匹配的3个对象 ret=obj.search('abs123dhkjhdls')#正则表达式对象调用search方法,参数为待匹配的字符串 print(ret.group()) ret=obj.search('absdhkjhd456ls3skjhlah') print(ret.group()) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py 123 456 Process finished with exit code 0
finditer
import re ret=re.finditer('\d','abs123dhkjhdls')#finditer返回一个匹配结果的迭代器 print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py <callable_iterator object at 0x0000023BD3BB5B38> Process finished with exit code 0
优化
import re ret=re.finditer('\d','abs123dhkjhdls45678')#finditer返回一个匹配结果的迭代器 print(next(ret).group())#查看第一个结果 print(next(ret).group())#查看第二个结果 print(next(ret).group())#查看第三个结果 print([i.group() for i in ret])#查看剩余的所有结果 D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py 1 2 3 ['4', '5', '6', '7', '8'] Process finished with exit code 0
import re ret=re.finditer('\d','abs123dhkjhdls45678')#finditer返回一个匹配结果的迭代器 # print(next(ret).group())#查看第一个结果 # print(next(ret).group())#查看第二个结果 # print(next(ret).group())#查看第三个结果 # print([i.group() for i in ret])#查看剩余的所有结果 for i in ret: print(i)#得到的依然是一个迭代器对象 D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py <re.Match object; span=(3, 4), match='1'> <re.Match object; span=(4, 5), match='2'> <re.Match object; span=(5, 6), match='3'> <re.Match object; span=(14, 15), match='4'> <re.Match object; span=(15, 16), match='5'> <re.Match object; span=(16, 17), match='6'> <re.Match object; span=(17, 18), match='7'> <re.Match object; span=(18, 19), match='8'> Process finished with exit code 0
需要加上group才能得到结果
import re ret=re.finditer('\d','abs123dhkjhdls45678')#finditer返回一个匹配结果的迭代器 # print(next(ret).group())#查看第一个结果 # print(next(ret).group())#查看第二个结果 # print(next(ret).group())#查看第三个结果 # print([i.group() for i in ret])#查看剩余的所有结果 for i in ret: print(i.group())#得到的依然是一个迭代器对象 D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py 1 2 3 4 5 6 7 8 Process finished with exit code 0
用法总结:
findall:找到所有结果
serach:找到一个
match:从开头就开始匹配
split:分割
sub:替换的时候用
finditer:迭代器,节省内存的时候使用
特殊用法
import re ret=re.search('^[1-9](\d{14})(\d{2}[0-9x])?$','612522195812251234')#以身份证号为例 print(ret.group()) print(ret.group(1))#取第一个分组的值,从第二个数字开始的14位数字,注意这是python中的特殊功能,与正则表达式无关, print(ret.group(2))#取第二个分组的值,后三位数字 D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py 612522195812251234 12522195812251 234 Process finished with exit code 0
search和match都有group方法,这也就是group方法的妙用
注意findall和split的分组优先功能
由于findall和split没有group这种特殊功能,所以在python中存在着分组优先的特殊功能
findall的分组优先
import re ret=re.findall('www.(baidu|oldboy).com','www.oldboy.com')#注意在findall中存在分组优先的功能 print(ret) ret=re.findall('www.(?:baidu|oldboy).com','www.oldboy.com')#当加上?:以后就能够取消分组优先,这也是?在正则中的第三个用处 print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py ['oldboy'] ['www.oldboy.com'] Process finished with exit code 0
split的分组优先
import re ret=re.split('\d+','eva3egon4yuan') print(ret)#结果['eva', 'egon', 'yuan'] ret=re.split('(\d)','eva3egon4yuan') print(ret)#结果['eva', '3', 'egon', '4', 'yuan']
#在匹配部分加上()之后所切出的结果是不同的, #没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项, #这个在某些需要保留匹配部分的使用过程是非常重要的。
re模块的可选值
flags有很多可选值: re.I(IGNORECASE)忽略大小写,括号内是完整的写法 re.M(MULTILINE)多行模式,改变^和$的行为 re.S(DOTALL)点可以匹配任意字符,包括换行符 re.L(LOCALE)做本地化识别的匹配,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境,不推荐使用 re.U(UNICODE) 使用\w \W \s \S \d \D使用取决于unicode定义的字符属性。在python3中默认使用该flag re.X(VERBOSE)冗长模式,该模式下pattern字符串可以是多行的,忽略空白字符,并可以添加注释
匹配标签
还可以在分组中利用?<name>的形式给分组起名字
获取的匹配结果可以直接用group('名字')拿到对应的值
import re ret=re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>") print(ret.group('tag_name')) print(ret.group()) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py h1 <h1>hello</h1> Process finished with exit code 0
如果不给组起名字,也可以用\序号来找到对应的组,表示要找的内容和前面的组内容一致
获取的匹配结果可以直接用group(序号)拿到对应的值
1 import re 2 ret=re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>") 3 print(ret.group(1)) 4 print(ret.group()) 5 6 D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py 7 h1 8 <h1>hello</h1> 9 10 Process finished with exit code 0
同理也可以这样
import re ret=re.search(r"<(\w+)>\w+(</\1>)","<h1>hello</h1>") print(ret.group(1)) print(ret.group()) print(ret.group(2)) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py h1 <h1>hello</h1> </h1> Process finished with exit code 0
匹配整数
import re ret=re.findall(r"\d+","1-2*(60+(-40.35/5)-(-4*3))") print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py ['1', '2', '60', '40', '35', '5', '4', '3'] Process finished with exit code 0
如上结果所示,小数被分开了。那么用或的关系式来取
import re ret=re.findall(r"\d+\.\d+|\d+","1-2*(60+(-40.35/5)-(-4*3))") print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py ['1', '2', '60', '40.35', '5', '4', '3'] Process finished with exit code 0
但是上面的代码也是不符合要求,因为按照要求,需要找出所有的整数
import re ret=re.findall(r"\d+\.\d+|(\d+)","1-2*(60+(-40.35/5)-(-4*3))") print(ret) ret.remove('')#通过这个例子也说明,使用remove方法后,改变了原变量ret本身 print(ret) D:\anoconda\python.exe F:/python/python学习/人工智能/第一阶段day2/练习.py ['1', '2', '60', '', '5', '4', '3'] ['1', '2', '60', '5', '4', '3'] Process finished with exit code 0
参考教程:https://germey.gitbooks.io/python3webspider/content/3.3-%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.html
计算器作业
实现能计算类似
1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )等类似公式的计算器程序
讲解在day26
参考课程:https://www.jb51.net/tools/zhengze.html
正则表达式实例:
示例1
import re html = '''<div id="songs-list"> <h2 class="title">经典老歌</h2> <p class="introduction"> 经典老歌列表 </p> <ul id="list" class="list-group"> <li data-view="2">一路上有你</li> <li data-view="7"> <a href="/2.mp3" singer="任贤齐">沧海一声笑</a> </li> <li data-view="4" class="active"> <a href="/3.mp3" singer="齐秦">往事随风</a> </li> <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li> <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li> <li data-view="5"> <a href="/6.mp3" singer="邓丽君"><i class="fa fa-user"></i>但愿人长久</a> </li> </ul> </div>''' html1=re.sub('<i class="fa fa-user"></i>','',html)#因为<i class="fa fa-user"></i>对后面的正则结果有干扰,所以首先在这里先替换掉它 result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>',html,re.S) results = re.findall('<li.*?singer="(.*?)">(.*?)</a>',html1,re.S) print(results)#得到最终结果,提取出演唱者和歌曲名字 #[('任贤齐', '沧海一声笑'), ('齐秦', '往事随风'), ('beyond', '光辉岁月'), ('陈慧琳', '记事本'), ('邓丽君', '但愿人长久')] #分别得到演唱者和歌曲名 for i in results: print(i[0],i[1])
利用sub实现
import re html = '''<div id="songs-list"> <h2 class="title">经典老歌</h2> <p class="introduction"> 经典老歌列表 </p> <ul id="list" class="list-group"> <li data-view="2">一路上有你</li> <li data-view="7"> <a href="/2.mp3" singer="任贤齐">沧海一声笑</a> </li> <li data-view="4" class="active"> <a href="/3.mp3" singer="齐秦">往事随风</a> </li> <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li> <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li> <li data-view="5"> <a href="/6.mp3" singer="邓丽君"><i class="fa fa-user"></i>但愿人长久</a> </li> </ul> </div>''' html1=re.sub('<a.*?>|</a>|<i.*?></i>','',html) print(html1) results=re.findall('<li.*?>(.*?)</li>',html1,re.S) print(results) for i in results: print(i.strip()) #得到结果: 一路上有你 沧海一声笑 往事随风 光辉岁月 记事本 但愿人长久