九、正则表达式

  正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配

  re模块使python语言拥有全部的正则表达式功能

  compile函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象,该对象拥有一系列方法用于正则表达式匹配和替换

  re.match函数

  re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none

re.match(pattern, string, flags = 0)
#pattern:匹配的正则表达式
#String:要匹配的字符串
#flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等

  匹配成功re.match方法返回一个匹配的对象,否则返回None

  我们可以使用group(num)或groups()匹配对象函数来获取匹配表达式

  group(num=0):匹配的整个表达式的字符串,group()可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组

  即match的用法是匹配以什么开头的字符串

import re
set = re.match('速度与激情','速度与激情8') #必须是开头匹配
a = set.group()
print(a)
#结果:速度与激情

 

    字符:.                                           功能: 匹配任意一个字符串(\n除外),即匹配除换行符之外的所有字符

import re
s = '123\n456\n789'
re.findall(r'.+',s) #匹配所有,至少有一个

#['123', '456', '789']

re.findall(r'.+',s,re.S)
#['123\n456\n789']

 

      ^and$                匹配字符串的开头和结尾

         []                                                      匹配[]列举的字符串,比如[abc123]表明’a‘,’b‘,’c‘,’1‘,’2‘,’3‘都符合他的要求,可以被匹配,如果在[ ]里面加^即取非,括号内的字符都不匹配,[^a-zA-Z]表示不匹配所有的英文字母;但是^不在开头,则它就不是取非,而是其本身,如[a-z^A-Z]表明匹配所有的英文字母和字符’^‘  

         \d                 匹配一位数的数字:0-9,匹配的是一位数字,等价于[0-9]

         \D                匹配非数字,等价于[^0-9]

         \s                 匹配空白,如空格,tab键,回车键等表示分隔意义的字符,等价于[ \t\n\f\v](注意前面有个空格)

         \S                 匹配非空白,等价于[^\t\n\f\v]

         \w                 匹配单词字符,各种语言文字,即匹配字母和数字,等价于[a-zA-Z0-9]

         \W                匹配非单词字符,等价于[^a-zA-Z0-9]

         \A                 匹配字符串开头,他和^的区别是,\A只匹配整个字符串的开头,即使在M模式下,他也不会匹配其他行的行首

         \Z                 匹配字符串的结尾,他和$的区别是,\Z只匹配整个字符串的结尾,即使在M模式下,他也不会匹配其他行的行尾

         \b               匹配单词边界,比如空格等,不过它是一个’0‘长度字节,它匹配完的字符串不会包括那个分界的字符,而如果用’\s‘来匹配的话,则匹配出的字符串中会包含那个分界符

 

s = 'abc abcde bc bcd'
re.findall(r'\bbc\b',s)
#['bc'],只匹配一个单独的单词’bc',而当它是其他单词的一部分的时候不匹配

re.findall(r'\bbc',s)
#['bc', 'bc']

re.findall(r'\babc\b',s)#说明最开始的abc前面有一个0字符
#['abc']

re.findall(r'bc\b',s)
#['bc', 'bc']

re.findall(r'\sbc\s',s)
#[' bc ']匹配一个单独的单词‘bc',只找到单独的那个’bc’,不过包含分界符,注意到前后各有一个空格,会把连同分隔符匹配出来

re.findall(r'\sbc',s)
#[' bc', ' bc']

 

        \B                 匹配非边界,和‘b’相反,它只匹配非边界的字符,它同样是0个长度字符

s = 'abc abcde bc bcd'
re.findall(r'\Bbc\w+',s)
#['bc', 'bc']

re.findall(r'\Bbc\B',s)
#['bc']

re.findall(r'\Bab\B',s)#说明第一个abc前面是有一个0字符的
#[]

       (?:)                   无捕获组,当你要将一部分规则作为一个整体对它进行某些操作,比如指定其重复次数时,你需要将这部分规则用(?:)把它包围起来,而不能仅仅只用一对括号

#匹配字符串中重复的‘ab’
s = 'ababab abbabb aabaab'
re.findall(r'\b(?:ab)+\b',s) #把ab作为一个整体,首先是首尾有分隔符,并且不匹配出来,其次,中间至少有一个ab
#['ababab']   

 

import re
ret1 = re.match(r'速度与激情\d','速度与激情88') #只匹配一个数字
a1 = ret1.group()
print(a1)
#速度与激情8

#'r'是防止字符转义的,如果路径中出现'\t'的话,不加r的话\t就能保留原有的样子
#在字符串赋值的时候,前面加'r'可以防止字符串在转义的时候不被转义
#在python的string前面加上'r',是为了告诉编译器这个String是个raw string,不要转义,例如\n在raw String中,是两个字符\和n,
#而不会转义为换行符,由于正则表达式和\会有冲突,因此,当一个字符串使用了正则表达式后,最好在前面加上'r'

ret2 = re.match(r'速度与激情[12345678]','速度与激情88') #后面加*是88,*:可有可无,说明是捆绑的
a2 = ret2.group()
print(a2)
#速度与激情8
ret2 = re.match(r'速度与*','速度与激情88') #因为你*前面的什么都不是,并没有捆绑
a2 = ret2.group()
print(a2)
#速度与
ret3 = re.match(r'速度与激情[1-8]','速度与激情88')
a3 = ret3.group()
print(a3)
#速度与激情8

ret4 = re.match(r'速度与激情[1-36-8]','速度与激情88')
a4 = ret4.group()
print(a4)
#速度与激情8

ret5 = re.match(r'速度与激情.','速度与激情88') #.:匹配任意一个字符串,无论前面是什么,什么都不是也可以,并没有捆绑
a5 = ret5.group()
print(a5)
#速度与激情8

ret6 = re.match(r'速度与激情.','速度与激情aa')
a6 = ret6.group()
print(a6)
#速度与激情a

ret7 = re.match(r'速度与激情.','速度与激情-')
a7 = ret7.group()
print(a7)
#速度与激情-

  匹配多个字符串:

    字符:*                 功能:匹配前一个字符出现0次或者无限次,即可有可无,匹配前面的规则0次或多次

         +                    匹配前一个字符出现1次或者无限次,即至少出现1次,匹配前面的规则至少一次,可以多次匹配

s = 'aaa bbb111 cc22cc 33dd'
re.findall(r'\b[a-z]+\d*\b',s) #空格或0字符,至少一个字母开头,数字可有可无,空格结尾,是数字可有可无还是有了数字之后后面的数字可有可无?说明+、*是和前面的是捆绑在一起的
#['aaa', 'bbb111']

#注意到上例中规则前后加了表示单词边界的\b指示符,如果不加的话就会变成:
re.findall(r'[a-z]+\d*',s)
#变成了全局查找

        ?                   匹配前一个字符出现0次或者出现1次,即最多出现1次,即只匹配前面的规则1次或0次

s = '123 10e3 20e4e4 30ee5'
re.findall(r'\b\d+[eE]?\d*\b',s) #说明?也是和前面的捆绑在一起的,前面的可有可无最多有一个
#['123', '10e3'] 
#注意前后的\b的使用,否则得不到期望的结果

 

          {m}                  匹配前一个字符要出现m次,精确匹配m次

        {m.n}                    匹配前一个字符出现的次数可以在m到n之间,匹配最少m次,最多n次。如果你只想指定一个最少次数或最大次数,你也可以把另一个参数空起来,最少次数为3:{3,},同时只想要最大次数为5:{,5},也可以写成{0,5}

s = '1 22 333 4444 55555 666666'
re.findall(r'\b\d{3}\b',s) #也是捆绑,必须要有三个数字,且前后为分界符
#['333']

re.findall(r'\b\d{2,4}\b',s) #首尾都包含
#['22', '333', '4444']

re.findall(r'\b\d{5,}\b',s)
#['55555', '666666']

re.findall(r'\b\d{1,4}\b',s)
#['1', '22', '333', '4444']

 

import re  

rat1 = re.match(r'速度与激情\d*','速度与激情')
a1 = ret1.group()
print(a1)
#速度与激情

rat2 = re.match(r'速度与激情\d*','速度与激情1')
a2 = rat2.group()
print(a2)
#速度与激情1

rat3 = re.match(r'速度与激情\d*','速度与激情1111122')
a3 = rat3.group()
print(a3)
#速度与激情1111122

#rat4 = re.match(r'速度与激情\d+','速度与激情') #至少有一个数字
#a4 = rat4.group()
#print(a4)
#报错

rat5 = re.match(r'速度与激情\d+','速度与激情1') 
a5 = rat5.group()
print(a5)
#速度与激情1

rat6 = re.match(r'速度与激情\d+','速度与激情11122') #即匹配前一个类型的东西,至少是一个
a6 = rat6.group()
print(a6)
#速度与激情11122

rat7 = re.match(r'速度与激情\d?','速度与激情')#最多是一次,多了不匹配就好了,0次或1次,是和前面的捆绑在一起的
a7 = rat7.group()
print(a7)
#速度与激情

rat8 = re.match(r'速度与激情\d?','速度与激情1')
a8 = rat8.group()
print(a8)
#速度与激情1

rat9 = re.match(r'速度与激情\d?','速度与激情11')
a9 = rat9.group()
print(a9)
#速度与激情1

rat10 = re.match(r'速度与激情\d{1}','速度与激情1')
a10 = rat9.group()
print(a10)
#速度与激情1

 rat13 = re.match(r'速度与激情\d{1}','速度与激情11') 
 a13 = rat11.group()
 print(a13)
  #速度与激情1

#rat11 = re.match(r'速度与激情\d{4}','速度与激情11')#可以少但是不能多
#a11 = rat11.group()
#print(a11)
#报错

rat12 = re.match(r'速度与激情\d{2,3}','速度与激情111')
a12 = rat12.group()
print(a12)
#速度与激情111

 

  其他符号:符号: ^                      功能:从字符串的开头开始匹配,match函数默认从头开始匹配

             $                       字符串必须以前一个字符结尾

           |                       匹配左右任意一个表达式,即将两个规则并列起来,表示只要满足其中之一就可以匹配,比如

[a-zA-Z] | [0,9] 表示满足数字或者字母就可以匹配,这个规则等价于[a-zA-Z0-9],注意:关于 | 的两点:

  1.它在 [] 中不在表示或,而表示他自身本身,如果在 [] 外表示一个 | 字符,必须用反斜杠引导,即'/| '

  2.它的有效范围是它两边的整条规则,比如‘dog|cat’匹配的是‘dog’和‘cat’,而不是‘g’和‘c’,如果想要限定他的有效范围,必须使用一个无捕获组'(?:)’包起来,比如要匹配‘I have a dog'或'I hava a cat',需要写成r'I have a (?:dog|cat),而不能写成r'I have a dog|cat'

s = 'I have a dog,I have a cat'
re.findall(r'I have a (?:dog|cat)',s)
#['I have a dog', 'I have a cat']
#等价于
re.findall(r'(?:I have a dog|I have a cat)',s) 
#['I have a dog', 'I have a cat']
#等价于
re.findall(r'I have a dog|I have a cat',s)

#不用无捕获组:
re.findall(r'I have a dog|cat',s)
#['I have a dog', 'cat']

 

         (ab)                    将括号中的字符作为一个分组

         \num                     引用分组num匹配到的字符

       (?p<name>)                     分组起别名                 

       (?p =name)                    引用别名为name的分组匹配到的字符,调用已匹配的组,也就是说它里面的内容和前面命名组里的内容是一样的

s = 'aaa111aaa,bbb222,333ccc,444ddd444,555eee666,fff777ggg'
#找出中间夹有数字的字母,只显示括号里面的
re.findall(r'([a-z]+)\d+([a-z]+)',s)
#[('aaa', 'aaa'), ('fff', 'ggg')]

#找出被中间夹有数字的前后同样的字母
re.findall(r'(?P<gl>[a-z]+)\d+(?P=gl)',s)
#['aaa']

#找出前面有字母引导,中间是数字,后面是字母的字符串中的中间的数字和后面的字母
re.findall(r'[a-z]+(\d+)([a-z]+)',s)
#[('111', 'aaa'), ('777', 'ggg')]

 

#找出完全对称的数字和字母
#\1:匹配第一组括号;#\2:匹配第二组括号;#\3:匹配第三组括号
s='111aaa222aaa111 , 333bbb444bb33'
re.findall(r'(\d+)([a-z]+)(\d+)(\2)(\1)',s)

 

import re
#必须以数字为结尾
re.match(r'速度与激情\d$','速度与激情a').group()
#报错

re.match(r'速度与激情\d$','速度与激情1').group()
#速度与激情1

#re.match(r'速度与激情\d$','速度与激情11')
#报错

#分别取出字符串'速度与激情22aa'中的数字和小写字母
re.match(r'速度与激情(\d*)([a-z]*)','速度与激情22aa').group(1)
re.match(r'速度与激情(\d*)([a-z]*))','速度与激情22aa').group(2)

re.match(r'速度与激情(?P<num>\d*)(?P<str>[a-z]*)','速度与激情22aa').group(1)
re.match(r'速度与激情(?P<num>\d*)(?P<str>[a-z]*)','速度与激情22aa').group(2)#注意:外面在不需要写括号
re.match(r'速度与激情(?P<num>\d*)(?P<str>[a-z]*)','速度与激情22aa').group()#'速度与激情22aa'

#判断字符串是否对称
re.match(r'(\w){1}a{1}\1','1a1').group() #1a1
re.match(r'(?P<num>\w){1}a{1}(?P=num)','1a1').group()#1a1

#正则表达式中的三组括号把匹配结果分成三组
#group()同group(0)就是匹配正则表达式整体结果
#group(1)列出第一个括号匹配部分,group(2)列出第二个括号匹配部分,group(3)列出第三个括号匹配内容
#group(num=0)匹配的整个表达式的字符串,group()可以一次输入多个组号,在这种情况下,它将返回一个包含那些组所所对应值的元组
#groups()返回一个包含所有小组字符串的元组,从1到所含的组号

  findall查找所有符合条件的字符

import re
s = 'abc123abc456abc879' #没有\b的话是不管是不是首位的
re.findall(r'abc',s) 
#['abc', 'abc', 'abc']

 

#取出字符串中的所有数字,不用写group
re.findall(r'\d+','阅读量:1110,点赞数:12332')
#['1110', '12332'],说明findall能够查询所有符合条件的字符

 

   re模块的基本函数

  使用compile加速:直接使用findall(rule,target)的方式来匹配字符串,一次两次没什么,如果是多次使用,由于正则引擎每次都要把规则解释一遍,而规则的解释又是非常费

时间的,所以效率会很低,如果要多次使用统一规则来匹配的话,可以使用complie函数来将规则预编译,使用编译过返回的Regular Expression Object或叫做pattern对象来进

行查找

s = '111,222,aaa,bbb,ccc333,444ddd'
rule = r'\b\d+\b'
compiled_rule = re.compile(rule)
compiled_rule.findall(s)
#['111', '222']

  I:忽略大小写,用法:re.I

  M:MULTILINE多行匹配,在这个模式下^(代表字符串开头)和$(代表字符串结尾)将能够匹配多行的情况,成为行首和行尾标志,用法:re.M

s='123456\n789012\n345 678’
#匹配一个位于开头的数字,没有使用M选项
rc = re.compile(r'^\d+') 
rc.findall(s)
#['123']

#使用M选项
rcm = re.compile(r'^\d+',re.M)
rcm.findall(s)
#['123', '789', '345']  

rc = re.compile(r'\d+$')
rcm = re.compiler(r'\d+$',re.M)
rc.findall(s)
#['678']

rcm.findall(s)
#['456', '012', '678']

 

  match 和 search

  findall虽然很直观,但是在进行更复杂的操作时,就有些力不从心了,此时更多的使用的是match和search函数,他们的参数和findall是一样的,都是:

  match(rule, targetString[,flag])

  search(rule, targetString[,flag])

  需要注意的是,如果匹配不成功,它们则返回一个NoneType,所以在对匹配完的结果进行操作之前,你必须先判断是否成功了

  这两个函数唯一的区别是:match从字符串的开头开始匹配,如果开头位置没有匹配成功,就算失败了;而search会跳过开头,继续向后寻找是否有匹配的字符串

s = 'Tom:9527, Sharry:0003'
m = re.match(r'(?P<name>\w+):(?P<num>\d+)',s)
m.group()
#'Tom:9527'
m.groups()
#('Tom', '9527')
m.group('name')
#'Tom'
m.group('num')
#9527

  search从左往右搜索第一个符合条件的字符至不再符合条件的字符

import re
#取出字符串中的数字
re.search(r'\d+','阅读量:1110,点赞数:12332').group()
#1110
#可见search只能找出从左往右第一个符合条件的字符,直到条件符合结束

 

  finditer

  返回一个迭代器,finditer函数和findall函数的区别是,findall返回所有匹配的字符串,并存为一个列表,而finditer则并不直接返回这些字符串,而是返回一个迭代器

s = '111 222 333 444'
for i in re.finditer(r'\d+',s):
    print(i.group(),i.span())
#111 (0, 3)
#222 (4, 7)
#333 (8, 11)
#444 (12, 15)

  字符串的替换和修改:sub和subn

  sub返回一个被替换的字符串

  sub替换匹配的字符

  sub返回一个被替换的字符串

import re
#代码1
re.sub(r'\d+','10000','阅读量:1110')
#'阅读量:10000'

#代码2
re.sub(r'\d+','10000','阅读量:1110,点赞数:12332')
#'阅读量:10000,点赞数:10000'

#代码3
def add(temp):
    num = int(temp.group())+1
    str1 = str(num)
    return str1
re.sub(r'\d+',add,'阅读量:1110')
#'阅读量:1111'

  subn返回一个元组,第一个元素是被替换的字符串,第二个元素是一个数字,表明产生了多次替换

s='I have a dog, you have a dog, he have a dog'
re.sub(r'dog','cat',s)
#'I have a cat, you have a cat, he have a cat'

#如果我们指向替换前面两个
re.sub(r'dog','cat',s,2)
#'I have a cat, you have a cat, he have a dog'

#我们想知道发生了多少次替换,可以使用subn
re.subn(r'dog','cat',s)
#('I have a cat, you have a cat, he have a cat', 3)

re.subn(r'dog','cat',s,2)
#('I have a cat, you have a cat, he have a dog', 2)

  split切片函数,使用指定的正则规则在目标字符串中查找匹配的字符串,用它们作为分界,把字符串切片

#按照逗号分隔,同时去掉,两边的空格
s='  I have a dog  ,  you have a dog  ,  he have a dog'
re.split('\s*,\s*',s)
#['  I have a dog', 'you have a dog', 'he have a dog']

  split根据匹配进行切割字符串,并返回一个列表,此处应该是按照冒号拆分

import re 
re.split(r':| ','阅读量:1110 点赞数:11111')#注意:|后面要加空格,如果不加空格那么就变成个逐字拆分
#['阅读量', '1110', '点赞数', '11111']

 

  组与match对象

p = re.compile(r'(?P<name>[a-z]+)\s+(?P<age>\d+)\s+(?P<tel>\d+).*',re.I)
p.groupindex          
#mappingproxy({'name': 1, 'age': 2, 'tel': 3})

s = 'Tom 24 88888888 <='
m = p.search(s)
m.groups() #看看匹配的各组的情况
#('Tom', '24', '88888888')

m.group('name') #使用组名获取匹配的字符串
#'Tom'

m.group(1) #使用组序号获取匹配的字符串,同使用组名的效果一样
#'Tom'

m.group(0) #与m.group()效果相同
#'Tom 24 88888888 <='

  match对象的方法

  group(inidex(id)):获取匹配的组,缺省返回组0,也就是全部值

  groups():返回全部的组

  groupdict():返回组名为key,匹配内容为values的字典

  

  几个案例

    检查用户输入的是否为手机号码,手机号码为11位数字

import re
a = input('请输入手机号码:')
ret = re.match('\d{11}$',a) #$表示结束
if ret:
    print('输入的手机号码%s符合规范'%a)
else:
    print('输入的手机号码%s不符合规范'%a)

  检查一个字符是否符合作为变量名的标准,变量名只能由下划线(__)、字母、数字组成,第一个字符不能是数字  

import re
names = ['asd','_asd','3_d','d_3','a!a','aa#']
for name in names:
    ret = re.match('[a-zA-Z_][a-zA-Z0-9_]*$',name)#表示的是以大小写或下划线开头,只接受大小写字母、数字和下划线
    if ret:
        print('字符串%s可以作为变量名'%name)
    else:
        print('字符串%s不可以作为变量名'%name)

  检查用户输入的是否为163邮箱,给定的条件是@前面只能由数字或者字母组成,且字符量在5-20之间

import re
a = input('请输入一个邮箱:')
ret = re.match('[a-zA-Z0-9]{5,20}@163.com$',a) #$表示结束
if ret:
    print('输入的邮箱%s是一个规范的163邮箱'%a)
else:
    print('输入的邮箱%s不是一个规范的163邮箱'%a)

  

 

  

posted @ 2019-11-18 11:48  瞧我这个笨脑袋  阅读(260)  评论(0)    收藏  举报