正则表达式

参考这篇文章: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
5
6
3

表示匹配任意一个数字,匹配到4个结果
[\d]+ 456bdha3

456
3

表示匹配任意个数字,匹配到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

现在不会匹配错误的身份证号了
()表示分组,将\d{2}[0-9x]分成一组,就可以整体约束他们出现的次数为0-1次
^([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>
<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())


#得到结果:
一路上有你
沧海一声笑
往事随风
光辉岁月
记事本
但愿人长久

 

 

 

 

 

 

 

 

posted @ 2019-02-16 22:38  舒畅123  阅读(173)  评论(0编辑  收藏  举报