python中正则表达式的运用方式

什么是正则表达式

  • 官方解释:正则表达式的概念是使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。

  • 简单来说,正则表达式就是通过一定的匹配规则,从一个字符串中提取出我们想要的数据,

  • 虽然有时候会比较复杂,但无疑它是非常强大的。

  • 在python中要使用正则表达式,首先需要先导入python内置的re模块。


正则表达式的常用方法:

findall()
sub
match
search

正则表达式的匹配规则:

普通字符
元字符

字符集
概括字符集
量词
锚字符

贪婪与非贪婪


正则表达式的灵魂在于匹配规则的灵活使用,而匹配规则,我简单将之分为两大类,一类是普通字符,这类匹配意义往往不大,另一类是元字符,是我们需要重点掌握的内容。


1. 普通字符串的匹配

re.findall()

  • findall是re库的一个重要方法,第一个参数是匹配规则,第二个参数是要匹配的目标字符串,还有第三个参数,我们之后讲,findall返回的结果是一个列表。
import re

target = 'life is short, I use python.'
result = re.findall('python', target) # 这行代码的意思是从target中匹配'python',如果匹配到就返回,没有匹配到就返回空列表。

result1 = re.findall('java', target)

匹配结果:
在这里插入图片描述

  • 如果匹配规则是一个普通字符串的话,意义并不大,试想一下,一个网页上的内容都是变的,处处可能都不一样,我们想用一个固定的普通字符串去匹配到内容,显然是不太合适的。
  • 就比如CSDN,我们想要获取每篇文章的发布时间,每篇的文章的发布时间都是不一样的,用一个固定的字符串显然匹配不出来我们想要的内容。

2. 元字符


第一类:字符集

  • 接下来,就是我们的重头戏了,元字符,我粗略地将他们分为了7类,我们先来看第一类,字符集,用[]表示,中括号内可以写任意字符,各字符间是或的关系(不理解没关系,后面会有代码解释):
  • 现在我们得到了一个这样的字符串 target = ‘abc acc aec agc adc aic’,我们有这样一个需求,需要找出这个字符串中中间是d或者e的单词,我们该怎么做呢?
  • 很多童鞋第一反应就是for循环遍历,for循环当然可以写出来,for循环是非常厉害的,有兴趣的童鞋可以去尝试一下,但是用正则会简单很多,我们来看看怎么做:
import re

target = 'abc acc aec agc adc aic'
result = re.findall('a[de]c', target) # 这一行中的[de]表示这个位置上的字符是d或者是e都可以匹配出来
print(result)

匹配结果:
在这里插入图片描述

  • 这只是字符集[]的一个最简单的应用,现在我们又有一个需求,需要找出这个字符串中中间是b-z之间的任意一个字符的单词,就可以这样写了,而不需要把b-z之间的字符都写出来。
import re

target = 'abc acc aec agc adc aic'
result = re.findall('a[b-z]c', target) # 这一行中的[b‐z]表示这个位置上的字符在b‐z范围内都可以匹配出来
print(result)

匹配结果:
在这里插入图片描述

  • 插入符为[]内的第一个字符时,表示否定
import re

target = 'abc acc aec agc adc aic'
result = re.findall('a[^c-z]c', target) # 这一行中的[^c‐z]表示这个位置上的字符不在c‐z范围内都可以匹配出来,注意是不在
print(result)

匹配结果:
在这里插入图片描述

总结:

匹配规则(举例说明)释义
[abf]表示该位置上的字符为a或者b或者f,即匹配成功
[a-z]表示该位置上的字符在a-z之间,即匹配成功
[â-z]表示该位置上的字符不在a-z之间,即匹配成功

第二类:概括字符集

匹配规则释义等价于
\d表示该位置上的字符是数字,即匹配成功[0-9]
\D表示该位置上的字符不是数字,即匹配成功[^0-9]
\w表示该位置上的字符是字母或_,即匹配成功[A-Za-z_]
\W表示该位置上的字符不是是字母或_,即匹配成功[Â-Za-z_]
\s表示该位置上是不可见字符(空格、制表符\t、垂直制表符\v、回车符\r、换行符\n、换页符\f),即匹配成功[\f\n\t\r\v]
\S表示该位置上不是不可见字符,即匹配成功[^\f\n\t\r\v]
# \d的用法
import re

target = '点赞数:12'
result = re.findall('\d', target) # 这一行中的\d表示只要该位置上的字符是数字,就匹配成功,返回结果,一次只表示一个字符
print(result)

在这里插入图片描述

# \D的用法
import re

target = '点赞数:12'
result = re.findall('\D', target) # 这一行中的\D表示只要该位置上的字符不是数字,就匹配成功,返回结果,一次只表示一个字符
print(result)

在这里插入图片描述

# \w的用法
import re

target = 'I love python_'
result = re.findall('\w', target) # 这一行中的\w表示只要该位置上的字符是字母或者下划线,就匹配成功,返回结果,一次只表示一个字符
print(result)

在这里插入图片描述

# \W的用法
import re

target = 'I love python_'
result = re.findall('\W', target) # 这一行中的\W表示只要该位置上的字符不是字母或者下划线,就匹配成功,返回结果,一次只表示一个字符
print(result)

在这里插入图片描述

# \s的用法
import re

target = 'Life is short \n I love python'
result = re.findall('\s', target) # 这一行中的\s表示只要该位置上的字符是不可见字符,就匹配成功,返回结果,一次只表示一个字符
print(result)

在这里插入图片描述

# \S的用法
import re

target = 'Life is short \n I love python'
result = re.findall('\S', target) # 这一行中的\S表示只要该位置上的字符不是不可见字符,就匹配成功,返回结果,一次只表示一个字符
print(result)

在这里插入图片描述


第三类:量词

匹配规则(举例说明)释义
{3}表示{3}前面的一个字符出现3次
{3,8}表示{3}前面的一个字符出现3-8次
?表示?前面的一个字符出现0次或1次
+表示+前面的一个字符出现1次或无限多次
*表示*前面的一个字符出现0次或无限多次
案例一:
  • 假设现在我们获取到了一本英文书的全部内容,想要判断这本书有多少字数,该怎么做呢?还是老规矩,我们通过代码来解释:
import re

content = "To be or not to be, that is a question."
result = re.findall('\w{1,30}', content) # 这一行中的\w表示一个字母或者_,{1,30}表示\w出现1次到30次之间,只要一个单词的长度在1‐30之间就能被匹配出来
print(result)
print(len(result))

在这里插入图片描述

  • 上述代码还是存在某些缺陷,万一某个英文单词的长度超过30了怎么办?所以我们应该换种方式来写:
import re

content = "To be or not to be, that is a question."
result = re.findall('\w+', content) # +表示匹配一次或多次,只要一个单词的长度大于一就能被匹配出来
print(result)
print(len(result))

在这里插入图片描述

案例二:
  • 提取文章的点赞数或者评论数
import re

content = '点赞数:12'
result = re.findall('\d{1,10}', content)# 这一行中的\d表示一个数字字符,{1,10}表示这个\d出现1‐10次都匹配成功,只要点赞数在0‐9999999999之间都可以匹配出来。
# 同样也可以这样写:
result = re.findall('\d+', content)
print(result)

在这里插入图片描述
至此,数量词我们已经讨论了{}和+,至于?和*,我们放在后面和贪婪与非贪婪一起讲,因为 ? 和 ** 还有 . 经常连在一起使用。


第四类:锚字符

匹配规则释义
^表示只要是以^后面的字符开头的,即匹配成功
$表示只要是以$前面的字符结尾的,即匹配成功
案例一
import re

content = 'https://www.zhihu.com'
content1 = '/question/62749917/answer/576934857'
result = re.findall('^http.*', content) # 这一行的^http表示匹配content的首部是http的内容,后面的.表示一个除换行符\n以外的所有字符,*表示.重复0次或无限多次,.*放在一起就是匹配除换行符以外的任意字符无限多次,这两个字符经常放在一起用,之后会单独讨论。

result1 = re.findall('^http.*', content1)

print('result的匹配结果:{}'.format(result))
print('result1的匹配结果:{}'.format(result1))

在这里插入图片描述

  • 通过上述例子,我们能发现^其实就相当于我们之间讲的字符串的一个方法,叫做startswith。
  • 同样的,有startswith,当然会有类似于endswith的匹配规则,我们用代码来看下:
import re

content = 'https://www.zhihu.com/shiyue.png'
content1 = '/question/62749917/answer/576934857'

result = re.findall('.*png$', content) # 这一行的.*和之前一样,表示png前面可以有除换行符之外的任意字符,png$表示匹配content以png结尾的内容。
result1 = re.findall('.*png$', content1)

print('result的匹配结果:{}'.format(result))
print('result1的匹配结果:{}'.format(result1))

在这里插入图片描述


第五类:组/()

  • 我们为什么要有组这个概念呢,因为一个字符串中,我们有时候只想要其中某一连续的满足某个条件的字符串。
  • 例如content = ‘发布于2018/12/23’,我们需要提取出其中的发布时间,用之间的\d是提取不出来的,因为\d提取不出/,这个时候就可以用到组的概念了。
  • 现在,我们来看看什么是组:
匹配规则(举例说明)释义
(\d+)()内的内容构成一个组,只要当前位置满足\d+就匹配成功,返回()内匹配成功的内容
案例一:提取文章的发布日期
import re

content = '发布于2018/12/23'
result = re.findall('.*?(\d.*\d)', content) # 这一行的.*表示匹配除换行符外的任意多个字符,?表示非贪婪匹配
# (\d.*\d)表示一个组,以数字开头,以数字结尾,.*表示中间可以是除换行符以外的任意多个字符
# 最终返回的结果就是括号内匹配到的结果。
print(result)

在这里插入图片描述
组的作用:只输出匹配组中条件的内容

不加括号的结果:
import re

content = '发布于2018/12/23'
result = re.findall('.*?\d.*\d', content)
print(result)

在这里插入图片描述
因为python默认会在正则表达式首尾各添加一个括号,第三行代码其实等价于

result = re.findall((’.?\d.\d’), content)

正则表达式中可以存在多个组,例如下面这种情况:
案例二:提取发布时间和发布人
import re

content = '发布于2018/12/23,发布人:九月'
result = re.findall('.*?(\d.*\d).*:(.*)', content)
print(result)

在这里插入图片描述

  • 这里的前一部分和上面的代码是一样的意思,两个括号之间的内容.*:表示中间是除换行符以外的任意字符,直到遇见:才终止,进入第二个组。
  • 所以上述正则表达式的意思是:以除换行符以外的任意字符开头,直到遇见第一个组,以数字开头,以数字结尾,这样就能匹配到发布时间2018/12/23,然后又是除换行符外的任意字符,直到遇见:进入第二个组,冒号后面所有的内容构成第二个组,匹配到发布人九月
  • 会发现得到的结果好像和我们想的不一样呀,我们希望得到 (‘2018/12/23’, ‘九月’) ,虽然上述结果我们也方便获取我们想要的内容,但是能不能直接获取到类似 (‘2018/12/23’, ‘九月’) 呢?
  • 这就需要用到re的另一个方法了,match方法。

re.macth()方法——从第一个字符开始匹配(默认在最前面带^)

案例三:提取发布时间和发布人
import re

content = '发布于2018/12/23,发布人:九月'
result = re.match('.*?(\d.*\d).*:(.*)', content) ## match方法的参数和findall是一样的,返回的结果是re.Match对象
print(result.group()) # 该方法默认是result.group(0)

在这里插入图片描述

  • 前文说过re.match(’.?(\d.\d).:(.)’, content)等价于re.match(’(.?(\d.\d).:(.))’, content)
  • result.group(0)获取的内容就是最外层的括号匹配的内容。
    在这里插入图片描述
    在用match方法的时候有一个需要注意的地方,很重要,非常容易导致出错,老规矩,用代码解释:
import re

content = '评论数:12'
result = re.match('\d', content)
print(result)

在这里插入图片描述

  • 得到的结果是None,如果直接print(result.group())是会报错的。
  • 原因在于match方法是从content第一个字符开始去匹配\d,如果未匹配到,直接就返回None。这里因为content第一个字符不是数字,所以直接返回None

第六类:贪婪与非贪婪

案例:提取发布时间
  1. 非贪婪模式
import re

content = '发布于2018/12/23'
result = re.findall('.*?(\d.*\d)', content) # 这里的?表示的就是非贪婪模式,第一个.*会尽可能少地去匹配内容,因为后面跟的是\d,所以碰见第一个数字就终止了。
print(result)

在这里插入图片描述
2. 贪婪模式

import re

content = '发布于2018/12/23'
result = re.findall('.*(\d.*\d)', content)
print(result)

在这里插入图片描述
这里的第一个 .* 后面没有添加问号,表示的就是贪婪模式,第一个 .* 会尽可能多地去匹配内容,后面跟的是\d,碰见第一个数字并不一定会终止,当它匹配到2018的2的时候,发现剩下的内容依然满足(\d.*\d),所以会一直匹配下去,直到匹配到12后面的/的时候,发现剩下的23依然满足(\d.*\d),但是如果.*再匹配下去,匹配到23的2的话,剩下的3就不满足(\d.*\d)了,所以第一个.*就会停止匹配,(\d.*\d)最终匹配到的结果就只剩下23了。

再来看下面这段代码:
import re

content = '发布于2018/12/23'
result = re.findall('.*(\d.*?\d)', content)
print(result)

在这里插入图片描述
得到的结果是[‘23’],原因在于第一个.*是贪婪模式,会一直匹配到12后面的/,这样结果就是[‘23’]

再来看下面这段代码:
import re

content = '发布于2018/12/23'
result = re.findall('.*?(\d.*?\d)', content)
print(result)

在这里插入图片描述

  • 这里的第一个.*?表示非贪婪模式,匹配到2018前面的’于’之后就停止了
  • 括号里的.*?也是表示非贪婪模式,括号里的内容从2018的2开始匹配,因为后面一个数字是0,那么也就满足了(\d.*?\d),所以就直接返回结果了,同样的,接下来的18也是这样,一直匹配到23才结束。
import re

content = '发布于2018/12/23'
result = re.match('.*?(\d.*?\d)', content)
print(result.group())

在这里插入图片描述
简单来说,贪婪模式就是尽可能多地去匹配字符,非贪婪模式就是尽可能少地去匹配字符,python默认采取的是贪婪模式。

贪婪与非贪婪在使用的要慎重,因为一不小心就容易出错,匹配到的结果并不是我们想要的。

re.search()方法

比较search和match方法的区别

import re

content = '点赞数:12'
result_match = re.match('\d', content)
result_search = re.search('\d', content)

在这里插入图片描述

  • 可以看到,使用match方法,会从content的开头去匹配\d,没有匹配到就直接返回None了。
  • 而search方法也是从头开始匹配,只要匹配到有一个字符符合\d,就直接返回了,不会继续往下匹配。
  • search方法返回的也是一个re.Match对象,和match方法的取值是一样的,用group()。

re.sub(待匹配字符串, 待替换字符串, 操作对象[, count=0, flags=])方法

  • 功能:匹配出结果并替换掉内容
  • sub方法的第一个参数是正则表达式,第二个参数是替换之后的字符串,第三个参数是目标字符串
案例一:将content中的php全部换成python
import re

content = 'python php java c javascript java php'
result = re.sub('php', 'python', content)
print(result)

案例二:将content中的php全部替换成python,php不区分大小写
# 只替换小写部分
import re

content = 'python PHP java c javascript java php'
result = re.sub('php', 'python', content)
print(result)

在这里插入图片描述

# 替换大写和小写部分
import re

content = 'python PHP java c javascript java php'
result = re.sub('php', 'python', content, flags=re.I)
print(result)

在这里插入图片描述

  • flags=re.I表示的是匹配模式,re.I表示第一个参数不区分大小写

  • 还有一种常用的匹配模式re.S,它表示“.”(不包含外侧双引号)的作用扩展到整个字符串,包括“\n”。(正常情况下 . 的扩展是按行进行的,也就是说遇到\n就停止匹配,然后从下一行重新开始匹配),加上re.S之后,就是把包括\n在内的文本作为一个整体来进行匹配

案例三:将第一个出现的php替换成python
import re

content = 'python PHP java c javascript java php'
result = re.sub('php', 'python', content, flags=re.I)
# re.sub的第四个参数是count,默认count=0,表示无论匹配到多少个php,都替换成python
result1 = re.sub('php', 'python', content, count=1, flags=re.I)
# 这里的count=1表示无论匹配到多少个php,最多只将第1个php替换成python,count=8的话就表示无论匹配到多少个php,最多只将前8个php替换成python

在这里插入图片描述

接下来,我们来看下sub方法设计的精妙之处,就是sub的第二个参数可以是一个函数。

精妙之处在哪呢,就在于当你拿到匹配结果的时候,不一定要将它替换成固定的字符串,你可以传递一个函数,在函数中对匹配结果进行逻辑处理,这样主动权就交到了用户手上,用户可以随便处理。

案例:将学生的分数替换成相应的等级,0-59分为不及格, 60-79分为中,80-89分为良,90-100分为优(这个功能用字符串的replace方法还是会比较复杂的。)
import re

def judge(value):
    value = value.group() # 用group方法获取到匹配结果,以下逻辑是对value进行逻辑判断
    if int(value) < 60:
        return '不及格'
    elif int(value) < 80:
        return '中'
    elif int(value) < 90:
        return '良'
    else:
        return '优'
content = '小明:59 小红:66 小白:83 小绿:98 小王:100'
result = re.sub('\d+', judge, content)
# 这里sub的第二个参数就是一个函数judge,第一个参数匹配到的结果会作为value传递进judge函数中,从而在judge中可以对他进行判断,函数的返回值将会替换掉匹配结果。
print(result)

在这里插入图片描述


函数式编程

  • 这个sub方法的使用并不难,可有一点值得我们去学习,那就是将函数作为一个参数传递到另一个函数中(而且返回值也可以是一个函数),这种编程思想叫做函数式编程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值