一篇解决正则表达式(11539字,看完学不会你砍我)

本文介绍了正则表达式的基本概念,包括var_string和operation的逻辑,以及常用通配符的用法。通过实例展示了如何构造var_string和使用不同的operation,如search、match和findall。文中强调了防止转义的重要性,并提供了处理转义字符的策略。最后列举了一些常用的正则表达式操作函数,如search、match、findall和sub。
摘要由CSDN通过智能技术生成

什么是正则表达式

正则表达式英文名叫regular_expression,直译过来就是规则的表达式。正则表达式的目的我自己是这么定义的:利用一定的规则从一个字符串中提取出我们想要的部分。而正则表达式就是用来表示这个规则的。我将正则表达式的代码语句分成以下三个部分:
r e s u l t = o p e r a t i o n ( v a r s t r i n g , s t r i n g ) result=operation(varstring,string) result=operation(varstring,string)
其中result是我们的输出量,string是我们的输入量,这两个可以不用管。所以一个完整的正则表达式的代码语句我们只需要解决两个参数,一个var_string(这个名字是我自己取的,可以把它理解成是一个不知道长度,可以变换的字符串,但是本质是字符串),所以它就是一个字符串,operation表示要用var_string这个字符串干什么。所以从逻辑上我们就可以推理出:正则表达式会先确定var_string是什么,然后根据operation来决定用var_string做什么

用一个非常非常简单的例子来引出我们的内容:

result=re.search('www', 'www.runoob.com')
print(result)

re是我们python自带的一个库,直接import就可以了。
var_string=‘www’,这个字符串在这里例子中就相当明确了,然后转到operation=search(表示匹配)
所以这个指令的要求就是将’www’这个字符串拿去’www.runoob.com’这个字符串中去找。返回的result是re类的一个对象,我们可以利用print来输出这个对象保存的内容:

<re.Match object; span=(0, 3), match='www'>

只要print出来不是None,就代表操作有效结果,'www.runoob.com’中确实存在’www’字符串,并且是在’www.runoob.com’中第0个位置到第2个位置。我们还可以直接用下面的方法使其只返回匹配到的位置:

print(result.span())#(0, 3)

这个例子我并不是想去说明re.search这个函数怎么用,目的只是阐述var_string和operation在操作的时候的逻辑是什么样的。有人可能因此产生一个疑问:如果string里面出现了不止一个’www’怎么办呢?这个可以自己去试试或者往后再说,因为如果再说下去就本末倒置了。

var_string

所以要熟练掌握正则表达式,首要目的构成我们的var_string。上面例子中的var_string是自己给你的。但是’www’还有很多种写法:

'www'
'w{1,}'
'w{1,2,3}'#'w{1,3}','w{3}'
'w+'

上面的所有表达式在本例中的结果都是相同。其中:

  • ‘w{1,}’表示形成由一个w或多个w组成的字符串
  • ‘w{1,2,3}表示形成1个w或2个w或3个w组成的字符串’
  • 'w+'表示形成一个任意个w组成的字符串

这就是正则表达式的便利所在。要注意的是,小中大括号不可混用,各自由各自的含义,大括号类似于我们python中创建的一个集合,集合里面表示的是长度大小,将大括号前面的一个字符重复的次数只要满足大括号里面数字均可。而+号属于通配符中的一种,表示匹配前一个字符任意次。很多人可能会疑惑:啥叫通配符?通配符就是用来在var_string中表示特殊含义的简单符号。由于初次接触到正则表达式的人最大的疑惑点一定在这个通配符上面,所以首当其冲的是要熟悉常用的通配符(那那个大括号怎么办呢,还有中括号,小括号呢?它们也属于通配符,后面会说)

常用通配符

首先要明确,通配符并不是仅仅用于正则表达式,只不过在正则表示中使用的频率尤其明显。通配符其实有很多,但是常用的也就在10个左右。其实通配符我们肯定早就接触过了,我们经常看到\n表示换行,其中**\**就是一种通配符,它的作用就是转义。

  • * :表示重复前一个字符0次或多次
  • +:表示重复前一个字符1次或多次

匹配0次是什么意思呢?这俩为啥要纠结是不是0次呢?我们首先来说匹配0次是什么意思,我直接说结论:有没有这个字符都无所谓。依然以search为例子:

result=re.search('qw{0}', 'qqqqwww.runoob.com')
print(result)#<re.Match object; span=(0, 1), match='q'>

这里顺带也解决了search匹配多个问题,它只会匹配到第一个。可以看到我们将大括号中的长度规定只有0,相当于把w给抹去了,这样匹配的内容就只有q了。这样做的意义是什么呢?再举个例子:

result=re.search('qw{0}aaaa', 'qqqqaaaa.runoob.com')
print(result)#<re.Match object; span=(3, 8), match='qaaaa'>

可以看到匹配的到的内容是‘qaaa’。如果var_string仅仅是存在{0},那么把{0}连同它前面的一个字符拿掉,var_string不会发生实质性的改变。
故而下面两个var_string的等价于:

'qw*a'
'qw+a'

前者表示var_string从q开始,到a结束,中间有没有w,有几个w都无所谓
后者表示var_string从q开始,到a结束,中间必须有且至少有一个w

  • ?:多用于表示operation采用非贪婪算法(之后会举例说明)
  • .:(这前面是个小点不是空格)表示出现任意一个除了换行字符外的任意字符,这个非常重要!
  • ^:表示从头开始
  • $:表示直到末尾
    ^和$是一对,我上面的解释只是一个大概。因为它们俩的原理是一样,所以放一块详说。只要出现^和$,就代表目前这个var_string里面的内容不仅是你能简单用几个通配符和字符就能决定了,它现在还由输入进来的string来决定,举个例子:
example = "马上就要兔年啦"
result_for_start= re.search('^.',  example)
print(result_for_start)#<re.Match object; span=(0, 1), match='马'>
result_for_end=re.search('.$',  example)
print(result_for_end)#<re.Match object; span=(6, 7), match='啦'>

同样是只匹配任意一个字符,一个匹配开头的马,一个匹配末尾的啦。所以^和$并不是给var_string规定的,更像是给operation看的,它会告诉operation,当你在进行操作时候,从头还是尾进行操作。当我们在编写var_string的时候,遇到^就读成从头开始,遇到$就读成直到末尾,这样就能很快翻译出var_string的含义。

在var_string中常用的单个字符表示的通配符只有上面几种(还有一个“|”表示或,由于作用与[]类似,所以我放在后面对比说了)。但是它们可以组合丫,例如:

phone = "2004-959-559 # 这是一个国外电话号码"
result= re.search('#.*$',  phone)
print(result)#<re.Match object; span=(13, 25), match='# 这是一个国外电话号码'>

只要记住,在var_string字符串的两个双引号之内,任何的单个字符只要没有和转义符\一起使用,那么除了我们说的那几个通配符,它们都是这个字符的本意。例如这个例子中的#,它就是#号的意思,所以这个var_string就表示这个字符串从#开始,然后后面跟任意一个字符,同时将**.**重复0到任意次,换句话说就是#开始,后面跟任意多的任何字符,直到末尾,所以我们最终找到的就是#号后面直到最后的所有字符。再来一套组合拳:

string = "第一,绝对不意气用事,第二,绝对不漏判任何一件坏事,第三,绝对裁判的公正漂亮,第六百一十三,我凑特例的"
print(re.search("第.+?,", string))
#['', '绝对不意气用事,', '绝对不漏判任何一件坏事,', '绝对裁判的公正漂亮,', '我凑特例的']

我们将不加?和加?的结果对比就知道了:

<re.Match object; span=(0, 46), match='第一,绝对不意气用事,第二,绝对不漏判任何一件坏事,第三,绝对裁判的公正漂亮,第六百一十三,'>
<re.Match object; span=(0, 3), match='第一,'>

不加问号的var_string表示从第开始,后面跟任意多个任意字符,一直到逗号结束,可问题是,这一条字符串有多个逗号,到底到哪个逗号呢?在我们的程序执行中会默认采用贪婪算法,也就是到最后一个逗号为止。而采用了问号的var_string则表示采用非贪婪算法,那么?应该加在哪个地方,加在出现多次的那个字符的前面,这里就是这个逗号的前面。这样,在遇到第一个逗号的就会停止了。

说完了单字符的通配符,下面来说说多字符的通配符,大部分都是通过”\字符“的形式产生的转移字符

  • \d :表示此处为一个数字字符,介于0到9
  • \D:表示此处为任意一个非数字字符
  • \n:表示此处为一个换行字符
  • \t :表示此处为一个Tab字符
  • \s:表示此处为任意一个白字符,最常见的就是\n和\t

最后来说一下比较特殊的通配符,小括号,中括号,大括号:

  • 小括号:标记匹配字符串的位置,我们在上面print中内容中会发现里面会出现我们匹配到的内容,当我们匹配到的内容不止一个的时候,就可以用小括号在var_string中标识出我们需要匹配到的内容,最后玩一点小手段就可以仅仅将匹配到的内容输出了:
line = "Cats are smarter than dogs"
matchObj_ = re.search('.* are .*? ', line)

print(matchObj_.group())#<re.Match object; span=(0, 17), match='Cats are smarter '>

这时候再来翻译一下var_string就容易多了:这个字符串are前面有一个空格,空格前面有任意多个任意字符,are后面也有空格,后面也有任意多个任意字符,直到后方第一次遇到空格。所以自然输出就是:“Cats are smarter ” 。are一开始就是确定的,我们不过是在寻找are前后的内容是什么,故而我们可以直接利用括号,来直接将这两个内容单独提出来:

line = "Cats are smarter than dogs"
matchObj_ = re.search('(.*) are (.*?) ', line)

print(matchObj_.group())#Cats are smarter 
print(matchObj_.group(1))#Cats
print(matchObj_.group(2))#smarter

第二个括号的右括号在那个空格前后面都可以,相应的第二个括号圈出来后面也就有个空格。
小括号还有一个作用,就像我们在数学计算的时候如果要先算加法再算乘法会将加法的部分用小括号括起来一样,所以它还有将括号内的字符串看作一个整体的作用。最后我们会举一个综合的例子来说明。

  • 中括号:规则该字符选取的范围,只要在中括号里面的信息,都可以被选取,一般它有两种用法:加横杠和不加横杠:
string=' I like learning deep learning'

print(re.search('[bad]',string))#<re.Match object; span=(10, 11), match='a'>
print(re.search('[e-g]',string))#<re.Match object; span=(6, 7), match='e'>

第一种方式是将所有可以匹配的值挨着写,string中中括号里面谁先出现,那么var_string最后就是谁,与中括号中各个字符的前后顺序无关。这一种方式我们还可以通过“|”来替换,将‘[bad]’改成‘b|a|d’是一样的,“|”也是正则表达式下单字符通配符,表示或的意思,这种方式常常用于选择不同字符串而不是字母(选择字母也可以,但是用中括号不是更方便么):

string=' 哈哈哈哈,嘻嘻嘻,呵呵呵呵'

print(re.search('哈哈|哟哟|啾啾',string))#哈哈

第二种则是从前者到后者,它要求前后的字符中间夹的是“一类”字符,例如[a-z]就是小写字符a到z。[A-Z]就是大写A到Z,[0-9]就是0到9。(如果是左端小写,右端大写或者数字左端是大数,右端是小数,可以自己去试试)
其中[0-9]还可以用\d来替换

  • 大括号:用来指名前面一个字符重复的次数,例如我们之前的w{1,2,3},只要满足中括号里面任意次数都可以。

有一个栗子非常有意思,它用来表示0.0.0.0-255.255.255.255的IP地址:

var_string=''([0-2][0-5][0-5]?\.){3}[0-2][0-5][0-5]'

首先前半段我们先利用三个中括号,分别可以得到A,B,C三个数,其中A从0到2,B从0到5,C从0到5。后面规定匹配到第一个小点的时候就打住。斜杠是为了防止小点发生转义(待会会进一步细说),因为小点在正则表达式里面是任意字符的意思。
之后用小括号将这四个字符括起来看作一个整体并且复制3份。
最后再规定一组ABC,这样就可以用来表示IP地址的范围了。

防止转义

我们很多人都知道在pycharm中的字符串,例如我们print(“我爱\n学习”):

我爱
学习

因为这里python编译器会将\n转义为换行。那如果我们就想输出\n呢?有两种方法:

print("我爱\\n学习")#我爱\n学习
print(r"我爱\n学习")#我爱\n学习

可以利用\使得\n等于\和n,也可以在字符串前面加r来防带有\的转义。但是,如果你用我们普通字符串的时候来理解正则表达式的防转义,有的时候要吃大亏,可以看下面这个例子:

string='我爱\n学习,学习?学个屁'

print(re.search((r'我爱\n学习'),string))#<re.Match object; span=(0, 5), match='我爱\n学习'>
print(re.search(('我爱\n学习'),string))#<re.Match object; span=(0, 5), match='我爱\n学习'>

两种方式加不加r结果居然都一样。理由是,当我们使用python中的正则表达式的时候,实际上对字符串里面的内容会编译两次,按照流程来说,第一次是我们var_string形成的时候,就是仅仅在var_string里面,python编译器会编译一次,这个时候会进行一次转义,第二次是我们正则表达式在operation的时候,又会对var_string进行一次编译,这时候又会转义一次。之所以会这样,因为\n在python字符串语言和正则表达式的字符串语言中都是转义字符

那么上面二者为什么会相同就可以解释了
第一个print:

  • 我们抛开正则表达式这个框架来看,r’我爱\n学习’ 就是一个普通的字符串,由于前面添加了r,所以我们的var_string读作:我爱斜杠n学习,这时候我们python的编译就结束了
  • 然后将var_string拿到operation中,这时候正则表达式会对var_string再编译一次,那么它寻找的内容就可以读作:我爱换行学习
  • 最后到string中去寻找,由于string只被python所编译且没有做放转义处理,所以它里面确实存在:我爱换行学习,所以得到了match。
    所以要验证上面的逻辑的准确性就太简单了,我们只需要将string进行防转义处理,这样的话string就是我爱斜杠n学习,第一个print必然找不到:
string=r'我爱\n学习,学习?学个屁'

print(re.search((r'我爱\n学习'),string))#None

第二个print:

  • 依然只关注var_string,由于其会被python先编译一次,所以它拿到operation之前就已经叫:我爱换行学习了。
  • 然后拿到operation中又会被编译一次,请注意,这个时候已经叫我爱换行学习了,换行不会再被编译成其他东西,所以operation编译前后var_string的含义不变。自然也可以从string中找到我爱换行学习。同样,如果我们对string进行放转义处理,第二个print一样找不到:
string=r'我爱\n学习,学习?学个屁'

print(re.search(('我爱\n学习'),string))#None

所以我们得出
结论一:如果var_string里面只有\n这个带\的转义字符,并且string没有做防转义处理,那么var_string前面加不加r无影响

与\n相同性质的还有\t和以及一切白字符(没必要去纠结什么是白字符,最常用的白字符就是\n和\t)。
可以看到我把一开始给的结论给删除了,因为我觉得这个结论看了也记不住,记住了也没屁用。没有固定的结论就是最好的结论。

对于正则表达式还有一些带\的转义字符但是在python编译环境中不属于转义字符的,例如\d,\D,\s。分析起来就简单多了:

string='我爱\s学习,学习?学个屁'

print(re.search((r'我爱\s学习'),string))#None
print(re.search(('我爱\s学习'),string))#None

由于python编译器对于\s不会进行转义,所以两个print传到operation里面都是我爱斜杠s学习,但是在进行operation操作的时候,会将var_sting转义为我爱任意白字符学习。很显然在string中,不管你是否对string加r,\s对于python编译器而言都是斜杠s,所以根本找不着。那我们就是要找到string中的我爱\s学习应该怎么写呢?既然python对\s没有反应,那么就不能让正则表达式对\s进行编译,所以可以改成:

string='我爱\s学习,学习?学个屁'

print(re.search((r'我爱\\s学习'),string))#<re.Match object; span=(0, 6), match='我爱\\s学习'>
print(re.search(('我爱\\\s学习'),string))#<re.Match object; span=(0, 6), match='我爱\\s学习'>

\也可以同时在python和正则表达式中被编译成斜杠。为了防止python先给我编译了,我要么加r防止它预先给我编译我准备送到operation中的双斜杠(第一种)。要么就送它一个斜杠让它编译(第二种),python编译器并不会俄罗斯套娃,比如编译了前两个斜杠成一个斜杠,它不会又和第三个斜杠再编译成一个斜杠,编译过后的字符不会再参与编译。string加不加r不会影响我们的结果,因为python本来就不会编译\s

我们再来看一个例子:

print(re.search("(图)", "贾玲成中国影史票房最高女导演(图)").group())#图
print(re.search(r"(图)", "贾玲成中国影史票房最高女导演(图)").group())#图
print(re.search("\(图\)", "贾玲成中国影史票房最高女导演(图)").group())#(图)
print(re.search(r"\(图\)", "贾玲成中国影史票房最高女导演(图)").group())#(图)

如果我们想把图连同括号一起找出来,首先1号失败了,因为小括号会在operation里面被编译。2号加了r也失败了,因为由于括号在python里面本来就不会编译,所以传递给operation的还是(图)。三号四号成功了,因为“\(”并不会被python编译成转义字符,所以加不加r的作用是一样的,所以传递给operation的内容"(图)",这个时候由于存在斜杠,所以括号不会被转义,而会被认为是小括号本身,故而var_string就是(图)。

所以通过上面的说明,我建议各位在处理可能存在你认为是转义字符的字符串的时候,可以遵循以下的步骤来决定如何设置var_string:

  • 先看看这个转义字符是否在python和正则表达式都会被转义
  • 如果都会被转义,首先看仅仅对于var_string而言,加不加r各自表达的是什么
  • 然后将表达的含义再放到正则表达式的编译环境中看看会被编译成什么
  • 再来决定加不加r或者利用\来防不防转义
  • 如果这个转义字符python环境下不会被转义,而正则表达式会对其转义,直接在该转义字符前加\防正则的转义即可,不要加r。
  • 上一步的方式有的时候也可以通过加r来解决,我只不过提供一个较为万能的方式,毕竟处理的办法非常灵活。

最后有一个小tip我想单独分享以下,如果var_string是如下的格式:

var_string=r'(.*) are (.*?) '

请问这个r有必要加吗?秒答,没必要,只要看到var_string里面没有\,加不加r通通没有影响,这在我们学习工作中可能会经常遇到,所以单独拿出来说一下。

如何看懂var_string以及如何按照自己的目的编写var_string我想如果看懂了我上面的说明应该已经如鱼得水了,那么下一步就是看看几个常用的operation语句

常用operation

可能初学者看着这一个模块很重要,实际上很鸡肋。因为根本记不住,就算能记住某某operation可以用来干嘛,也记不住怎么用,更记不住它的返回值。所以我在这里给大家的建议是,只需要记住正则表达式可以对字符串进行哪些处理,需要的时候再去查用哪个函数,怎么用就可以了。

  • pattern=re.compile(var_string)
    先来说一个最特殊的,有人问string跑哪去了。这一步并不需要string,它的目的是方便对同一个var_string进行多个operation操作,这样只需需要修改compile里面的var_string可以了。使用方法为:
pattern = re.compile('\d+')  #re.compile('\\d+')
result1 = pattern.search('runoob 123 google 456')

#result1=re.compile('\d+').search('runoob 123 google 456')

首先将var_string(\d表示任意一个数字,由于python不会编译\d,所以直接这么写就可以了,+表示重复前面这个字符任意次,所以var_string实际就表示任意长的一个数字)放入compile中返回一个对象叫做pattern,直接将其print可以发现里面的字符串多了一个\,首先我们最好不要去纠结pattern打印出来的结果,如果硬要弄明白为什么多了个斜杠,它相当于是将var_string修改为就算经过operation编译以后也和输入的var_string保持相同含义的样子,为什么要这么做这是底层代码的问题没必要去管它,我将这一段删除免得看糊涂了

然后直接从pattern里面调用searc方法并且输入string就可以了,如果为了装B,还可以像下面注释一样连着写。

  • re.search(vs,s)
    这个op我想大家已经不再陌生了,它表示根据vs的内容,在s里面从头开始搜索第一个与vs内容相同的字符串片段,我们可以通过.group()返回匹配上的内容(有的时候就是vs一开始的样子,有的时候是vs确定以后的样子),也可以通过.span()返回这个字符串片段在原来字符串中的起始索引:
r=re.compile('\d+').search('runoob 123 google 456')
print(r.group())#123
print(r.span())#(7, 10)
  • re.match(vs,s)——看看s的起始位置和vs是否相同,是则像search一样可以返回group和span的信息,否则返回None↓
r=re.compile('\d+').match('runoob 123 google 456')
print(r)#None
r_=re.compile('\d+').match('123 google 456')
print(r_.group())#123
print(r_.span())#(0, 3)
  • re.findall(vs,s)——从s里面查找和vs相同格式或样式的所有字符串片段,并直接返回一个保存了查找到的所有字符串的列表
r=re.compile('\d+').findall('runoob 123 google 456')
print(r)#['123', '456']

这种情况下无法知道123,456在string中的位置

常用的查找函数就是以上三个,重点记住findall就可以了。

  • re.sub(vs,rp,s,count)——替换作用,从s开头开始寻找,找到与vs相同的内容以后(不需要对vs添加*或+号,它会将s中与vs内容相同的片段全部找出来),把找到的字符串片段用字符串rp代替把并返回被替换后的字符串s,count用来控制如果找到多个字符串片段与vs相同,从头到尾替换几个,默认全部替换

这个操作并不会改变原始的s,相当于返回了新的字符串。
它看起来好像是替换,但是我们常常将rp设置为空字符‘’,以此来达从s中删除某些字符串片段的目的,例如删除一个字符串中的所有非数字片段:↓

r=re.compile('\D').sub("",'2004-959-559 # 这是一个国外电话号码')
print(r)#2004959559
  • re.split(vs,s)——对文本进行分割。s从头开始寻找与vs相同的片段,然后用查找到的所有vs片段所在的位置处砍一刀分成多段,并直接用列表返回多段的内容:↓
r=re.compile('第.+?,').split('第一,绝对不意气用事,第二,绝对不漏判任何一件坏事,第三,绝对裁判的公正漂亮,第六百一十三,我凑特例的')
print(r)#['', '绝对不意气用事,', '绝对不漏判任何一件坏事,', '绝对裁判的公正漂亮,', '我凑特例的']

我在编写这段代码的时候犯了一个错,那就是vs里面问号后面的逗号写成了英文的格式,我们在编写代码的时候巴不得全是英文的逗号,到了这里反而要实事求是了。split多用于我们对法律条款等有固定开头的文本进行处理的时候,这样让我们后面的NLP任务训练集更加完美。

以上就是常用的几个operation,只要你记住,凡是你要对字符串进行处理,那么必须第一时间想到正则表达式,不管它能不能行。上面几个已经可以解决百分之80的问题了,有一些特殊的要求你可以通过构建复杂一点的var_string来实现,如果逻辑感不足直接就去搜正则表达式对字符串干嘛干嘛。好了,说明就全部结束了,希望对大家有所帮助,如果哪里说错了或者我文案有误,欢迎指证。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值