Python之正则表达式

正则表达式的作用

正则表达式是专门用于对字符串进行匹配处理的。

引子

例如有如下需求:

有如下的字符串: s = 'hello world' 将字符串中的'w' 和 'l' 之间的字符找出来。

如果使用Python的字符串的方法进行匹配,可能有些困难,因为对于字符串的方法来说,字符串提供的匹配是完全匹配。而我们的需求是进行的模糊匹配,所以这就需要用到另一种方法对字符串进行处理---正则表达式。

使用正则表达式处理上述的需求:

import re

ret = re.findall('w\w{2}l',s)  # 将s字符串以前面参数的规则进行匹配
print(ret)  # ['worl']
复制代码

正则表达式概念:

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

Python中使用正则表达式

在Python中如果要使用正则表达式对字符串进行匹配,就需要引用一个're'模块。这里面提供了正则表达式所需要用到的的一些方法。在正则表达式中,使用的规则一般是将某个字符串按照某个规则进行匹配,而这个规则需要我们自己定义,这就需要引入一个概念---元字符,元字符就是专门用来编写匹配规则的。

元字符

在正则表达式中一般会使用到11个元字符,分别如下:. ^ $ * + ? { } [ ] | ( ) \,下面就简单介绍下这11个元字符。

'.' 通配符

'.'代表的是除了'\n'的任意一个字符。

还是按前面的需求进行匹配,我们可以做如下的处理:

import re
ret = re.findall('w..l', s)
print(ret)  # ['worl']
复制代码

因为 '.'是匹配任何的字符(除开换行符),所以在'w'和'l'之间的任意两个字符都能进行匹配,所以打印的结果如下:['worl']。

验证换行符

print(re.findall('w..l', 'wh\nlsss'))  # [] 所以不能匹配换行符
复制代码

因此 '.' 只能去匹配除了换行符(\n)的任意一个字符。

'^' 尖角符

尖角符代表的意思是,从一个字符串的开始位置进行匹配。除了这个作用外,还有其他的功能,需要和其他的元字符进行结合使用。

print(re.findall('^h...o', 'hdaslheelo'))  # [] 
# h字符是在字符串的开始位置,在h字符后面的三个字符并没有找到o字符,所以匹配不成功,打印的是空列表
print(re.findall('^h...o', 'hdasolheelo'))  # ['hdaso']
# 匹配成功,是因为在h字符后面三个字符找到了字符o,所以能匹配成功。
复制代码
'$' 美元符

'$'的作用和'^'是相反的,是从一个字符串的结尾的位置开始匹配。

print(re.findall('h...o$', 'hdaslheelo'))  # ['heelo']
# 从字符o开始查找,往前三个字符进行查找,找到了字符h,所以将找到的结果进行返回。
复制代码
* + ? { } 重复匹配 -> 默认是贪婪匹配

这几个的作用类似,就放在一起进行说明。

'*' 重复匹配

先看例子:

print(re.findall('w*as', 'wangjunwwwwasdaswasasgjun'))  # ['wwwwas', 'as', 'was', 'as']
# 通过以上的例子可以看到的返回值,有的是有很多个'w',有的是没有的,这就是因为'*'进行匹配的时候是从[0, +∞)进行匹配的,
复制代码

也就是说对字符w进行[0, +∞)次数的匹配,所以返回的结果是['wwwwas'(匹配了4次), 'as'(匹配了0次), 'was'(匹配了1次), 'as'(匹配了0次)]

'+' 重复匹配

'+'的作用和'*'类似,都是重复匹配,但是匹配的次数不一样,为[1, +∞)次。

print(re.findall('w+as', 'wangjunwwwwasdaswasasgjun'))  # ['wwwwas', 'was']
# 由于是[1, +∞)次的匹配,所以匹配0次的就被剔除了。
复制代码
'?' 0/1次匹配

'?'匹配的时候,要么进行一次匹配,要么就不进行匹配。

print(re.findall('w?as', 'wangjunwwwwasdaswasasgjun'))  # ['was', 'as', 'was', 'as']
# 由于是[0, 1]次的匹配,所以匹配4次的就被剔除了,就保留了1次或者0次的。
复制代码
'{}' 重复匹配

这种匹配的方式就可以自定义了,可以选择重复几次或者选择一个区间进行重复。

print(re.findall('a{3}d','aaadsas'))  # ['aaad']
# 大括号中的重复匹配次数是自定义的3次
# print(re.findall('a{1,3}d', 'aadssadasdadsfsdaaad'))  # ['aad', 'ad', 'ad', 'aaad']
# 匹配次数为1~3次,且不能有空格
# 如果是{1,} 这代表的是 1~+∞匹配
复制代码

总结:以上的四种元字符都是表示重复匹配,而且'* + ?'还可以用'{}'进行表达:

* = {0,+∞}

+ = {1,+∞}

? = {0,1}
复制代码

在使用以上的元字符进行匹配的时候,都是按照最多的字符情况进行匹配,这就是贪婪匹配。如果当我们的需求恰好不需要进行那么多的匹配的时候,我们可以使用惰性匹配

# 类似的这种情况,我们可能不需要匹配那么多的'c'字符,尽可能少的匹配。
print(re.search('abc*', 'abcccdcccabcccc').group())   # abccc
print(re.search('abc*?', 'abcccdcccabcccc').group())  # ab
# 最少的适配是从0次开始的,所以是ab

print(re.search('abc+', 'abcccdcccabcccc').group())   # abccc
print(re.search('abc+?', 'abcccdcccabcccc').group())  # abc
# 最少的适配是从1次开始的,所以是abc

print(re.search('abc?', 'abcccdcccabcccc').group())   # abc
print(re.search('abc??', 'abcccdcccabcccc').group())  # ab
# ? 代表的是0次或者是1次,所以再添加一个?就代表适配0次,也就是ab

print(re.search('abc{1,3}', 'abcccdcccabcccc').group())  # abccc
print(re.search('abc{1,3}?', 'abcccdcccabcccc').group())  # abc
# {1,3}最少适配1次,也就是abc

# 所以在默认的情况下,所有的重复匹配都是贪婪匹配的,如果有修改就要在后面添加上一个'?'就可以改成惰性匹配。
复制代码

以上就是重复匹配的所有元字符,下面就是剩下的一些元字符。

[] | () \
[] 字符集

字符集和Python中的列表并不是一个东西,这点需要区分。

在'[ ]'中的所有的字符都是只能选择一个进行匹配,也就是相当于在一个列表中的一堆数据中去选择一个字符去匹配。

print(re.findall('a[b,s]cd','abcd')) # ['abcd'] 
# 注意的是这里的',' 也是一个字符(并不是分隔符的意思), 就把逗号当做普通字符进行处理就可以了。

# 适配所有的小写字母
# print(re.findall('[a-z]', 'asda')) # ['a', 's', 'd', 'a']  '[a-z]' 表示的是a~z的所有字母的集合
复制代码

字符集除了有上述的功能之外,还有一些特殊的功能,也就是将一些元字符的特殊功能取消掉。

print(re.findall('[w,*]', 'ww3*')) # ['w', 'w', '*'] 这些元字符都可以匹配出来 
# 当然也有特殊情况 ('\', '^', '-' 这三种特殊的不能匹配出来)
复制代码

上面讲到了'^'号的作用就是从一个字符串的开始位置进行匹配,这里和字符集结合的话有其他的作用----就是匹配除了这个字符以外的所有字符。

# 例子
print(re.findall('[^t]', 'asdaetwqrt')) # ['a', 's', 'd', 'a', 'e', 'w', 'q', 'r']
# 尖角号是除了t全部进行匹配
复制代码
\

' \ '后面如果跟随着特殊字符,这会将特殊字符的特殊意义取消掉;如果后面跟随着普通的字符,那就将这些普通字符赋值成有特殊意义的字符(个别的普通字符)

# 例如
# \d 匹配的是任何十进制的数字   -> [0-9]
# \D 匹配的是任何非数字字符     -> [^0-9]
# \s 匹配的是任何空白字符       -> [\t\r\n\f\v]
# \S 匹配的是任何的非空白字符   -> [^\t\r\n\f\v]
# \w 匹配的是任何字母数字字符   -> [0-9a-zA-Z]
# \W 匹配的是任何非字母数字字符 -> [^0-9a-zA-Z]
# \b 匹配的是与一个特殊字符的边界,也就是一个单词和特殊字符之间的位置
复制代码
普通字符 -> 有特殊意义的字符

十进制的数字:

如果要去匹配一些限定位数的数字的话,就可以用'\d'代表数字,再加上重复匹配的就可以完成。

print(re.findall('\d{11}', 'asd15911115527ss15981111127asfsg'))      # ['15911115527', '15981111127']
# 这个例子中需要注意的是:
# 假如有一个匹配成功了,下次匹配就会从上次匹配成功的位置继续匹配,而不会从头开始进行匹配
复制代码

任何的空白字符:

print(re.findall('\s', 'asd1s25527 asfsg'))   # [' ']
复制代码

任何的字母或者数字字符:

print(re.findall('\w', 'as27 asfsg'))         # ['a', 's', '2', '7', 'a', 's', 'f', 's', 'g']
# 这里将空白进行剔除了,然后就剩下的所有字符返回。
复制代码

任何特殊字符的边界:

print(re.findall(r'I\b', 'I am a student LI$T'))  # ['I', 'I']  空格和 $ 都是特殊字符
# 这第一个I和空白是有边界的,所以就可以能被找到,第二个'$'也是一个特殊字符,所以第二个I也是能被找到的。
# 如果将第二个'$'更改掉:
print(re.findall(r'I\b', 'I am a student LIST'))  # ['I']  
print(re.findall(r'\bblow', 'blow'))              # ['blow'] 
# 因为空格也是一个特殊的字符
复制代码

所以这个\b就代表的是特殊字符和普通字符之间的位置。

这里的正则表达式前面,添加了一个r,原因如下: 由于\b在Python解释器中有特殊的意义,所以需要进行转义,所以要使用到re模块进行解释,所以要加上一个r。

特殊字符 -> 普通字符

比如说需要匹配一个'.',但是'.'是有特殊意义的。这样就需要用到''进行转义。

print(re.search('a.', 'adfs').group())       # ad
# 找到的是ad
# print(re.search('a\.', 'adfs').group()) 报错
# 因为没有找到对应的结果,所以对象调用group()方法会报错。
# print(re.search('a\.', 'a.dfs').group())   # a.
# 将'.'的匹配任意一个字符的作用取消掉了。
复制代码

对于'\'的处理

我们知道在Python解释器中,' \ '是有特殊的意义的,而在正则表达式re模块中,也是有特殊意义的,所以针对''的处理就要特别小心。

我们如果有其他语言的编程基础的话,对''进行转义的话,我们首先想到的是转成'\',下面做个试验:

print(re.findall('\\', 'asdaD\pd'))        # 这是会报错的。
复制代码

如果我们使用'\\'进行转义。

print(re.findall('\\\\', 'asdaD\pd'))      # ['\\']
# 打印的是['\\'] ,这就是Python解释器对'\'进行转义的打印值,这也就是我们想要的结果。
复制代码

为啥是需要4个' \ '进行转义,细想下其实可以理解的。

首先在Python解释器中,' \ '是有特殊的意义的,对于Python解释器来说,这就是需要转义了,于是就转成了' \ \ ',由于又引用了re模块,而' \ '在re模块中也是有特殊意义的,所以这就需要再次转义,所以就转成了'\\'。

对上述的例子做个修改:

print(re.findall(r'\\', 'asdaD\pd'))        # ['\\']
# 依然有效
复制代码

这是由于没有让Python解释器转义,而是直接告诉了re模块,正则表达式直接去进行转义,所以就不需要' \ \ \ \ '了,而进行一层转义就可以了。

() 分组

分组就是将几个字符分组成一个整体

print(re.findall('(as)+', 'sasdfsfsas'))     # ['as', 'as']
# 这个as是一个整体去匹配。
复制代码
| 或
print(re.findall('\+|-', '123+33-4'))        # ['+', '-']
# 将字符串中的'+'和'-'找到了。
复制代码

正则表达式中几个方法

findall()

上面的例子就提到了这个方法,也就是将所有的元素找到,并且返回到列表中进行展示。

这里还有些注意的地方:

print(re.findall('www.(\w+).com', 'www.baidu.com'))       # ['baidu']
# 注意
# findall函数,处理的时候是将匹配的结果放在一个组里面的,方便以后按组去取数据。因为组的权限比较高,所以默认返回的值的按组匹配的结果,也就是['baidu']。
# 如果非得要进行全部匹配的话,那就需要取消组的权限,使得返回一个整体的值
# 取消权限
print(re.findall('www.(?:\w+).com', 'www.bilibili.com'))  # ['www.bilibili.com']
复制代码
search()

返回匹配到的第一个对象,对象可以调用group()方法返回结果。 上面的例子也有用到过这个函数。

经常使用的还有给这些组进行名的操作:

gr = re.search('(?P<id>\d{3}):(?P<port>\w{3})', '192.168.2.215:222')
print(gr.group())               # 215:222
print(gr.group('id'))           # 215
print(gr.group('port'))         # 222
# 以上的  ?P<xxx> 是固定格式 也就是给这个组起一个名字叫xxx, 正则匹配起作用的只是  \d{3} 和 \w{3}
gr1 = re.findall('(?P<id>\d{3}):(?P<port>\w{3})', '192.168.2.215:222')
print(gr1)                      # [('215', '222')]
复制代码
match() 匹配

只在字符串开始匹配 和^的意义是一样的 也是返回匹配到的第一个对象,也可以调用group()返回结果

ret = re.match('res', 'ressssddasda')  # <_sre.SRE_Match object; span=(0, 3), match='res'>
print(ret.group())  
复制代码
split() 分割
sp = re.split('[a, j]', 'wangjun') 
print(sp)                              # ['w', 'ng', 'un']
复制代码

分割顺序: 首先以'a'进行分割 -> 分割成 ['w', 'ngjun'] -> 然后以'j'进行分割 -> 将'ngjun'进行分割 -> ['w', 'ng', 'un']

sub() 替换
sb = re.sub('j..s', 'jxxb', 'wangjunssasd')      
print(sb)                               # wangjxxbsasd
# 第一个的规则,第二个是需要替换成的内容,第三个是原始的字符串 -> 替换的内容是没有要求的
sb = re.sub('j..s', 'jxxb', 'wangjunssasd', 1) 
# 这个1代表的意思就是只替换一次。
 
sbn = re.subn('\d', 'aaa', 'asd2rfdsf4sdf')
print(sbn)                              # 他将返回替换的次数 ('asdaaarfdsfaaasdf', 2)
复制代码
compile()

将正则表达式转换成一个正则表达式的对象,以后就可以重复使用这个对象,然后重复执行一些字符串的匹配,就可以提高工作效率。

re.findall('\.com', 'asda.comsad')
cp = re.compile('\.com')               # 编译的规则 -> 转成了对象
print(cp)                              #
print(cp.findall('asda.comsad'))       # 直接用cp这个对象调用findall()方法,只用传入字符串参数就可以,因为是有了规则了 ['.com']
print(cp.search('asda.comsad').group())
复制代码
finditer() 返回的结果是一个迭代器
iterRet = re.finditer('\.com', 'asda.comsad')
print(iterRet)                         # <callable_iterator object at 0x10629ce80>
print(next(iterRet).group())  	      # ['.com']
复制代码

返回的结果是一个迭代器,那么就可以调用迭代器的next()方法,调用了后还是一个对象,<_sre.SRE_Match object; span=(4, 8), match='.com'>,所以就调用group()方法取到值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值